Сбита ли математика с плавающей запятой?

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

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

Почему происходят эти неточности?

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

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

  • 0.1000000000000000055511151231257827021181583404541015625 в десятичной 0.1000000000000000055511151231257827021181583404541015625 или
  • 0x1.999999999999ap-4 в обозначении гексафлоата C99 .

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

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

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

Довольно комплексное рассмотрение арифметических вопросов с плавающей точкой – это то, что должен знать каждый компьютерный ученый о арифметике с плавающей точкой . Более простое объяснение см. В файле floating-point-gui.de .

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

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

1. Обзор

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

2. Стандарты

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

В стандарте IEEE-754 разработчикам аппаратного обеспечения допускается любое значение ошибки / эпсилон, если оно меньше половины одного устройства на последнем месте, и результат должен быть меньше половины одной единицы в последнем место для одной операции. Это объясняет, почему при повторных операциях ошибки складываются. Для двойной точности 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 бита фактора за раз (radix 4), будет равна 2+2= 4 бита (плюс несколько дополнительных битов).

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

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

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

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

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

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

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

6. Резюме

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

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

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

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

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

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


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

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

(Разница между этими двумя числами – это «самый маленький срез», который мы должны решить либо включить, что вводит восходящее смещение, либо исключить, что приводит к смещению вниз. Технический термин для этого наименьшего fragmentа – это 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.3000000000000000444089209850062616169452667236328125, тогда как число, самое близкое к 0,3, фактически составляет 0.29999999999999999988897769753748434595763683319091796875.


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

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

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

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

Например:

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

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


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

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

преамбула

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

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

в 64 бит:

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

1 – IEEE 754 допускает трактовку знака с нулевым +0+0 и -0 по-разному: 1 / (+0) – положительная бесконечность; 1 / (-0) – отрицательная бесконечность. Для нулевых значений биты мантиссы и экспоненты равны нулю. Примечание: нулевые значения (+0 и -0) явно не classифицируются как денормальные 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 ).


Преrotation числа двойной точности в двоичный

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

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

 0.1 = 2^-4 * [1].1001100110011001100110011001100110011001100110011010 0.2 = 2^-3 * [1].1001100110011001100110011001100110011001100110011010 

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

 0.1 = 2^-3 * 0.1100110011001100110011001100110011001100110011001101(0) 0.2 = 2^-3 * 1.1001100110011001100110011001100110011001100110011010 sum = 2^-3 * 10.0110011001100110011001100110011001100110011001100111 

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

 sum = 2^-2 * 1.0011001100110011001100110011001100110011001100110011(1) 

В мантиссе сейчас 53 бит (53-е место в квадратных скобках в строке выше). Режим округления по умолчанию для IEEE 754 равен « Round to Nearest », т.е. если число x падает между двумя значениями a и b , выбирается значение, в котором наименьший значащий бит равен нулю.

 a = 2^-2 * 1.0011001100110011001100110011001100110011001100110011 x = 2^-2 * 1.0011001100110011001100110011001100110011001100110011(1) b = 2^-2 * 1.0011001100110011001100110011001100110011001100110100 

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

 sum = 2^-2 * 1.0011001100110011001100110011001100110011001100110100 

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 : последние несколько бит округлены.

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

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

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

Проблема связана с числами, которые могут быть представлены точно в базе 10, но не в базе 2. Эти цифры должны быть округлены до их ближайшего эквивалента. Assuming the very common IEEE 64-bit floating point format, the closest number to 0.1 is 3602879701896397 x 2⁻⁵⁵ , and the closest number to 0.2 is 7205759403792794 x 2⁻⁵⁵ ; adding them together results in 10808639105689191 x 2⁻⁵⁵ , or an exact decimal value of 0.3000000000000000444089209850062616169452667236328125 . Floating point numbers are generally rounded for display.

Floating point rounding error. From What Every Computer Scientist Should Know About Floating-Point Arithmetic :

Squeezing infinitely many real numbers into a finite number of bits requires an approximate representation. Although there are infinitely many integers, in most programs the result of integer computations can be stored in 32 bits. In contrast, given any fixed number of bits, most calculations with real numbers will produce quantities that cannot be exactly represented using that many bits. Therefore the result of a floating-point calculation must often be rounded in order to fit back into its finite representation. This rounding error is the characteristic feature of floating-point computation.

My workaround:

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

precision refers to the number of digits you want to preserve after the decimal point during addition.

A lot of good answers have been posted, but I’d like to append one more.

