Математика с плавающей точкой нарушена? - PullRequest
2646 голосов
/ 26 февраля 2009

Рассмотрим следующий код:

0.1 + 0.2 == 0.3  ->  false
0.1 + 0.2         ->  0.30000000000000004

Почему возникают эти неточности?

Ответы [ 30 ]

1994 голосов
/ 26 февраля 2009

Binary с плавающей запятой математика такая. В большинстве языков программирования он основан на стандарте IEEE 754 . В JavaScript используется 64-битное представление с плавающей точкой, которое совпадает с Java double. Суть проблемы заключается в том, что числа представлены в этом формате как целое число, умноженное на два; рациональные числа (такие как 0.1, то есть 1/10), знаменатель которых не является степенью двойки, не могут быть точно представлены.

Для 0.1 в стандартном формате binary64 представление может быть записано точно как

Напротив, рациональное число 0.1, которое является 1/10, может быть записано точно как

  • 0.1 в десятичном виде или
  • 0x1.99999999999999...p-4 в аналоге шестигранной нотации C99, где ... представляет бесконечную последовательность из 9-х.

Константы 0.2 и 0.3 в вашей программе также будут приблизительными к их истинным значениям. Бывает, что ближайшее double к 0.2 больше рационального числа 0.2, но самое близкое double к 0.3 меньше рационального числа 0.3. Сумма 0.1 и 0.2 оказывается больше рационального числа 0.3 и, следовательно, не согласуется с константой в вашем коде.

Достаточно всеобъемлющий подход к решению арифметических операций с плавающей запятой: Что должен знать каждый компьютерный специалист об арифметике с плавающей запятой . Более простое объяснение см. В floatingpoint-gui.de .

.

Боковое примечание: Все позиционные (базовые N) системы счисления точно решают эту проблему

Обычные старые десятичные числа (основание 10) имеют те же проблемы, поэтому такие числа, как 1/3, заканчиваются на 0,333333333 ...

Вы только что наткнулись на число (3/10), которое легко представить с помощью десятичной системы, но не соответствует двоичной системе. Он также идет в обоих направлениях (в некоторой степени): 1/16 - это уродливое число в десятичном виде (0,0625), но в двоичном виде оно выглядит так же аккуратно, как 10 000-е в десятичном (0,0001) ** - если бы мы были в Привычка использовать систему счисления с базой 2 в нашей повседневной жизни - вы даже посмотрите на это число и инстинктивно поймете, что можете прийти туда, вдвое уменьшив что-то вдвое, снова и снова и снова.

** Конечно, это не совсем то, как числа с плавающей запятой хранятся в памяти (они используют форму научной записи). Тем не менее, это иллюстрирует тот факт, что двоичные ошибки точности с плавающей точкой имеют тенденцию возникать, потому что числа «реального мира», с которыми мы обычно заинтересованы работать, часто имеют степень десяти - но только потому, что мы используем десятичную систему счисления день - сегодня. По этой же причине мы будем говорить такие вещи, как 71% вместо «5 из каждых 7» (71% - это приблизительное значение, поскольку 5/7 нельзя точно представить ни одним десятичным числом).

Так что нет: двоичные числа с плавающей запятой не ломаются, они просто так же несовершенны, как и любая другая система счисления с базовым N:)

Side Side Примечание: Работа с плавающими в программировании

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

Вам также необходимо заменить тесты на равенство сравнениями, которые допускают некоторую степень допуска, что означает:

До Не До if (float1 == float2) { ... }

Вместо этого if (Math.Abs(float1 - float2) < myToleranceValue) { ... }.

myToleranceValue может быть что-то вроде 1/2 ^ 16 (0.0000152587890625). В Javascript значение Number.EPSILON предоставляется для использования в качестве допуска.

558 голосов
/ 18 апреля 2013

Перспектива разработчика оборудования

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

1. Обзор

С инженерной точки зрения большинство операций с плавающей запятой будут иметь некоторый элемент ошибки, поскольку аппаратное обеспечение, которое выполняет вычисления с плавающей запятой, должно иметь ошибку менее половины одной единицы в последнем месте. Следовательно, большое количество аппаратного обеспечения остановится с точностью, необходимой только для того, чтобы выдать ошибку менее половины одного блока на последнем месте для одиночной операции , что особенно проблематично при делении с плавающей запятой. То, что составляет одну операцию, зависит от того, сколько операндов принимает блок. Для большинства это два, но некоторые единицы принимают 3 или более операндов. Из-за этого нет гарантии, что повторные операции приведут к желаемой ошибке, так как ошибки накапливаются со временем.

