Рассмотрим следующий код:
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
Почему эти неточности случаются?
math
language-agnostic
floating-point
floating-accuracy
Катон Джонстон
источник
источник
Ответы:
Двоичная математика с плавающей запятой такая. В большинстве языков программирования он основан на стандарте IEEE 754 . Суть проблемы заключается в том, что числа представлены в этом формате как целое число, умноженное на два; рациональные числа (например
0.1
, то есть1/10
), знаменатель которых не является степенью двойки, не могут быть точно представлены.Ибо
0.1
в стандартномbinary64
формате представление может быть записано точно так же, как0.1000000000000000055511151231257827021181583404541015625
в десятичном или0x1.999999999999ap-4
в C99 hexfloat нотации .Напротив, рациональное число
0.1
, то есть1/10
, может быть записано точно так же, как0.1
в десятичном или0x1.99999999999999...p-4
в аналоге шестигранной нотации С99, где...
представляет бесконечную последовательность из 9-х.Константы
0.2
и0.3
в вашей программе также будут приблизительными к их истинным значениям. Бывает, что ближайшийdouble
к0.2
больше, чем рациональное число,0.2
но самый близкийdouble
к0.3
меньше, чем рациональное число0.3
. Сумма0.1
и0.2
оказывается больше рационального числа0.3
и, следовательно, не согласна с константой в вашем коде.Достаточно всеобъемлющим подходом к решению арифметических операций с плавающей точкой является то, что должен знать каждый компьютерный специалист об арифметике с плавающей точкой . Для более легкого для понимания объяснения, см . Плавающую точку- gui.de .
Примечание: все позиционные (базовые N) системы счисления точно решают эту проблему
Обычные старые десятичные числа (основание 10) имеют те же проблемы, поэтому такие числа, как 1/3, заканчиваются как 0.333333333 ...
Вы только что наткнулись на число (3/10), которое легко представить с помощью десятичной системы, но не соответствует двоичной системе. Он также идет в обе стороны (в некоторой степени): 1/16 - это уродливое десятичное число (0,0625), но в двоичном виде оно выглядит так же аккуратно, как 10 000-е в десятичном (0,0001) ** - если бы мы были в Привычка использовать систему счисления с базой 2 в нашей повседневной жизни - вы даже посмотрите на это число и инстинктивно поймете, что можете прийти туда, вдвое уменьшив что-то вдвое, снова и снова и снова.
** Конечно, это не совсем то, как числа с плавающей точкой хранятся в памяти (они используют форму научной записи). Тем не менее, это иллюстрирует тот факт, что двоичные ошибки точности с плавающей точкой имеют тенденцию возникать, потому что числа «реального мира», с которыми мы обычно заинтересованы работать, часто имеют степень десяти - но только потому, что мы используем десятичную систему счисления день - Cегодня. По этой же причине мы будем говорить такие вещи, как 71% вместо «5 из каждых 7» (71% - это приблизительное значение, поскольку 5/7 нельзя точно представить ни одним десятичным числом).
Так что нет: двоичные числа с плавающей запятой не ломаются, они просто так же несовершенны, как и любая другая система счисления с базовым N :)
Примечание стороны: работа с плавающими в программировании
На практике эта проблема точности означает, что вам нужно использовать функции округления для округления чисел с плавающей запятой до скольких интересующих вас десятичных разрядов, прежде чем вы их отобразите.
Вам также необходимо заменить тесты на равенство сравнениями, которые допускают некоторую толерантность, что означает:
Как не делать
if (x == y) { ... }
Вместо этого делай
if (abs(x - y) < myToleranceValue) { ... }
.где
abs
абсолютное значениеmyToleranceValue
должен быть выбран для вашего конкретного приложения - и он будет во многом зависеть от того, сколько «места для маневра» вы готовы предоставить, и какое наибольшее число вы собираетесь сравнивать (из-за потери точности) ). Остерегайтесь констант стиля «эпсилон» на своем языке. Они не должны использоваться в качестве значений допуска.источник
Перспектива разработчика оборудования
Я считаю, что я должен добавить точку зрения разработчика оборудования, так как я проектирую и собираю оборудование с плавающей запятой. Знание причины ошибки может помочь в понимании того, что происходит в программном обеспечении, и, в конечном счете, я надеюсь, что это поможет объяснить причины возникновения ошибок с плавающей запятой и, по-видимому, накапливаться с течением времени.
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 Ошибка округления деления: аппроксимация взаимности
То, какие обратные величины находятся в таблице коэффициентов выбора, зависит от метода деления : медленное деление, такое как деление СТО, или быстрое деление, такое как деление Гольдшмидта; каждая запись модифицируется в соответствии с алгоритмом деления в попытке получить минимально возможную ошибку. В любом случае, однако, все обратные величины являются приблизительнымифактического взаимного и ввести некоторый элемент ошибки. И методы с медленным, и с быстрым делением вычисляют частное итеративно, т. Е. Некоторое количество битов частного вычисляется на каждом шаге, затем результат вычитается из делимого, и делитель повторяет шаги, пока ошибка не станет меньше половины одного блок на последнем месте. Методы медленного деления вычисляют фиксированное количество цифр отношения на каждом шаге и, как правило, дешевле в построении, а методы быстрого деления вычисляют переменное количество цифр на шаг и, как правило, стоят дороже. Самая важная часть методов деления состоит в том, что большинство из них полагаются на повторное умножение на приближение обратной величины, поэтому они подвержены ошибкам.
4. Ошибки округления в других операциях: усечение
Другой причиной ошибок округления во всех операциях являются различные режимы усечения окончательного ответа, которые допускает IEEE-754. Есть усечение, округление к нулю, округление до ближайшего (по умолчанию), округление вниз и округление вверх. Все методы вводят элемент ошибки менее чем на одну единицу в последнем месте для одной операции. Со временем и повторяющимися операциями усечение также добавляет к результирующей ошибке. Эта ошибка усечения особенно проблематична в возведении в степень, которое включает в себя некоторую форму повторного умножения.
5. Повторные операции
Поскольку аппаратное обеспечение, которое выполняет вычисления с плавающей запятой, должно давать только результат с ошибкой менее половины одного блока в последнем месте для одной операции, ошибка будет расти по сравнению с повторяющимися операциями, если их не наблюдать. Это причина того, что в вычислениях, которые требуют ограниченной ошибки, математики используют такие методы, как использование четного округленного до ближайшего числа в последнем месте IEEE-754, потому что со временем ошибки с большей вероятностью отменяют друг друга out и Interval Arithmetic в сочетании с вариациями режимов округления IEEE 754прогнозировать ошибки округления и исправлять их. Из-за его низкой относительной погрешности по сравнению с другими режимами округления, округление до ближайшей четной цифры (на последнем месте) является режимом округления по умолчанию IEEE-754.
Обратите внимание, что режим округления по умолчанию, округление до ближайшей четной цифры в последнем месте , гарантирует ошибку менее одной половины одного блока в последнем месте для одной операции. Использование только усечения, округления и округления может привести к ошибке, которая больше, чем половина одного блока в последнем месте, но меньше, чем один блок в последнем месте, поэтому эти режимы не рекомендуются, если они не используется в интервальной арифметике.
6. Резюме
Короче говоря, основной причиной ошибок в операциях с плавающей запятой является сочетание усечения в аппаратных средствах и усечение обратной величины в случае деления. Поскольку стандарт IEEE-754 требует только ошибки менее половины одного блока в последнем месте для одной операции, ошибки с плавающей запятой при повторных операциях будут складываться, если не будут исправлены.
источник
Когда вы конвертируете .1 или 1/10 в основание 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 будет благоприятствовать последнему.
(Разница между этими двумя числами заключается в «наименьшем срезе», который мы должны решить либо включить, который вводит смещение вверх, либо исключить, который вводит смещение вниз. Техническим термином для этого наименьшего среза является 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,299999999999999988897769753748434595763683319091796875.
PS Некоторые языки программирования также предоставляют нарезчики пиццы, которые могут разбивать ломтики на ровные десятые доли . Хотя такие ножницы для пиццы являются редкостью, если у вас есть доступ к одному, вам следует использовать его, когда важно иметь возможность получить ровно одну десятую или одну пятую части.
(Первоначально опубликовано на Quora.)
источник
Ошибки округления с плавающей точкой. 0,1 нельзя представить так же точно в базе-2, как в базе-10, из-за отсутствующего простого множителя 5. Точно так же, как 1/3 занимает бесконечное число цифр для представления в десятичной форме, но равно «0,1» в базе-3, 0.1 занимает бесконечное количество цифр в base-2, а не в base-10. И у компьютеров нет бесконечного количества памяти.
источник
В дополнение к другим правильным ответам вы можете рассмотреть возможность масштабирования ваших значений, чтобы избежать проблем с арифметикой с плавающей точкой.
Например:
... вместо:
Выражение
0.1 + 0.2 === 0.3
возвращаетсяfalse
в JavaScript, но, к счастью, целочисленная арифметика с плавающей точкой является точной, поэтому ошибок масштабирования можно избежать с помощью масштабирования.В качестве практического примера, чтобы избежать проблем с плавающей запятой, где точность имеет первостепенное значение, рекомендуется 1 обрабатывать деньги как целое число, представляющее количество центов:
2550
центов вместо25.50
долларов.1 Дуглас Крокфорд: JavaScript: Хорошие части : Приложение A - Ужасные части (стр. 105) .
источник
Мой ответ довольно длинный, поэтому я разделил его на три части. Поскольку вопрос касается математики с плавающей точкой, я сделал упор на том, что на самом деле делает машина. Я также определил двойную (64-битную) точность, но аргумент одинаково применим к любой арифметике с плавающей запятой.
преамбула
IEEE 754 с двойной точностью в двоичном формате с плавающей точкой (binary64) число представляет собой число вида
в 64 битах:
1
если число отрицательное,0
иначе 1 .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
s) составляет 2 -1023 + 1 - 2 -1023 - 51 (≈ 2,225 * 10 -308 ).Превращение числа с двойной точностью в двоичное
Существует много онлайн-конвертеров для преобразования числа с плавающей запятой двойной точности в двоичное (например, на сайте binaryconvert.com ), но здесь приведен пример кода C # для получения представления IEEE 754 для числа двойной точности (я разделяю эти три части двоеточиями (
:
) :Приступая к делу: оригинальный вопрос
(Перейти к нижней части для версии TL; DR)
Катон Джонстон (задающий вопрос) спросил, почему 0,1 + 0,2! = 0,3.
Написанные в двоичном виде (с двоеточиями, разделяющими три части), представления значений IEEE 754:
Обратите внимание, что мантисса состоит из повторяющихся цифр
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:Чтобы добавить два числа, показатель должен быть одинаковым, то есть:
Поскольку сумма не имеет вид 2 n * 1. {bbb}, мы увеличиваем показатель степени на единицу и сдвигаем десятичную ( двоичную ) точку, чтобы получить:
Теперь в мантиссе 53 бита (53-я в квадратных скобках в строке выше). Режим округления по умолчанию для IEEE 754 - « Округление до ближайшего », т. Е. Если число x попадает между двумя значениями a и b , выбирается значение, где младший значащий бит равен нулю.
Обратите внимание, что a и b отличаются только последним битом;
...0011
+1
=...0100
. В этом случае значение с наименьшим значащим нулевым битом равно b , поэтому сумма равна:тогда как двоичное представление 0,3:
который отличается только от двоичного представления суммы 0,1 и 0,2 на 2 -54 .
Двоичное представление 0,1 и 0,2 является наиболее точным представлением чисел, допустимым IEEE 754. Добавление этого представления из-за режима округления по умолчанию приводит к значению, которое отличается только младшим значащим битом.
TL; DR
Запись
0.1 + 0.2
в двоичном представлении IEEE 754 (с двоеточиями, разделяющими три части) и сравнение с ним0.3
, это (я поместил отдельные биты в квадратные скобки):Преобразованные обратно в десятичные, эти значения:
Разница составляет ровно 2 -54 , что составляет ~ 5.5511151231258 × 10 -17 - незначительно (для многих приложений) по сравнению с исходными значениями.
Сравнение последних нескольких битов числа с плавающей запятой по своей природе опасно, и каждый, кто прочитает знаменитую статью « Что должен знать каждый компьютерный специалист об арифметике с плавающей запятой » (которая охватывает все основные части этого ответа).
Большинство калькуляторов используют дополнительные защитные цифры, чтобы обойти эту проблему, как
0.1 + 0.2
бы это было0.3
: последние несколько бит округляются.источник
Числа с плавающей запятой, хранящиеся в компьютере, состоят из двух частей: целого числа и показателя степени, из которого берется основание и умножается на целочисленную часть.
Если бы компьютер работал в базе 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
. Числа с плавающей точкой обычно округляются для отображения.источник
Ошибка округления с плавающей точкой. Из того, что каждый компьютерщик должен знать об арифметике с плавающей точкой :
источник
Мой обходной путь:
Точность относится к числу цифр, которые вы хотите сохранить после десятичной точки во время сложения.
источник
Было опубликовано много хороших ответов, но я хотел бы добавить еще один.
Не все числа могут быть представлены с помощью поплавков / двойников Например, число «0,2» будет представлена как «0,200000003» в одинарной точности в стандарте IEEE754 флоат точки.
Модель для хранения реальных чисел под капотом представляет числа с плавающей точкой в виде
Даже если вы можете
0.2
легко печатать ,FLT_RADIX
иDBL_RADIX
2; не 10 для компьютера с FPU, который использует «Стандарт IEEE для двоичной арифметики с плавающей запятой (ISO / IEEE Std 754-1985)».Поэтому немного сложно точно представить такие числа. Даже если вы укажете эту переменную явно без каких-либо промежуточных вычислений.
источник
Немного статистики, связанной с этим знаменитым вопросом двойной точности.
При сложении всех значений ( a + b ) с шагом 0,1 (от 0,1 до 100) вероятность ошибки точности составляет ~ 15% . Обратите внимание, что ошибка может привести к чуть большим или меньшим значениям. Вот некоторые примеры:
При вычитании всех значений ( a - b, где a> b ) с шагом 0,1 (от 100 до 0,1) мы имеем ~ 34% вероятности погрешности точности . Вот некоторые примеры:
* 15% и 34% действительно огромны, поэтому всегда используйте BigDecimal, когда точность имеет большое значение. С двумя десятичными цифрами (шаг 0.01) ситуация ухудшается немного больше (18% и 36%).
источник
Нет, не разбито, но большинство десятичных дробей должно быть аппроксимировано
Арифметика с плавающей точкой является точным, к сожалению, это не соответствует хорошо с нашим обычным базой-10 представлением чисел, так получается , что мы часто придав ему вход , который немного не от того, что мы написали.
Даже простые числа, такие как 0,01, 0,02, 0,03, 0,04 ... 0,24, не могут быть представлены в виде двоичных дробей. Если вы подсчитаете 0,01, 0,02, 0,03 ..., то только до 0,25 вы получите первую дробь, представимую в базе 2 . Если вы попробуете это с использованием FP, ваш 0.01 был бы немного не таким, поэтому единственный способ добавить 25 из них до точного 0.25 потребовал бы длинной цепочки причинно-следственных связей, включающей защитные биты и округление. Трудно предсказать, поэтому мы вскидываем руки и говорим: «FP - это неточно», но это не совсем так.
Мы постоянно даем оборудованию FP что-то, что кажется простым в базе 10, но является повторяющейся дробью в базе 2.
Когда мы пишем в десятичном виде, каждая дробь (в частности, каждый завершающий десятичный) является рациональным числом вида
а / (2 н х 5 м )
В двоичном коде мы получаем только 2 n член, то есть:
а / 2 н
Таким образом , в десятичной системе , мы не можем представить 1 / 3 . Поскольку основание 10 включает в себя 2 в качестве простого множителя, каждое число, которое мы можем записать в виде двоичной дроби, также может быть записано в виде дробной базы 10. Однако вряд ли все, что мы пишем как дробь с основанием 10, представимо в двоичном виде. В диапазоне от 0,01, 0,02, 0,03 до 0,99 в нашем формате FP могут быть представлены только три числа: 0,25, 0,50 и 0,75, поскольку они равны 1/4, 1/2 и 3/4, все числа с простым множителем, использующим только 2 n член.
В базе 10 мы не можем представить 1 / 3 . Но в двоичном коде, мы не можем сделать +1 / +10 или +1 / +3 .
Таким образом, хотя каждая двоичная дробь может быть записана в десятичном виде, обратное неверно. И фактически большинство десятичных дробей повторяется в двоичном формате.
Разработчикам, как правило, дают указание делать < сравнения epsilon , лучше посоветовать округлить до целочисленных значений (в библиотеке C: round () и roundf (), т. Е. Остаться в формате FP), а затем сравнить. Округление до определенной длины десятичной дроби решает большинство проблем с выводом.
Кроме того, в реальных проблемах с сокращением чисел (проблемы, которые были изобретены FP на ранних, ужасно дорогих компьютерах) физические константы вселенной и все другие измерения известны лишь относительно небольшому числу значащих цифр, поэтому все проблемное пространство был "неточным" в любом случае. Точность FP не является проблемой в такого рода приложениях.
Вся проблема действительно возникает, когда люди пытаются использовать FP для подсчета бобов. Это работает для этого, но только если вы придерживаетесь целочисленных значений, что побеждает смысл его использования. Вот почему у нас есть все эти библиотеки программного обеспечения с десятичной дробью.
Мне нравится ответ Криса на «Пиццу» , потому что он описывает реальную проблему, а не просто обычные пометки о «неточности». Если бы FP были просто «неточными», мы могли бы это исправить и сделали бы это десятилетия назад. Причина, по которой мы этого не делаем, заключается в том, что формат FP компактен и быстр, и это лучший способ сократить множество чисел. Кроме того, это наследие космической эры и гонки вооружений и ранних попыток решить большие проблемы с очень медленными компьютерами с использованием небольших систем памяти. (Иногда отдельные магнитные сердечники для 1-битного хранилища, но это уже другая история. )
Если вы просто подсчитываете бины в банке, программные решения, которые в первую очередь используют десятичные строковые представления, работают превосходно. Но вы не можете делать квантовую хромодинамику или аэродинамику таким образом.
источник
nextafter()
с целочисленным приращением или уменьшением двоичное представление числа с плавающей запятой IEEE. Кроме того, вы можете сравнить числа с плавающей точкой как целые числа и получить правильный ответ, за исключением случаев, когда они оба отрицательны (из-за величины знака против дополнения 2).Вы пробовали решение для клейкой ленты?
Попробуйте определить, когда возникают ошибки, и исправить их с помощью коротких операторов if, это не красиво, но для некоторых проблем это единственное решение, и это одно из них.
У меня была такая же проблема в научном симуляционном проекте на c #, и я могу вам сказать, что если вы проигнорируете эффект бабочки, он превратится в большого толстого дракона и укусит вас в а **
источник
Чтобы предложить лучшее решение, я могу сказать, что обнаружил следующий метод:
Позвольте мне объяснить, почему это лучшее решение. Как уже упоминалось в ответах выше, для решения проблемы рекомендуется использовать готовую функцию toFixed () Javascript. Но, скорее всего, вы столкнетесь с некоторыми проблемами.
Представьте , что вы собираетесь сложить два числа с плавающей точкой , как
0.2
и0.7
здесь:0.2 + 0.7 = 0.8999999999999999
.Ваш ожидаемый результат
0.9
означает, что в этом случае вам нужен результат с точностью до 1 цифры. Таким образом, вы должны были использовать,(0.2 + 0.7).tofixed(1)
но вы не можете просто дать определенный параметр toFixed (), так как он зависит от заданного числа, напримерВ этом примере вам нужна точность в 2 цифры, так и должно быть
toFixed(2)
, так какой же параметр должен соответствовать каждому заданному числу с плавающей точкой?Вы можете сказать, пусть это будет 10 в каждой ситуации:
Черт! Что вы собираетесь делать с этими нежелательными нулями после 9? Пришло время преобразовать это в float, чтобы сделать это, как вы хотите:
Теперь, когда вы нашли решение, лучше предложить его в виде такой функции:
Давайте попробуем сами:
Вы можете использовать это так:
Как предполагает W3SCHOOLS, есть и другое решение, вы можете умножить и разделить, чтобы решить проблему выше:
Имейте в виду, что
(0.2 + 0.1) * 10 / 10
это не будет работать вообще, хотя кажется, что это то же самое! Я предпочитаю первое решение, так как я могу применить его как функцию, которая преобразует входной float в точный выходной float.источник
Эти странные числа появляются, потому что компьютеры используют двоичную (основание 2) систему счисления для целей расчета, а мы используем десятичную (основание 10).
Существует большинство дробных чисел, которые нельзя точно представить ни в двоичном, ни в десятичном виде, ни в обоих. Результат - округленное (но точное) число результатов.
источник
Многие из многочисленных дубликатов этого вопроса спрашивают о влиянии округления с плавающей запятой на конкретные числа. На практике легче понять, как это работает, глядя на точные результаты вычислений, а не просто читая об этом. Некоторые языки обеспечивают способы сделать это - такие как преобразование
float
илиdouble
вBigDecimal
в Java.Поскольку это вопрос, не зависящий от языка, ему нужны инструменты, не зависящие от языка, такие как преобразование десятичных чисел в числа с плавающей запятой .
Применяя его к числам в вопросе, рассматривается как двойное число:
0,1 преобразуется в 0,1000000000000000055511151231257827021181583404541015625,
0.2 преобразуется в 0.200000000000000011102230246251565404236316680908203125,
0,3 преобразуется в 0,299999999999999988897769753748434595763683319091796875 и
0.30000000000000004 преобразуется в 0.3000000000000000444089209850062616169452667236328125.
При добавлении первых двух чисел вручную или в десятичном калькуляторе, таком как Калькулятор полной точности , отображается точная сумма фактических входных значений: 0,3000000000000000166533453693773481063544750213623046875.
Если бы оно было округлено до эквивалента 0,3, ошибка округления была бы равна 0,0000000000000000277555756156289135105907917022705078125. Округление до эквивалента 0,30000000000000004 также дает ошибку округления 0,0000000000000000277555756156289135105907917022705078125. Применяется прерыватель круглой и четной связи.
Возвращаясь к преобразователю с плавающей запятой, необработанный шестнадцатеричный код для 0.30000000000000004 равен 3fd3333333333334, который заканчивается четной цифрой и, следовательно, является правильным результатом.
источник
Учитывая, что никто не упомянул об этом ...
Некоторые языки высокого уровня, такие как Python и Java, поставляются с инструментами для преодоления двоичных ограничений с плавающей запятой. Например:
decimal
Модуль Python иBigDecimal
класс Java , которые представляют числа внутренне в десятичной записи (в отличие от двоичной записи). Оба имеют ограниченную точность, поэтому они все еще подвержены ошибкам, однако они решают наиболее распространенные проблемы с двоичной арифметикой с плавающей запятой.Десятичные числа очень хороши при работе с деньгами: десять центов плюс двадцать центов всегда равны тридцати центам:
decimal
Модуль Python основан на стандарте IEEE 854-1987 .fractions
Модуль Python иBigFraction
класс Apache Common . Оба представляют рациональные числа в виде(numerator, denominator)
пар, и они могут дать более точные результаты, чем десятичная арифметика с плавающей запятой.Ни одно из этих решений не является идеальным (особенно если мы посмотрим на производительность или если нам требуется очень высокая точность), но тем не менее они решают большое количество проблем с двоичной арифметикой с плавающей запятой.
источник
Могу ли я просто добавить; люди всегда считают, что это компьютерная проблема, но если вы рассчитываете своими руками (база 10), вы не можете получить,
(1/3+1/3=2/3)=true
если у вас нет бесконечности, чтобы добавить 0,333 ... к 0,333 ... так же, как с(1/10+2/10)!==3/10
проблемой в базе 2, вы усекаете его до 0,333 + 0,333 = 0,666 и, вероятно, округляете его до 0,667, что также будет технически неточным.Считай в троице, и трети не проблема, хотя - может быть, какая-то гонка с 15 пальцами на каждой руке спросит, почему твоя десятичная математика была нарушена ...
источник
Тип математики с плавающей точкой, который может быть реализован в цифровом компьютере, обязательно использует аппроксимацию действительных чисел и операций с ними. ( Стандартная версия содержит более пятидесяти страниц документации и имеет комитет, который занимается ее ошибками и дальнейшей доработкой.)
Это приближение представляет собой смесь приближений разных видов, каждое из которых может либо игнорироваться, либо тщательно учитываться из-за своего специфического отклонения от точности. Это также включает в себя ряд явных исключительных случаев как на аппаратном, так и на программном уровне, которые большинство людей проходят мимо, делая вид, что не замечают.
Если вам нужна бесконечная точность (например, с использованием числа π вместо одного из множества его более коротких заменителей), вы должны вместо этого написать или использовать символическую математическую программу.
Но если вы согласны с идеей, что иногда математика с плавающей запятой нечеткая по значению, а логика и ошибки могут быстро накапливаться, и вы можете написать свои требования и тесты, чтобы учесть это, тогда ваш код часто может обойтись тем, что в ваш FPU.
источник
Просто для забавы, я играл с представлением поплавков, следуя определениям из Standard C99, и я написал код ниже.
Код печатает двоичное представление с плавающей точкой в 3 отдельных группах
и после этого он печатает сумму, которая при суммировании с достаточной точностью покажет значение, которое действительно существует в аппаратном обеспечении.
Поэтому, когда вы пишете
float x = 999...
, компилятор преобразует это число в битовое представление, напечатанное функциейxx
, так, чтобы сумма, напечатанная функцией,yy
была равна данному числу.На самом деле эта сумма только приблизительная. Для числа 999 999 999 компилятор вставит в битовое представление числа с плавающей точкой число 1 000 000 000
После кода я присоединяю консольный сеанс, в котором я вычисляю сумму терминов для обеих констант (за исключением PI и 999999999), которые действительно существуют в аппаратных средствах, вставленных туда компилятором.
Вот сеанс консоли, в котором я вычисляю реальное значение с плавающей точкой, которое существует в аппаратных средствах. Раньше я
bc
печатал сумму терминов, выводимых основной программой. Можно вставить эту сумму в Pythonrepl
или что-то подобное.Вот и все. Значение 999999999 на самом деле
Вы также можете проверить,
bc
что -3.14 также возмущен. Не забудьте установитьscale
фактор вbc
.Отображаемая сумма - это то, что находится внутри оборудования. Значение, которое вы получите путем вычисления, зависит от установленного вами масштаба. Я установил
scale
коэффициент на 15. Математически, с бесконечной точностью, кажется, это 1 000 000 000.источник
Другой способ взглянуть на это: используются 64 бита для представления чисел. Как следствие, не может быть более 2 ** 64 = 18 446 744 073 709 551 616 различных чисел.
Тем не менее, Мат говорит, что между 0 и 1 уже существует бесконечно много десятичных знаков. IEE 754 определяет кодировку для эффективного использования этих 64 битов для гораздо большего числового пространства плюс NaN и +/- Infinity, поэтому между точно представленными числами, заполненными числа только приблизительные.
К сожалению, 0,3 сидит в пробел.
источник
Представьте, что вы работаете в базовой десятке, скажем, с 8 цифрами точности. Вы проверяете,
и узнай, что это возвращается
false
. Почему? Ну, а реальные цифры у нас есть1/3 = 0,333 .... и 2/3 = 0,666 ....
Обрезая в восемь знаков после запятой, мы получаем
что, конечно, отличается от
1.00000000
точно0.00000001
.Ситуация для двоичных чисел с фиксированным числом битов в точности аналогична. Как реальные цифры, мы имеем
1/10 = 0,0001100110011001100 ... (база 2)
а также
1/5 = 0,0011001100110011001 ... (база 2)
Если бы мы урезали их, скажем, до семи бит, то мы бы получили
в то время как с другой стороны,
3/10 = 0,01001100110011 ... (база 2)
который, усеченный до семи бит, есть
0.0100110
, и они отличаются точно0.0000001
.Точная ситуация немного сложнее, потому что эти цифры обычно хранятся в научной записи. Так, например, вместо того, чтобы хранить 1/10, поскольку
0.0001100
мы можем хранить это как-то так1.10011 * 2^-4
, в зависимости от того, сколько битов мы выделили для экспоненты и мантиссы. Это влияет на то, сколько знаков точности вы получите для своих расчетов.В результате из-за этих ошибок округления вы по существу никогда не захотите использовать == для чисел с плавающей запятой. Вместо этого вы можете проверить, меньше ли абсолютное значение их разности, чем некоторое фиксированное небольшое число.
источник
Начиная с Python 3.5 вы можете использовать
math.isclose()
функцию для проверки приблизительного равенства:источник
Поскольку этот поток немного расширился до общего обсуждения текущих реализаций с плавающей запятой, я бы добавил, что существуют проекты по устранению их проблем.
Взгляните, например, на https://posithub.org/ , который демонстрирует тип числа, называемый posit (и его предшественник unum), который обещает предложить лучшую точность с меньшим количеством битов. Если мое понимание верно, это также устраняет проблемы в этом вопросе. Довольно интересный проект, за ним стоит математик доктор Джон Густафсон . Все это с открытым исходным кодом, со многими актуальными реализациями на C / C ++, Python, Julia и C # ( https://hastlayer.com/arithmetics ).
источник
С https://0.30000000000000004.com/
источник
Десятичные числа, такие как
0.1
,0.2
и0.3
не представлены точно в двоично-кодированных типах с плавающей запятой. Сумма аппроксимаций для0.1
и0.2
отличается от аппроксимации, использованной для0.3
, следовательно, ложь,0.1 + 0.2 == 0.3
как можно увидеть здесь более четко:Вывод:
Чтобы эти вычисления были оценены более надежно, вам необходимо использовать десятичное представление для значений с плавающей запятой. Стандарт C не определяет такие типы по умолчанию, но как расширение, описанное в техническом отчете .
В
_Decimal32
,_Decimal64
и_Decimal128
типы могут быть доступны в вашей системе (например, GCC поддерживает их на выбранные цели , но Clang не поддерживает их на OS X ).источник
Math.sum (javascript) .... вид замены оператора
идея состоит в том, чтобы использовать Math вместо операторов, чтобы избежать ошибок с плавающей точкой
Math.sum автоматически определяет точность использования
Math.sum принимает любое количество аргументов
источник
Я только что увидел эту интересную проблему с плавающей точкой:
Рассмотрим следующие результаты:
Мы можем четко видеть точку останова, когда
2**53+1
- все работает нормально, пока2**53
.Это происходит из-за двоичного двоичного формата IEEE 754 с двойной точностью двоичного формата: двоичная64
Со страницы Википедии для формата с плавающей запятой двойной точности :
Спасибо @a_guest за указание на это мне.
источник
Другой вопрос был назван как дубликат этого:
В C ++, почему результат
cout << x
отличается от значения, для которого отображается отладчикx
?В
x
вопросе этоfloat
переменная.Одним из примеров будет
Отладчик показывает
9.89999962
, выводcout
операции есть9.9
.Получается, что ответом является
cout
точность по умолчанию дляfloat
6, поэтому он округляется до 6 десятичных цифр.Смотрите здесь для справки
источник