2011-01-12 4 views
12

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

CalculateTotalTax = function (TaxRate, TaxFreePrice) { 
    return ((parseFloat(TaxFreePrice)/100) * parseFloat(TaxRate)).toFixed(4); 
}; 

Мне не удалось ввести любые два значения, которые вызвали для меня неправильный результат для этого метода. Если я удаляю toFixed (4), я могу понять, где вычисления начинают терять точность (где-то около шестого десятичного разряда). Сказав это, мое понимание поплавков заключается в том, что иногда даже небольшие числа могут быть не представлены или я неправильно понял, и их можно было бы точно указать на 4 десятичных знака (например).

MSDN объясняет, как поплавки such ...

Это означает, что они не могут провести точную представления любого количества, которое не двоичная дробь (в^п формы к/ (2), где k и n являются целыми числами)

Теперь я предполагаю, что это относится ко всем поплавкам (в том числе к тем, которые использовались в javascript).

По существу, мой вопрос сводится к этому. Как определить, будет ли какой-либо конкретный метод уязвимым для ошибок в операциях с плавающей запятой, с какой точностью будут выполняться эти ошибки и какие входы потребуются для получения этих ошибок?

Надеюсь, что я прошу, имеет смысл.

+0

Возможно, это связано с этой темой: http://en.wikipedia.org/wiki/Machine_epsilon –

+0

* Тони Пони *, также известный как [Джон Скит] (http://stackoverflow.com/users/22656/jon-skeet), имеет [отличное объяснение] (http://codeblog.jonskeet.uk/2009/11/02/omg-ponies-aka-humanity-epic-fail/). Читаемые, понятные, забавные, не раздражающие и не такие тяжелые, как [некоторые ссылки] (http://docs.sun.com/source/806-3568/ncg_goldberg.html), которые часто публикуются на эту тему. – MarkJ

ответ

10

Начните с чтения Что каждый ученый должен знать о плавающей точкой: http://docs.sun.com/source/806-3568/ncg_goldberg.html

Короткий ответ: двойной точности поплавки (которые по умолчанию в JavaScript) имеют около 16 десятичных цифр точности. Округление может варьироваться от платформы к платформе. Если абсолютно необходимо, чтобы вы получили правильный ответ, вы должны сделать рациональную арифметику самостоятельно (это не должно быть сложно - для валюты, возможно, вы можете просто умножить на 100, чтобы сохранить число центов как целое число).

Но если достаточно получить ответ с высокой степенью точности, поплавки должны быть достаточно хорошими, особенно двойной точностью.

4

Есть два главное, что вы должны теперь, когда дело с поплавками:

1- Вы должны быть осведомлены о machine epsilon. Чтобы узнать, какая у вас точность.

2- Вы не должны принять if two values are equal in base 10, they are equal in base 2 in a machine with precision limit.

if ((6.0/10.0)/3.0 != .2) { 
     cout << "gotcha" << endl; 
} 

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

+1

Номер 2 не означает, что вы «никогда не должны сравнивать числа с плавающей запятой для равенства»; в конце концов, так же легко следует, что вы никогда не должны сравнивать десятичные числа для равенства. Это означает, что нельзя писать код, который он не понимает. Потратьте время, чтобы узнать, как работает плавающая точка. –

+0

Как вы сказали, нужно время, затрачиваемое на обучение плавающей точке; что-то вроде (0.1 == 1/10) выглядит так невинно, что никто, вероятно, не подумает, что он вызывает ошибку, если только он не читал об этом раньше. Даже говоря об основах математики, мало кто знает, что 10 = 9.999 .... или как преобразовать десятичную дробь в двоичную. –

+1

Причина '.1! = 1/10' заключается в том, что' 1/10 == 0'. Вы используете целочисленное деление. –

1

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

Попробуйте .1 * 3, или 162.295/10, или 24.0 + 47.98. Мне это не удается в JS. Но, 24.0 * 47.98 не подводит.

Чтобы ответить на ваши три вопроса, любая операция для любой точности потенциально уязвима. Независимо от того, будет ли данный вход или нет, я не знаю, как ответить, но у меня есть догадка, что есть ряд факторов. 1) Как близко фактический ответ на ближайшую двоичную дробь. 2) Точность в двигателе, выполняющая расчет. 3) Метод, используемый для выполнения расчета (например, умножение на сдвиг бит может давать разные результаты, чем умножение путем повторного добавления)

2

Другие ответы указывают на хорошие ресурсы для понимания этой проблемы. Если вы фактически используете денежные значения в своем коде (как в вашем примере), вы должны предпочесть десятичные типы (System.Decimal в .Net). Это позволит избежать некоторых проблем округления при использовании float и лучше соответствовать домену.

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