2. Стандарты

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

В стандарте IEEE-754 разработчикам аппаратного обеспечения разрешается любое значение error / epsilon, если в последнем месте меньше половины одного модуля, а результат должен составлять менее половины одного модуля. в последнем месте за одну операцию. Это объясняет, почему при повторных операциях ошибки складываются. Для двойной точности IEEE-754 это 54-й бит, поскольку 53 бита используются для представления числовой части (нормализованной), также называемой мантиссой, числа с плавающей запятой (например, 5.3 в 5.3e5). В следующих разделах более подробно рассматриваются причины аппаратной ошибки при различных операциях с плавающей запятой.

3. Причина ошибки округления в делении

Основной причиной ошибки в делении с плавающей запятой являются алгоритмы деления, используемые для вычисления отношения. Большинство компьютерных систем вычисляют деление, используя умножение на обратное, в основном в Z=X/Y, Z = X * (1/Y). Деление вычисляется итеративно, то есть каждый цикл вычисляет некоторые биты частного до тех пор, пока не будет достигнута желаемая точность, которая для IEEE-754 равна нулю с ошибкой менее одной единицы в последнем месте. Таблица обратных значений Y (1 / Y) называется таблицей выбора коэффициентов (QST) при медленном делении, а размер в битах таблицы коэффициентов выбора обычно равен ширине радиуса или числу битов. коэффициент, вычисленный в каждой итерации, плюс несколько защитных битов. Для стандарта IEEE-754 с двойной точностью (64 бита) это будет размер радиуса делителя плюс несколько защитных битов k, где k>=2. Так, например, типичная таблица выбора коэффициента для делителя, который вычисляет 2 бита частного за раз (основание 4), будет 2+2= 4 бит (плюс несколько необязательных бит).

3.1 Ошибка округления деления: аппроксимация взаимности

То, какие обратные величины находятся в таблице выбора коэффициентов, зависит от метода деления : медленное деление, такое как деление SRT, или быстрое деление, такое как деление Голдшмидта; каждая запись модифицируется в соответствии с алгоритмом деления в попытке получить минимально возможную ошибку. В любом случае, однако, все обратные значения являются приближениями фактического обратного значения и вносят некоторый элемент ошибки. И методы с медленным, и с быстрым делением вычисляют частное итеративно, т. Е. Определенное количество бит частного вычисляется на каждом шаге, затем результат вычитается из делимого, и делитель повторяет шаги, пока ошибка не станет меньше половины одного блок на последнем месте. Методы медленного деления вычисляют фиксированное количество цифр отношения на каждом шаге и, как правило, дешевле в построении, а методы быстрого деления вычисляют переменное количество цифр на шаг и, как правило, стоят дороже. Самая важная часть методов деления состоит в том, что большинство из них полагаются на повторное умножение на приближение обратной величины, поэтому они подвержены ошибкам.

4. Ошибки округления в других операциях: усечение

Другой причиной ошибок округления во всех операциях являются различные режимы усечения окончательного ответа, которые допускает IEEE-754. Есть усечение, округление к нулю, округление до ближайшего (по умолчанию), округление вниз и округление вверх. Все методы вводят элемент ошибки менее чем на одну единицу в последнем месте для одной операции. Со временем и повторяющимися операциями усечение также добавляет к результирующей ошибке. Эта ошибка усечения особенно проблематична при возведении в степень, которая включает в себя некоторую форму повторного умножения.

5. Повторные операции

Поскольку аппаратное обеспечение, которое выполняет вычисления с плавающей запятой, должно давать только результат с ошибкой менее одной половины одного блока в последнем месте для одной операции, ошибка будет расти по сравнению с повторными операциями, если их не наблюдать. Это причина того, что в вычислениях, которые требуют ограниченной ошибки, математики используют такие методы, как использование округленной до ближайшей четной цифры в последнем месте IEEE-754, потому что со временем ошибки более вероятно, что они будут взаимно аннулировать, а Интервальная арифметика в сочетании с вариациями IEEE 754 режимов округления для прогнозирования ошибок округления и их исправления. Из-за его низкой относительной ошибки по сравнению с другими режимами округления, округление до ближайшей четной цифры (на последнем месте) является режимом округления по умолчанию IEEE-754.

