Понимание проблем с плавающей запятой
Может кто-то здесь, пожалуйста, помогите мне понять, как определить, когда ограничения с плавающей запятой вызовут ошибки в ваших расчетах. Например, следующий код.
CalculateTotalTax = function (TaxRate, TaxFreePrice) {
return ((parseFloat(TaxFreePrice) / 100) * parseFloat(TaxRate)).toFixed(4);
};
Мне не удалось ввести какие-либо два значения, которые вызвали для меня неправильный результат для этого метода. Если я удаляю toFixed (4), я могу понять, где вычисления начинают терять точность (где-то около шестого десятичного разряда). Сказав это, мое понимание поплавков заключается в том, что иногда даже небольшие числа могут быть не представлены или я неправильно понял, и их можно было бы точно указать на 4 десятичных знака (например).
MSDN объясняет float как такой...
Это означает, что они не могут представление любого количества, которое а не двоичная дробь (формы k/ (2 ^ n), где k и n - целые числа)
Теперь я предполагаю, что это относится ко всем поплавкам (в том числе к тем, которые используются в javascript).
В сущности, мой вопрос сводится к этому. Как определить, будет ли какой-либо конкретный метод уязвимым для ошибок в операциях с плавающей запятой, с какой точностью будут выполняться эти ошибки и какие входы потребуются для создания этих ошибок?
Надеюсь, то, что я прошу, имеет смысл.
Ответы
Ответ 1
Начните с чтения Что каждый компьютерный ученый должен знать о плавающей точке:
http://docs.sun.com/source/806-3568/ncg_goldberg.html
Короткий ответ: float с двойной точностью (по умолчанию в JavaScript) имеет около 16 десятичных цифр точности. Округление может варьироваться от платформы к платформе. Если абсолютно необходимо, чтобы вы получили правильный ответ, вы должны сделать рациональную арифметику самостоятельно (это не должно быть сложно - для валюты, возможно, вы можете просто умножить на 100, чтобы сохранить число центов как целое число).
Но если достаточно получить ответ с высокой степенью точности, поплавки должны быть достаточно хорошими, особенно двойной точностью.
Ответ 2
Есть две важные вещи, которые вы должны сейчас при работе с float:
1- Вы должны знать машину epsilon. Чтобы узнать, какая у вас точность.
2- Вы не должны считать если два значения равны в базе 10, они равны в базе 2 в машине с предел точности.
if ((6.0 / 10.0) / 3.0 != .2) {
cout << "gotcha" << endl;
}
Номер 2 может быть достаточно убедителен, чтобы избежать сравнения чисел с плавающей запятой для равенства, вместо этого для сравнения можно использовать пороговые и более крупные или меньшие операторы
Ответ 3
Другие ответы указывают на хорошие ресурсы для понимания этой проблемы. Если вы фактически используете денежные значения в своем коде (как в вашем примере), вы должны предпочесть десятичные типы (System.Decimal в .Net). Это позволит избежать некоторых проблем округления при использовании float и лучше соответствовать домену.
Ответ 4
Нет, число десятичных знаков не имеет ничего общего с тем, что может быть представлено.
Попробуйте .1 * 3, или 162.295/10, или 24.0 + 47.98. Мне это не удается в JS. Но, 24.0 * 47.98 не подводит.
Итак, чтобы ответить на ваши три вопроса, любая операция для любой точности потенциально уязвима. Независимо от того, будет ли данный вход или нет, я не знаю, как ответить, но у меня есть догадка, что есть ряд факторов. 1) Как близко фактический ответ на ближайшую двоичную дробь. 2) Точность в двигателе, выполняющая расчет. 3) Метод, используемый для выполнения вычисления (например, умножение на сдвиг бит может давать разные результаты, чем умножение путем повторного добавления)