Любопытное поведение при выполнении добавления на Nullable Floats

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

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 отображается некорректно для некоторых комбинаций значений с плавающей запятой, я предполагаю, что это какая-то проблема округления с несколькими преобразованиями (возможно, из-за бокса/распаковки или чего-то еще).

Любые идеи?

Ответ 1

См. комментарии от @EricLippert.

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

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

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

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

Ответ от Эрика Липперта по связанному вопросу также полезен для понимания того, что происходит.