Not all numbers can be represented via floats / doubles For example, the number “0.2” will be represented as “0.200000003” in single precision in IEEE754 float point standard.

Model for store real numbers under the hood represent float numbers as

enter image description here

Even though you can type 0.2 easily, FLT_RADIX and DBL_RADIX is 2; not 10 for a computer with FPU which uses “IEEE Standard for Binary Floating-Point Arithmetic (ISO/IEEE Std 754-1985)”.

So it is a bit hard to represent such numbers exactly. Even if you specify this variable explicitly without any intermediate calculation.

No, not broken, but most decimal fractions must be approximated

Резюме

Floating point arithmetic is exact, unfortunately, it doesn’t match up well with our usual base-10 number representation, so it turns out we are often giving it input that is slightly off from what we wrote.

Even simple numbers like 0.01, 0.02, 0.03, 0.04 … 0.24 are not representable exactly as binary fractions, even if you had thousands of bits of precision in the mantissa, even if you had millions. If you count off in 0.01 increments, not until you get to 0.25 will you get the first fraction (in this sequence) representable in base 10 and base 2 . But if you tried that using FP, your 0.01 would have been slightly off, so the only way to add 25 of them up to a nice exact 0.25 would have required a long chain of causality involving guard bits and rounding. It’s hard to predict so we throw up our hands and say “FP is inexact”.

We constantly give the FP hardware something that seems simple in base 10 but is a repeating fraction in base 2.

How did this happen?

When we write in decimal, every fraction is a rational number of the form

x / (2 n + 5 n ).

In binary, we only get the 2 n term, that is:

x / 2 n

So in decimal, we can’t represent 1 / 3 . Because base 10 includes 2 as a prime factor, every number we can write as a binary fraction also can be written as a base 10 fraction. However, hardly anything we write as a base 10 fraction is representable in binary. In the range from 0.01, 0.02, 0.03 … 0.99, only three numbers can be represented in our FP format: 0.25, 0.50, and 0.75, because they are 1/4, 1/2, and 3/4, all numbers with a prime factor using only the 2 n term.

In base 10 we can’t represent 1 / 3 . But in binary, we can’t do 1 / 10 or 1 / 3 .

So while every binary fraction can be written in decimal, the reverse is not true. And in fact most decimal fractions repeat in binary.

Dealing with it

Developers are usually instructed to do < epsilon comparisons, better advice might be to round to integral values (in the C library: round() and roundf(), ie, stay in the FP format) and then compare. Rounding to a specific decimal fraction length solves most problems with output.

Also, on real number-crunching problems (the problems that FP was invented for on early, frightfully expensive computers) the physical constants of the universe and all other measurements are only known to a relatively small number of significant figures, so the entire problem space was “inexact” anyway. FP “accuracy” isn’t a problem in this kind of application.

The whole issue really arises when people try to use FP for bean counting. It does work for that, but only if you stick to integral values, which kind of defeats the point of using it. This is why we have all those decimal fraction software libraries.

I love the Pizza answer by Chris , because it describes the actual problem, not just the usual handwaving about “inaccuracy”. If FP were simply “inaccurate”, we could fix that and would have done it decades ago. The reason we haven’t is because the FP format is compact and fast and it’s the best way to crunch a lot of numbers. Also, it’s a legacy from the space age and arms race and early attempts to solve big problems with very slow computers using small memory systems. (Sometimes, individual magnetic cores for 1-bit storage, but that’s another story. )

Conclusion

If you are just counting beans at a bank, software solutions that use decimal string representations in the first place work perfectly well. But you can’t do quantum chromodynamics or aerodynamics that way.

Some statistics related to this famous double precision question.

When adding all values ( a + b ) using a step of 0.1 (from 0.1 to 100) we have ~15% chance of precision error . Note that the error could result in slightly bigger or smaller values. Here are some examples:

 0.1 + 0.2 = 0.30000000000000004 (BIGGER) 0.1 + 0.7 = 0.7999999999999999 (SMALLER) ... 1.7 + 1.9 = 3.5999999999999996 (SMALLER) 1.7 + 2.2 = 3.9000000000000004 (BIGGER) ... 3.2 + 3.6 = 6.800000000000001 (BIGGER) 3.2 + 4.4 = 7.6000000000000005 (BIGGER) 