Обратите внимание, что режим округления по умолчанию, округление до ближайшей четной цифры на последнем месте , гарантирует ошибку менее половины одного устройства в последнем месте для одной операции. Использование только усечения, округления и округления может привести к ошибке, которая больше, чем половина одного блока в последнем месте, но меньше, чем один блок в последнем месте, поэтому эти режимы не рекомендуются, если они не используется в интервальной арифметике.

6. Краткое описание

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

421 голосов
/ 26 февраля 2009

Когда вы конвертируете .1 или 1/10 в основание 2 (двоичное), вы получаете повторяющийся шаблон после десятичной точки, точно так же, как пытаетесь представить 1/3 в основании 10. Значение не является точным, и поэтому вы можете не делайте точных математических расчетов, используя обычные методы с плавающей запятой.

273 голосов
/ 20 ноября 2014

Большинство ответов здесь решают этот вопрос в очень сухих технических терминах. Я хотел бы рассмотреть это в терминах, которые могут понять нормальные люди.

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

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

Теперь, как бы вы нарезали все ломтики таким образом, чтобы можно было получить одну десятую (0,1) или одну пятую (0,2) пиццы? На самом деле подумайте об этом и попробуйте решить это. Вы даже можете попробовать настоящую пиццу, если у вас под рукой мифическая прецизионная пиццерия. : -)


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

Для чисел с двойной точностью (то есть точности, которая позволяет вам вдвое сократить пиццу в 53 раза), числа, которые сразу меньше и больше 0,1, равны 0,09999999999999999167332731531132594682276248931884765625 и 0,1000000000000000055511151231257827021181583824520125 Последний немного ближе к 0,1, чем первый, поэтому числовой синтаксический анализатор при вводе 0,1 будет благоприятствовать последнему.

(Разница между этими двумя числами заключается в «наименьшем срезе», который мы должны решить либо включить, который вводит смещение вверх, либо исключить, который вводит смещение вниз. Технический термин для этого наименьшего среза - ULP .)

В случае 0,2 все числа одинаковы, только увеличены в 2 раза. Опять же, мы предпочитаем значение, немного превышающее 0,2.

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

В частности, 0,1 + 0,2 на самом деле 0,1000000000000000055511151231257827021181583404541015625 + 0,200000000000000011102230246251565404236316680908203125 = 0,3000000000000000444089209899 033 099 099 099 099 099 099 099 099 099 099 099 099 099 099 067 0291995 0291 5995 067 099 067 029 099 099 099 067 029 099 029 099 029 099 099 029 099 099 099 099 0991 099 099 099 899 099 099 0999 0) 399 0))))) здесь не здесь)))))))))))))


P.S. Некоторые языки программирования также предоставляют ножницы для пиццы, которые могут разделять ломтики на точные десятые . Хотя такие ножницы для пиццы являются редкостью, если у вас есть доступ к одному, вам следует использовать его, когда важно иметь возможность получить ровно одну десятую или одну пятую части.

(Первоначально опубликовано в Quora.)

204 голосов
/ 26 февраля 2009

Ошибки округления с плавающей точкой. 0,1 не может быть представлен с такой же точностью в base-2, как в base-10, из-за отсутствующего простого множителя 5. Точно так же, как 1/3 занимает бесконечное количество цифр для представления в десятичном виде, но равен «0.1» в base-3, 0.1 принимает бесконечное количество цифр в base-2, а не в base-10. И у компьютеров нет бесконечного количества памяти.

113 голосов
/ 09 апреля 2010

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

Например:

var result = 1.0 + 2.0;     // result === 3.0 returns true

... вместо:

var result = 0.1 + 0.2;     // result === 0.3 returns false

Выражение 0.1 + 0.2 === 0.3 возвращает false в JavaScript, но, к счастью, целочисленная арифметика в числах с плавающей точкой является точной, поэтому путем масштабирования можно избежать ошибок десятичного представления.

В качестве практического примера, чтобы избежать проблем с плавающей запятой, где точность является первостепенной, рекомендуется 1 обрабатывать деньги как целое число, представляющее количество центов: 2550 центов вместо 25.50 долларов.


1 Дуглас Крокфорд: JavaScript: Хорошие части : Приложение A - Ужасные части (стр. 105) .

92 голосов
/ 23 февраля 2015

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

Преамбула

двоичный формат с плавающей точкой двойной точности IEEE 754 (binary64) число представляет число вида

значение = (-1) ^ с * (1 м 51 м 50 ... м 2 м 1 м 0 ) 2 * 2 e-1023

