Почему 1.0f + 0.0000000171785715f возвращает 1f? - PullRequest
3 голосов
/ 30 марта 2009

После часа попытки найти ошибку в моем коде я наконец нашел причину. Я пытался добавить очень маленький поплавок к 1f, но ничего не происходило. При попытке выяснить, почему я обнаружил, что добавление этого маленького числа с плавающей точкой к 0f работает отлично.

Почему это происходит? Связано ли это с «порядками величины»? Есть ли решение этой проблемы?

Заранее спасибо.

Edit:

Переключение на двойную точность или десятичную в настоящее время не вариант.

Ответы [ 8 ]

21 голосов
/ 30 марта 2009

Поскольку точность значения с плавающей запятой одинарной точности (32 бита) составляет около 7 цифр после десятичной точки. Это означает, что добавляемое вами значение практически равно нулю, по крайней мере, при добавлении к 1. Однако само значение может быть легко сохранено в плавающей запятой, поскольку показатель степени в этом случае мал. Но чтобы успешно добавить его к 1, необходимо использовать показатель степени большего числа ... и затем цифры после нулей исчезают при округлении.

Вы можете использовать double, если вам нужна большая точность. С точки зрения производительности это не должно иметь значения для современного оборудования, и память часто также не столь ограничена, что вам приходится думать о каждой отдельной переменной.

РЕДАКТИРОВАТЬ: Поскольку вы заявили, что использование double не вариант, вы можете использовать суммирование Кахана , как указано akuhn в комментарии.

Другим вариантом может быть выполнение промежуточных вычислений с двойной точностью, а затем снова приведение к float. Это поможет, однако, только когда есть несколько больше операций, чем просто добавление очень маленького числа к большему.

7 голосов
/ 30 марта 2009
4 голосов
/ 30 марта 2009

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

Это означает, что, хотя вы можете добавить свое маленькое число к 0, вы не можете ожидать, что оно будет добавлено к числу с показателем степени, отличным от 0, поскольку не останется достаточно цифр точности.

Вы должны прочитать Что каждый ученый должен знать об арифметике с плавающей точкой .

3 голосов
/ 09 декабря 2009

В дополнение к принятому ответу: Если вам нужно сложить много маленьких и несколько больших чисел, вы должны использовать Суммирование Кахана .

3 голосов
/ 30 марта 2009

С float вы получите точность порядка семи цифр . Таким образом, ваш номер будет округлен до 1f. Если вы хотите сохранить такой номер, используйте double вместо

http://msdn.microsoft.com/en-us/library/ayazw934.aspx

3 голосов
/ 30 марта 2009

Похоже, это связано с точностью с плавающей запятой. На вашем месте я бы использовал другой тип, например decimal. Это должно исправить ошибки точности.

2 голосов
/ 30 марта 2009

Если производительность является проблемой (потому что вы не можете использовать double), тогда двоичное масштабирование / фиксированная точка может быть вариантом. float s хранятся в виде целых чисел, но масштабируются большим числом (скажем, 2 ^ 16). Промежуточная арифметика выполняется с (относительно быстрыми) целочисленными операциями. Окончательный ответ можно преобразовать обратно в число с плавающей точкой в ​​конце, разделив на коэффициент масштабирования.

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

0 голосов
/ 30 марта 2009

Вы используете суффикс f для своих литералов, который будет делать эти числа с плавающей точкой вместо двойных. Таким образом, ваш очень маленький поплавок исчезнет в большем поплавке.

...