When subtracting all values ( a – b where a > b ) using a step of 0.1 (from 100 to 0.1) we have ~34% chance of precision error . Here are some examples:

 0.6 - 0.2 = 0.39999999999999997 (SMALLER) 0.5 - 0.4 = 0.09999999999999998 (SMALLER) ... 2.1 - 0.2 = 1.9000000000000001 (BIGGER) 2.0 - 1.9 = 0.10000000000000009 (BIGGER) ... 100 - 99.9 = 0.09999999999999432 (SMALLER) 100 - 99.8 = 0.20000000000000284 (BIGGER) 

*15% and 34% are indeed huge, so always use BigDecimal when precision is of big importance. With 2 decimal digits (step 0.01) the situation worsens a bit more (18% and 36%).

Did you try the duct tape solution?

Try to determine when errors occur and fix them with short if statements, it’s not pretty but for some problems it is the only solution and this is one of them.

  if( (n * 0.1) < 100.0 ) { return n * 0.1 - 0.000000000000001 ;} else { return n * 0.1 + 0.000000000000001 ;} 

I had the same problem in a scientific simulation project in c#, and I can tell you that if you ignore the butterfly effect it's gonna turn to a big fat dragon and bite you in the a**

Those weird numbers appear because computers use binary(base 2) number system for calculation purposes, while we use decimal(base 10).

There are a majority of fractional numbers that cannot be represented precisely either in binary or in decimal or both. Result – A rounded up (but precise) number results.

Can I just add; people always assume this to be a computer problem, but if you count with your hands (base 10), you can’t get (1/3+1/3=2/3)=true unless you have infinity to add 0.333… to 0.333… so just as with the (1/10+2/10)!==3/10 problem in base 2, you truncate it to 0.333 + 0.333 = 0.666 and probably round it to 0.667 which would be also be technically inaccurate.

Count in ternary, and thirds are not a problem though – maybe some race with 15 fingers on each hand would ask why your decimal math was broken…

Given that nobody has mentioned this…

Some high level languages such as Python and Java come with tools to overcome binary floating point limitations. Например:

  • Python’s decimal module and Java’s BigDecimal class , that represent numbers internally with decimal notation (as opposed to binary notation). Both have limited precision, so they are still error prone, however they solve most common problems with binary floating point arithmetic.

    Decimals are very nice when dealing with money: ten cents plus twenty cents are always exactly thirty cents:

     >>> 0.1 + 0.2 == 0.3 False >>> Decimal('0.1') + Decimal('0.2') == Decimal('0.3') True 

    Python’s decimal module is based on IEEE standard 854-1987 .

  • Python’s fractions module and Apache Common’s BigFraction class . Both represent rational numbers as (numerator, denominator) pairs and they may give more accurate results than decimal floating point arithmetic.

Neither of these solutions is perfect (especially if we look at performances, or if we require a very high precision), but still they solve a great number of problems with binary floating point arithmetic.

Many of this question’s numerous duplicates ask about the effects of floating point rounding on specific numbers. In practice, it is easier to get a feeling for how it works by looking at exact results of calculations of interest rather than by just reading about it. Some languages provide ways of doing that – such as converting a float or double to BigDecimal in Java.

Since this is a language-agnostic question, it needs language-agnostic tools, such as a Decimal to Floating-Point Converter .

Applying it to the numbers in the question, treated as doubles:

0.1 converts to 0.1000000000000000055511151231257827021181583404541015625,

0.2 converts to 0.200000000000000011102230246251565404236316680908203125,

0.3 converts to 0.299999999999999988897769753748434595763683319091796875, and

0.30000000000000004 converts to 0.3000000000000000444089209850062616169452667236328125.

Adding the first two numbers manually or in a decimal calculator such as Full Precision Calculator , shows the exact sum of the actual inputs is 0.3000000000000000166533453693773481063544750213623046875.

If it were rounded down to the equivalent of 0.3 the rounding error would be 0.0000000000000000277555756156289135105907917022705078125. Rounding up to the equivalent of 0.30000000000000004 also gives rounding error 0.0000000000000000277555756156289135105907917022705078125. The round-to-even tie breaker applies.

Returning to the floating point converter, the raw hexadecimal for 0.30000000000000004 is 3fd3333333333334, which ends in an even digit and therefore is the correct result.

The kind of floating-point math that can be implemented in a digital computer necessarily uses an approximation of the real numbers and operations on them. (The standard version runs to over fifty pages of documentation and has a committee to deal with its errata and further refinement.)

This approximation is a mixture of approximations of different kinds, each of which can either be ignored or carefully accounted for due to its specific manner of deviation from exactitude. It also involves a number of explicit exceptional cases at both the hardware and software levels that most people walk right past while pretending not to notice.