в 64 битах:

  • Первый бит - это знаковый бит : 1, если число отрицательное, 0 в противном случае 1 .
  • Следующими 11 битами являются экспонента , что составляет смещение на 1023. Другими словами, после считывания экспонентных битов из числа с двойной точностью, 1023 должно быть вычтено из получить силу двух.
  • Остальные 52 бита имеют значение и (или мантиссу). В мантиссе «подразумеваемый» 1. всегда 2 опускается, так как самый старший бит любого двоичного значения - 1.

1 - IEEE 754 допускает концепцию нулевого знака - +0 и -0 трактуются по-разному: 1 / (+0) - положительная бесконечность; 1 / (-0) отрицательная бесконечность. Для нулевых значений биты мантиссы и экспоненты равны нулю. Примечание: нулевые значения (+0 и -0) явно не классифицируются как денормальные 2 .

2 - Это не относится к ненормальным числам , у которых показатель смещения равен нулю (и подразумевается 0.). Диапазон числовых чисел двойной точности равен d min ≤ | x | ≤ d max , где d min (наименьшее представимое ненулевое число) составляет 2 -1023 - 51 (≈ 4,94 * 10 -324 ) и d max (наибольшее денормальное число, для которого мантисса полностью состоит из 1 с) составляет 2 -1023 + 1 - 2 -1023 - 51 (≈ 2,225 * 10 -308 ).


Превращение числа с двойной точностью в двоичное

Существует много онлайн-конвертеров для преобразования числа с плавающей запятой двойной точности в двоичное (например, в binaryconvert.com ), но здесь приведен пример кода C # для получения представления IEEE 754 для числа двойной точности ( Я разделяю три части с помощью двоеточий (:):

public static string BinaryRepresentation(double value)
{
    long valueInLongType = BitConverter.DoubleToInt64Bits(value);
    string bits = Convert.ToString(valueInLongType, 2);
    string leadingZeros = new string('0', 64 - bits.Length);
    string binaryRepresentation = leadingZeros + bits;

    string sign = binaryRepresentation[0].ToString();
    string exponent = binaryRepresentation.Substring(1, 11);
    string mantissa = binaryRepresentation.Substring(12);

    return string.Format("{0}:{1}:{2}", sign, exponent, mantissa);
}

Приступая к делу: оригинальный вопрос

(Перейти к нижней части для версии TL; DR)

Катон Джонстон (задающий вопрос) спросил, почему 0,1 + 0,2! = 0,3.

Записанные в двоичном виде (с двоеточиями, разделяющими три части), представления значений IEEE 754:

0.1 => 0:01111111011:1001100110011001100110011001100110011001100110011010
0.2 => 0:01111111100:1001100110011001100110011001100110011001100110011010

Обратите внимание, что мантисса состоит из повторяющихся цифр 0011. Это ключ к тому, почему в вычислениях есть какая-либо ошибка - 0,1, 0,2 и 0,3 не могут быть представлены в двоичном виде точно в конечном количестве двоичных битов любого более 1/9, 1/3 или 1/7 могут быть представлены точно в десятичных цифрах .

Также обратите внимание, что мы можем уменьшить мощность в показателе степени на 52 и сместить точку в двоичном представлении вправо на 52 места (очень похоже на 10 -3 * 1.23 == 10 -5 * 123). Это тогда позволяет нам представить двоичное представление как точное значение, которое оно представляет в форме a * 2 p . где «а» - целое число.

Преобразование показателей степени в десятичное, удаление смещения и повторное добавление подразумеваемых 1 (в квадратных скобках), 0,1 и 0,2:

0.1 => 2^-4 * [1].1001100110011001100110011001100110011001100110011010
0.2 => 2^-3 * [1].1001100110011001100110011001100110011001100110011010
or
0.1 => 2^-56 * 7205759403792794 = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794 = 0.200000000000000011102230246251565404236316680908203125

Чтобы добавить два числа, показатель степени должен быть одинаковым, т. Е .:

0.1 => 2^-3 *  0.1100110011001100110011001100110011001100110011001101(0)
0.2 => 2^-3 *  1.1001100110011001100110011001100110011001100110011010
sum =  2^-3 * 10.0110011001100110011001100110011001100110011001100111
or
0.1 => 2^-55 * 3602879701896397  = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794  = 0.200000000000000011102230246251565404236316680908203125
sum =  2^-55 * 10808639105689191 = 0.3000000000000000166533453693773481063544750213623046875

