2014-08-28 3 views
12

Я заметил что-то очень странное при работе с добавлением обнуляемых поплавков. Возьмем следующий код:Любопытное поведение при выполнении добавления на неустойчивых поплавках

float? a = 2.1f; 
float? b = 3.8f; 
float? c = 0.2f; 
float? result = 
(a == null ? 0 : a) 
+ (b == null ? 0 : b) 
+ (c == null ? 0 : c); 
float? result2 = 
(a == null ? 0 : a.Value) 
+ (b == null ? 0 : b.Value) 
+ (c == null ? 0 : c.Value); 

result является 6.099999 тогда result2 является 6.1. Мне повезло, что я наткнулся на это, потому что, если я изменил значения для a, b и c, поведение, как правило, выглядит правильным. Это также может случиться с другими арифметическими операторами или другими типами значений NULL, но это случай, когда я смог воспроизвести. Я не понимаю, почему неявный листинг в float от float? не работал правильно в первом случае. Я мог бы понять, попытался ли он получить значение int, учитывая, что другая сторона условного номера равна 0, но это не похоже на то, что происходит. Учитывая, что result отображается некорректно для некоторых комбинаций значений с плавающей запятой, я предполагаю, что это какая-то проблема округления с несколькими преобразованиями (возможно, из-за бокса/распаковки или чего-то еще).

Любые идеи?

+0

Просто, чтобы вы знали, [floats не являются точными значениями в компьютерах] (http://floating-point-gui.de/) – Default

+0

http://stackoverflow.com/questions/6683059/are-floating -point-numbers-compatible-in-c-can-they-be – Donal

+1

Я предполагаю, что разница заключается в «значениях в регистре» и «значениях в стеке»; регистры ** намного шире **, чем те же значения в стеке (в данном случае 80 бит вместо 32). Если компилятор может сделать все, что нужно для записи в регистре, он может сохранить большую точность. Если он скомпилирует его для использования стека: гораздо меньше. Но: вы всегда должны ожидать округления с плавающими точками. –

ответ

4

Просмотреть комментарии от @EricLippert.

НИЧЕГО разрешается изменить результат - позвольте мне подчеркнуть, что снова НИЧЕГО ВООБЩЕ включая фазы луны разрешается изменений ли вычисляются поплавки в 32-битной точности или выше точности. Процессор всегда разрешен по любой причине. решил внезапно начать выполнение арифметики с плавающей запятой в 80 бит или 128 бит или независимо от того, что он выбирает, если он больше или равен 32-битная точность. См. (.1f+.2f==.3f) != (.1f+.2f).Equals(.3f) Why? для получения более подробной информации.

Запрашивается, что конкретно в данном случае заставило процессор принять решение , чтобы использовать более высокую точность в одном случае, а не в другом, является проигрышной игрой . Это может быть ничего. Если вам нужны точные вычисления в десятичные цифры затем используйте метко названный decimal. Если вам нужны повторяющиеся вычисления в поплавках, тогда C# имеет два механизма для , заставляя процессор вернуться к 32 бит. (1) явно приложить к (float) без необходимости или (2) сохранить результат в элементе массива float или в поле float поля ссылочного типа.

Поведение здесь не имеет ничего общего с типом Nullable. Это вопрос о том, что поплавки никогда не были точными и были рассчитаны с разной точностью по прихотям процессора.

В общем, это сводится к совету, что если точность важна, лучше всего использовать что-то другое, кроме float (или использовать методы, описанные @EricLippert, чтобы заставить процессор использовать 32-битную точность).

answer from Eric Lippert on linked question также полезен при понимании того, что происходит.

Смежные вопросы