If you need infinite precision (using the number π, for example, instead of one of its many shorter stand-ins), you should write or use a symbolic math program instead.

But if you’re okay with the idea that sometimes floating-point math is fuzzy in value and logic and errors can accumulate quickly, and you can write your requirements and tests to allow for that, then your code can frequently get by with what’s in your FPU.

Just for fun, I played with the representation of floats, following the definitions from the Standard C99 and I wrote the code below.

The code prints the binary representation of floats in 3 separated groups

 SIGN EXPONENT FRACTION 

and after that it prints a sum, that, when summed with enough precision, it will show the value that really exists in hardware.

So when you write float x = 999... , the compiler will transform that number in a bit representation printed by the function xx such that the sum printed by the function yy be equal to the given number.

In reality, this sum is only an approximation. For the number 999,999,999 the compiler will insert in bit representation of the float the number 1,000,000,000

After the code I attach a console session, in which I compute the sum of terms for both constants (minus PI and 999999999) that really exists in hardware, inserted there by the compiler.

 #include  #include  void xx(float *x) { unsigned char i = sizeof(*x)*CHAR_BIT-1; do { switch (i) { case 31: printf("sign:"); break; case 30: printf("exponent:"); break; case 23: printf("fraction:"); break; } char b=(*(unsigned long long*)x&((unsigned long long)1<>23))-127; printf(sign?"positive" " ( 1+":"negative" " ( 1+"); unsigned int i = 1<<22; unsigned int j = 1; do { char b=(fraction&i)!=0; b&&(printf("1/(%d) %c", 1<>=1); printf("*2^%d", exponent); printf("\n"); } void main() { float x=-3.14; float y=999999999; printf("%lu\n", sizeof(x)); xx(&x); xx(&y); yy(x); yy(y); } 