Поскольку сумма не имеет вид 2 n * 1. {bbb}, мы увеличиваем показатель степени на единицу и сдвигаем десятичную ( двоичная ) точку, чтобы получить:

sum = 2^-2  * 1.0011001100110011001100110011001100110011001100110011(1)
    = 2^-54 * 5404319552844595.5 = 0.3000000000000000166533453693773481063544750213623046875

Теперь в мантиссе 53 бита (53-я в квадратных скобках в строке выше). По умолчанию режим округления для IEEE 754 равен ' Округление до ближайшего ' - т.е. если число x находится между двумя значениями a и b , выбрано значение, где младший значащий бит равен нулю.

a = 2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875
  = 2^-2  * 1.0011001100110011001100110011001100110011001100110011

x = 2^-2  * 1.0011001100110011001100110011001100110011001100110011(1)

b = 2^-2  * 1.0011001100110011001100110011001100110011001100110100
  = 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125

Обратите внимание, что a и b отличаются только последним битом; ...0011 + 1 = ...0100. В этом случае значение с наименьшим значащим нулевым битом равно b , поэтому сумма равна:

sum = 2^-2  * 1.0011001100110011001100110011001100110011001100110100
    = 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125

, тогда как двоичное представление 0,3:

0.3 => 2^-2  * 1.0011001100110011001100110011001100110011001100110011
    =  2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875

, который отличается от двоичного представления суммы 0,1 и 0,2 только на 2 -54 .

Бинарное представление 0,1 и 0,2 является наиболее точным представлением чисел, допустимым IEEE 754. Добавление этого представления из-за режима округления по умолчанию приводит к значению, которое отличается только младший бит.

TL; DR

Запись 0.1 + 0.2 в двоичном представлении IEEE 754 (с двоеточиями, разделяющими три части) и сравнение его с 0.3, это (я поставил отдельные биты в квадратных скобках):

0.1 + 0.2 => 0:01111111101:0011001100110011001100110011001100110011001100110[100]
0.3       => 0:01111111101:0011001100110011001100110011001100110011001100110[011]

Преобразовано обратно в десятичное, эти значения:

0.1 + 0.2 => 0.300000000000000044408920985006...
0.3       => 0.299999999999999988897769753748...

Разница составляет ровно 2 -54 , что составляет ~ 5.5511151231258 × 10 -17 - незначительно (для многих приложений) по сравнению с исходными значениями.

Сравнение последних нескольких битов числа с плавающей запятой по своей природе опасно, так как любой, кто читает знаменитое «, что должен знать каждый компьютерщик об арифметике с плавающей запятой » (которая охватывает все основные части этот ответ) узнаем.

Большинство калькуляторов используют дополнительные защитные цифры , чтобы обойти эту проблему, вот как 0.1 + 0.2 даст 0.3: последние несколько бит округляются.

53 голосов
/ 16 марта 2016

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

Если бы компьютер работал в базе 10, 0.1 было бы 1 x 10⁻¹, 0.2 было бы 2 x 10⁻¹, а 0.3 было бы 3 x 10⁻¹. Целочисленная математика проста и точна, поэтому добавление 0.1 + 0.2, очевидно, приведет к 0.3.

Компьютеры обычно не работают в базе 10, они работают в базе 2. Вы все еще можете получить точные результаты для некоторых значений, например, 0.5 равно 1 x 2⁻¹ и 0.25 равно 1 x 2⁻², и добавив их приводит к 3 x 2⁻² или 0.75. Точно.

Проблема возникает с числами, которые могут быть представлены точно в базе 10, но не в базе 2. Эти числа должны быть округлены до их ближайшего эквивалента. Предполагая, что очень распространенный IEEE 64-битный формат с плавающей запятой, ближайший номер к 0.1 равен 3602879701896397 x 2⁻⁵⁵, а ближайший к 0.2 равен 7205759403792794 x 2⁻⁵⁵; сложение их вместе дает 10808639105689191 x 2⁻⁵⁵ или точное десятичное значение 0.3000000000000000444089209850062616169452667236328125. Числа с плавающей точкой обычно округляются для отображения.

44 голосов
/ 26 февраля 2009

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

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

31 голосов
/ 26 декабря 2011

Мой обходной путь:

function add(a, b, precision) {
    var x = Math.pow(10, precision || 2);
    return (Math.round(a * x) + Math.round(b * x)) / x;
}

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

...