Here is a console session in which I compute the real value of the float that exists in hardware. I used bc to print the sum of terms outputted by the main program. One can insert that sum in python repl or something similar also.

 -- .../terra1/stub @ qemacs fc -- .../terra1/stub @ gcc fc -- .../terra1/stub @ ./a.out sign:1 exponent:1 0 0 0 0 0 0 fraction:0 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 1 0 0 0 0 1 1 sign:0 exponent:1 0 0 1 1 1 0 fraction:0 1 1 0 1 1 1 0 0 1 1 0 1 0 1 1 0 0 1 0 1 0 0 0 negative ( 1+1/(2) +1/(16) +1/(256) +1/(512) +1/(1024) +1/(2048) +1/(8192) +1/(32768) +1/(65536) +1/(131072) +1/(4194304) +1/(8388608) )*2^1 positive ( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29 -- .../terra1/stub @ bc scale=15 ( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29 999999999.999999446351872 

Вот и все. The value of 999999999 is in fact

 999999999.999999446351872 

You can also check with bc that -3.14 is also perturbed. Do not forget to set a scale factor in bc .

The displayed sum is what inside the hardware. The value you obtain by computing it depends on the scale you set. I did set the scale factor to 15. Mathematically, with infinite precision, it seems it is 1,000,000,000.

Another way to look at this: Used are 64 bits to represent numbers. As consequence there is no way more than 2**64 = 18,446,744,073,709,551,616 different numbers can be precisely represented.

However, Math says there are already infinitely many decimals between 0 and 1. IEE 754 defines an encoding to use these 64 bits efficiently for a much larger number space plus NaN and +/- Infinity, so there are gaps between accurately represented numbers filled with numbers only approximated.

Unfortunately 0.3 sits in a gap.

Since this thread branched off a bit into a general discussion over current floating point implementations I’d add that there are projects on fixing their issues.

Take a look at https://posithub.org/ for example, which showcases a number type called posit (and its predecessor unum) that promises to offer better accuracy with fewer bits. If my understanding is correct, it also fixes the kind of problems in the question. Quite interesting project, the person behind it is a mathematician it Dr. John Gustafson . The whole thing is open source, with many actual implementations in C/C++, Python, Julia and C# ( https://hastlayer.com/arithmetics ).

In order to offer The best solution I can say I discovered following method:

 parseFloat((0.1 + 0.2).toFixed(10)) => Will return 0.3 

Let me explain why it’s the best solution. As others mentioned in above answers it’s a good idea to use ready to use Javascript toFixed() function to solve the problem. But most likely you’ll encounter with some problems.

Imagine you are going to add up two float numbers like 0.2 and 0.7 here it is: 0.2 + 0.7 = 0.8999999999999999 .

Your expected result was 0.9 it means you need a result with 1 digit precision in this case. So you should have used (0.2 + 0.7).tofixed(1) but you can’t just give a certain parameter to toFixed() since it depends on the given number, for instance

 `0.22 + 0.7 = 0.9199999999999999` 

In this example you need 2 digits precision so it should be toFixed(2) , so what should be the paramter to fit every given float number?

You might say let it be 10 in every situation then:

 (0.2 + 0.7).toFixed(10) => Result will be "0.9000000000" 

Damn! What are you going to do with those unwanted zeros after 9? It’s the time to convert it to float to make it as you desire:

 parseFloat((0.2 + 0.7).toFixed(10)) => Result will be 0.9 

Now that you found the solution, it’s better to offer it as a function like this:

 function floatify(number){ return parseFloat((number).toFixed(10)); } function addUp(){ var number1 = +$("#number1").val(); var number2 = +$("#number2").val(); var unexpectedResult = number1 + number2; var expectedResult = floatify(number1 + number2); $("#unexpectedResult").text(unexpectedResult); $("#expectedResult").text(expectedResult); } addUp(); 
 input{ width: 50px; } #expectedResult{ color: green; } #unexpectedResult{ color: red; } 
   +  = 

Expected Result:

Unexpected Result:

A different question has been named as a duplicate to this one:

In C++, why is the result of cout << x different from the value that a debugger is showing for x ?

The x in the question is a float variable.

One example would be

 float x = 9.9F; 

The debugger shows 9.89999962 , the output of cout operation is 9.9 .

The answer turns out to be that cout 's default precision for float is 6, so it rounds to 6 decimal digits.

See here for reference

Math.sum ( javascript ) …. kind of operator replacement

 .1 + .0001 + -.1 --> 0.00010000000000000286 Math.sum(.1 , .0001, -.1) --> 0.0001 

 Object.defineProperties(Math, { sign: { value: function (x) { return x ? x < 0 ? -1 : 1 : 0; } }, precision: { value: function (value, precision, type) { var v = parseFloat(value), p = Math.max(precision, 0) || 0, t = type || 'round'; return (Math[t](v * Math.pow(10, p)) / Math.pow(10, p)).toFixed(p); } }, scientific_to_num: { // this is from https://gist.github.com/jiggzson value: function (num) { //if the number is in scientific notation remove it if (/e/i.test(num)) { var zero = '0', parts = String(num).toLowerCase().split('e'), //split into coeff and exponent e = parts.pop(), //store the exponential part l = Math.abs(e), //get the number of zeros sign = e / l, coeff_array = parts[0].split('.'); if (sign === -1) { num = zero + '.' + new Array(l).join(zero) + coeff_array.join(''); } else { var dec = coeff_array[1]; if (dec) l = l - dec.length; num = coeff_array.join('') + new Array(l + 1).join(zero); } } return num; } } get_precision: { value: function (number) { var arr = Math.scientific_to_num((number + "")).split("."); return arr[1] ? arr[1].length : 0; } }, diff:{ value: function(A,B){ var prec = this.max(this.get_precision(A),this.get_precision(B)); return +this.precision(AB,prec); } }, sum: { value: function () { var prec = 0, sum = 0; for (var i = 0; i < arguments.length; i++) { prec = this.max(prec, this.get_precision(arguments[i])); sum += +arguments[i]; // force float to convert strings to number } return Math.precision(sum, prec); } } }); 

the idea is to use Math instead operators to avoid float errors

 Math.diff(0.2, 0.11) == 0.09 // true 0.2 - 0.11 == 0.09 // false 

also note that Math.diff and Math.sum auto-detect the precision to use

Math.sum accepts any number of arguments

Sine Python 3.5 you can use math.isclose() function in if conditions

 import math if math.isclose(0.1 + 0.2, 0.3, abs_tol=0.01): pass 
  • Существует ли алгоритм сортировки по целому числу O (n)?
  • Что такое lambda?
  • Что такое оптимизация хвостового звонка?
  • Алгоритм генерации анаграмм
  • Алгоритм естественной сортировки
  • Что такое lambda (функция)?
  • Как определить тип кредитной карты на основе номера?
  • Вычислить наибольший прямоугольник во вращающемся прямоугольнике
  • Как вы находите точку на заданном перпендикулярном расстоянии от линии?
  • Уравнение для тестирования, если точка находится внутри круга
  • Что такое композиция, относящаяся к объектно-ориентированному дизайну?
  • Давайте будем гением компьютера.