Рассмотрим следующий код:
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
Почему эти неточности случаются?
Рассмотрим следующий код:
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
Почему эти неточности случаются?
Ответы:
Двоичная математика с плавающей запятой такая. В большинстве языков программирования он основан на стандарте 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
должен быть выбран для вашего конкретного приложения - и он будет во многом зависеть от того, сколько «места для маневра» вы готовы предоставить, и какое наибольшее число вы собираетесь сравнивать (из-за потери точности) ). Остерегайтесь констант стиля «эпсилон» на своем языке. Они не должны использоваться в качестве значений допуска.
Я считаю, что я должен добавить точку зрения разработчика оборудования, так как я проектирую и собираю оборудование с плавающей запятой. Знание причины ошибки может помочь в понимании того, что происходит в программном обеспечении, и, в конечном счете, я надеюсь, что это поможет объяснить причины возникновения ошибок с плавающей запятой и, по-видимому, накапливаться с течением времени.
С инженерной точки зрения, большинство операций с плавающей запятой будут иметь некоторый элемент ошибки, поскольку аппаратное обеспечение, выполняющее вычисления с плавающей запятой, должно иметь ошибку менее половины одного модуля в последнем месте. Следовательно, большое количество аппаратного обеспечения будет останавливаться с точностью, необходимой только для того, чтобы выдать ошибку менее половины одного блока в последнем месте для одной операции, что особенно проблематично при делении с плавающей запятой. То, что составляет одну операцию, зависит от того, сколько операндов принимает блок. Для большинства это два, но некоторые единицы принимают 3 или более операндов. Из-за этого нет гарантии, что повторные операции приведут к желаемой ошибке, так как ошибки накапливаются со временем.
Большинство процессоров соответствуют стандарту IEEE-754, но некоторые используют денормализованные или другие стандарты. Например, в IEEE-754 есть денормализованный режим, который позволяет представлять очень маленькие числа с плавающей запятой за счет точности. Следующее, однако, будет охватывать нормализованный режим IEEE-754, который является типичным режимом работы.
В стандарте IEEE-754 разработчикам аппаратного обеспечения разрешается любое значение error / epsilon, если в последнем месте оно составляет менее половины одного модуля, а результат должен составлять менее половины одного модуля в последнем месте. место для одной операции. Это объясняет, почему при повторных операциях ошибки складываются. Для двойной точности IEEE-754 это 54-й бит, поскольку 53 бита используются для представления числовой части (нормализованной), также называемой мантиссой, числа с плавающей запятой (например, 5.3 в 5.3e5). В следующих разделах более подробно рассматриваются причины аппаратной ошибки при различных операциях с плавающей запятой.
Основной причиной ошибки в делении с плавающей запятой являются алгоритмы деления, используемые для вычисления отношения. Большинство компьютерных систем расчета деление используя умножение на инверсию, в основном 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 Ошибка округления деления: аппроксимация взаимности
То, какие обратные величины находятся в таблице коэффициентов выбора, зависит от метода деления : медленное деление, такое как деление СТО, или быстрое деление, такое как деление Гольдшмидта; каждая запись модифицируется в соответствии с алгоритмом деления в попытке получить минимально возможную ошибку. В любом случае, однако, все обратные величины являются приблизительнымифактического взаимного и ввести некоторый элемент ошибки. И методы с медленным, и с быстрым делением вычисляют частное итеративно, т. Е. Некоторое количество битов частного вычисляется на каждом шаге, затем результат вычитается из делимого, и делитель повторяет шаги, пока ошибка не станет меньше половины одного блок на последнем месте. Методы медленного деления вычисляют фиксированное количество цифр отношения на каждом шаге и, как правило, дешевле в построении, а методы быстрого деления вычисляют переменное количество цифр на шаг и, как правило, стоят дороже. Самая важная часть методов деления состоит в том, что большинство из них полагаются на повторное умножение на приближение обратной величины, поэтому они подвержены ошибкам.
Другой причиной ошибок округления во всех операциях являются различные режимы усечения окончательного ответа, которые допускает IEEE-754. Есть усечение, округление к нулю, округление до ближайшего (по умолчанию), округление вниз и округление вверх. Все методы вводят элемент ошибки менее чем на одну единицу в последнем месте для одной операции. Со временем и повторяющимися операциями усечение также добавляет к результирующей ошибке. Эта ошибка усечения особенно проблематична в возведении в степень, которое включает в себя некоторую форму повторного умножения.
Поскольку аппаратное обеспечение, которое выполняет вычисления с плавающей запятой, должно давать только результат с ошибкой менее половины одного блока в последнем месте для одной операции, ошибка будет расти по сравнению с повторяющимися операциями, если их не наблюдать. Это причина того, что в вычислениях, которые требуют ограниченной ошибки, математики используют такие методы, как использование четного округленного до ближайшего числа в последнем месте IEEE-754, потому что со временем ошибки с большей вероятностью отменяют друг друга out и Interval Arithmetic в сочетании с вариациями режимов округления IEEE 754прогнозировать ошибки округления и исправлять их. Из-за его низкой относительной погрешности по сравнению с другими режимами округления, округление до ближайшей четной цифры (на последнем месте) является режимом округления по умолчанию IEEE-754.
Обратите внимание, что режим округления по умолчанию, округление до ближайшей четной цифры в последнем месте , гарантирует ошибку менее одной половины одного блока в последнем месте для одной операции. Использование только усечения, округления и округления может привести к ошибке, которая больше, чем половина одного блока в последнем месте, но меньше, чем один блок в последнем месте, поэтому эти режимы не рекомендуются, если они не используется в интервальной арифметике.
Короче говоря, основной причиной ошибок в операциях с плавающей запятой является сочетание усечения в аппаратных средствах и усечение обратной величины в случае деления. Поскольку стандарт 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 Некоторые языки программирования также предоставляют нарезчики пиццы, которые могут разбивать ломтики на ровные десятые доли . Хотя такие ножницы для пиццы являются редкостью, если у вас есть доступ к одному, вам следует использовать его, когда важно иметь возможность получить ровно одну десятую или одну пятую части.
Ошибки округления с плавающей точкой. 0,1 нельзя представить так же точно в базе-2, как в базе-10, из-за отсутствующего простого множителя 5. Точно так же, как 1/3 занимает бесконечное число цифр для представления в десятичной форме, но равно «0,1» в базе-3, 0.1 занимает бесконечное количество цифр в base-2, а не в base-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, но, к счастью, целочисленная арифметика с плавающей точкой является точной, поэтому ошибок масштабирования можно избежать с помощью масштабирования.
В качестве практического примера, чтобы избежать проблем с плавающей запятой, где точность имеет первостепенное значение, рекомендуется 1 обрабатывать деньги как целое число, представляющее количество центов: 2550
центов вместо 25.50
долларов.
1 Дуглас Крокфорд: JavaScript: Хорошие части : Приложение A - Ужасные части (стр. 105) .
Мой ответ довольно длинный, поэтому я разделил его на три части. Поскольку вопрос касается математики с плавающей точкой, я сделал упор на том, что на самом деле делает машина. Я также определил двойную (64-битную) точность, но аргумент одинаково применим к любой арифметике с плавающей запятой.
преамбула
IEEE 754 с двойной точностью в двоичном формате с плавающей точкой (binary64) число представляет собой число вида
значение = (-1) ^ с * (1.м 51 м 50 ... м 2 м 1 м 0 ) 2 * 2 e-1023
в 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 для числа двойной точности (я разделяю эти три части двоеточиями ( :
) :
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
: последние несколько бит округляются.
Числа с плавающей запятой, хранящиеся в компьютере, состоят из двух частей: целого числа и показателя степени, из которого берется основание и умножается на целочисленную часть.
Если бы компьютер работал в базе 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
. Числа с плавающей точкой обычно округляются для отображения.
Ошибка округления с плавающей точкой. Из того, что каждый компьютерщик должен знать об арифметике с плавающей точкой :
Сжатие бесконечного числа действительных чисел в конечное число бит требует приблизительного представления. Хотя целых чисел бесконечно много, в большинстве программ результат целочисленных вычислений может храниться в 32 битах. Напротив, при любом фиксированном количестве битов большинство вычислений с действительными числами будут давать величины, которые невозможно точно представить с использованием такого количества битов. Поэтому результат вычисления с плавающей точкой часто должен быть округлен, чтобы соответствовать его конечному представлению. Эта ошибка округления является характерной особенностью вычисления с плавающей точкой.
Было опубликовано много хороших ответов, но я хотел бы добавить еще один.
Не все числа могут быть представлены с помощью поплавков / двойников Например, число «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% . Обратите внимание, что ошибка может привести к чуть большим или меньшим значениям. Вот некоторые примеры:
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)
При вычитании всех значений ( a - b, где a> b ) с шагом 0,1 (от 100 до 0,1) мы имеем ~ 34% вероятности погрешности точности . Вот некоторые примеры:
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% и 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, это не красиво, но для некоторых проблем это единственное решение, и это одно из них.
if( (n * 0.1) < 100.0 ) { return n * 0.1 - 0.000000000000001 ;}
else { return n * 0.1 + 0.000000000000001 ;}
У меня была такая же проблема в научном симуляционном проекте на c #, и я могу вам сказать, что если вы проигнорируете эффект бабочки, он превратится в большого толстого дракона и укусит вас в а **
Чтобы предложить лучшее решение, я могу сказать, что обнаружил следующий метод:
parseFloat((0.1 + 0.2).toFixed(10)) => Will return 0.3
Позвольте мне объяснить, почему это лучшее решение. Как уже упоминалось в ответах выше, для решения проблемы рекомендуется использовать готовую функцию toFixed () Javascript. Но, скорее всего, вы столкнетесь с некоторыми проблемами.
Представьте , что вы собираетесь сложить два числа с плавающей точкой , как 0.2
и 0.7
здесь: 0.2 + 0.7 = 0.8999999999999999
.
Ваш ожидаемый результат 0.9
означает, что в этом случае вам нужен результат с точностью до 1 цифры. Таким образом, вы должны были использовать, (0.2 + 0.7).tofixed(1)
но вы не можете просто дать определенный параметр toFixed (), так как он зависит от заданного числа, например
`0.22 + 0.7 = 0.9199999999999999`
В этом примере вам нужна точность в 2 цифры, так и должно быть toFixed(2)
, так какой же параметр должен соответствовать каждому заданному числу с плавающей точкой?
Вы можете сказать, пусть это будет 10 в каждой ситуации:
(0.2 + 0.7).toFixed(10) => Result will be "0.9000000000"
Черт! Что вы собираетесь делать с этими нежелательными нулями после 9? Пришло время преобразовать это в float, чтобы сделать это, как вы хотите:
parseFloat((0.2 + 0.7).toFixed(10)) => Result will be 0.9
Теперь, когда вы нашли решение, лучше предложить его в виде такой функции:
function floatify(number){
return parseFloat((number).toFixed(10));
}
Давайте попробуем сами:
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;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="number1" value="0.2" onclick="addUp()" onkeyup="addUp()"/> +
<input id="number2" value="0.7" onclick="addUp()" onkeyup="addUp()"/> =
<p>Expected Result: <span id="expectedResult"></span></p>
<p>Unexpected Result: <span id="unexpectedResult"></span></p>
Вы можете использовать это так:
var x = 0.2 + 0.7;
floatify(x); => Result: 0.9
Как предполагает W3SCHOOLS, есть и другое решение, вы можете умножить и разделить, чтобы решить проблему выше:
var x = (0.2 * 10 + 0.1 * 10) / 10; // x will be 0.3
Имейте в виду, что (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 , которые представляют числа внутренне в десятичной записи (в отличие от двоичной записи). Оба имеют ограниченную точность, поэтому они все еще подвержены ошибкам, однако они решают наиболее распространенные проблемы с двоичной арифметикой с плавающей запятой.
Десятичные числа очень хороши при работе с деньгами: десять центов плюс двадцать центов всегда равны тридцати центам:
>>> 0.1 + 0.2 == 0.3
False
>>> Decimal('0.1') + Decimal('0.2') == Decimal('0.3')
True
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 отдельных группах
SIGN EXPONENT FRACTION
и после этого он печатает сумму, которая при суммировании с достаточной точностью покажет значение, которое действительно существует в аппаратном обеспечении.
Поэтому, когда вы пишете float x = 999...
, компилятор преобразует это число в битовое представление, напечатанное функцией xx
, так, чтобы сумма, напечатанная функцией, yy
была равна данному числу.
На самом деле эта сумма только приблизительная. Для числа 999 999 999 компилятор вставит в битовое представление числа с плавающей точкой число 1 000 000 000
После кода я присоединяю консольный сеанс, в котором я вычисляю сумму терминов для обеих констант (за исключением PI и 999999999), которые действительно существуют в аппаратных средствах, вставленных туда компилятором.
#include <stdio.h>
#include <limits.h>
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<<i))!=0;
printf("%d ", b);
} while (i--);
printf("\n");
}
void
yy(float a)
{
int sign=!(*(unsigned long long*)&a&((unsigned long long)1<<31));
int fraction = ((1<<23)-1)&(*(int*)&a);
int exponent = (255&((*(int*)&a)>>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<<j, (fraction&(i-1))?'+':')' ), 0);
} while (j++, i>>=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);
}
Вот сеанс консоли, в котором я вычисляю реальное значение с плавающей точкой, которое существует в аппаратных средствах. Раньше я bc
печатал сумму терминов, выводимых основной программой. Можно вставить эту сумму в Python repl
или что-то подобное.
-- .../terra1/stub
@ qemacs f.c
-- .../terra1/stub
@ gcc f.c
-- .../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
Вот и все. Значение 999999999 на самом деле
999999999.999999446351872
Вы также можете проверить, 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 цифрами точности. Вы проверяете,
1/3 + 2 / 3 == 1
и узнай, что это возвращается false
. Почему? Ну, а реальные цифры у нас есть
1/3 = 0,333 .... и 2/3 = 0,666 ....
Обрезая в восемь знаков после запятой, мы получаем
0.33333333 + 0.66666666 = 0.99999999
что, конечно, отличается от 1.00000000
точно 0.00000001
.
Ситуация для двоичных чисел с фиксированным числом битов в точности аналогична. Как реальные цифры, мы имеем
1/10 = 0,0001100110011001100 ... (база 2)
а также
1/5 = 0,0011001100110011001 ... (база 2)
Если бы мы урезали их, скажем, до семи бит, то мы бы получили
0.0001100 + 0.0011001 = 0.0100101
в то время как с другой стороны,
3/10 = 0,01001100110011 ... (база 2)
который, усеченный до семи бит, есть 0.0100110
, и они отличаются точно 0.0000001
.
Точная ситуация немного сложнее, потому что эти цифры обычно хранятся в научной записи. Так, например, вместо того, чтобы хранить 1/10, поскольку 0.0001100
мы можем хранить это как-то так 1.10011 * 2^-4
, в зависимости от того, сколько битов мы выделили для экспоненты и мантиссы. Это влияет на то, сколько знаков точности вы получите для своих расчетов.
В результате из-за этих ошибок округления вы по существу никогда не захотите использовать == для чисел с плавающей запятой. Вместо этого вы можете проверить, меньше ли абсолютное значение их разности, чем некоторое фиксированное небольшое число.
Начиная с Python 3.5 вы можете использовать math.isclose()
функцию для проверки приблизительного равенства:
>>> import math
>>> math.isclose(0.1 + 0.2, 0.3)
True
>>> 0.1 + 0.2 == 0.3
False
Поскольку этот поток немного расширился до общего обсуждения текущих реализаций с плавающей запятой, я бы добавил, что существуют проекты по устранению их проблем.
Взгляните, например, на https://posithub.org/ , который демонстрирует тип числа, называемый posit (и его предшественник unum), который обещает предложить лучшую точность с меньшим количеством битов. Если мое понимание верно, это также устраняет проблемы в этом вопросе. Довольно интересный проект, за ним стоит математик доктор Джон Густафсон . Все это с открытым исходным кодом, со многими актуальными реализациями на C / C ++, Python, Julia и C # ( https://hastlayer.com/arithmetics ).
Это на самом деле довольно просто. Когда у вас есть система 10-й базы (как наша), она может выражать только дроби, которые используют основной множитель базы. Первичные множители 10 равны 2 и 5. Таким образом, 1/2, 1/4, 1/5, 1/8 и 1/10 могут быть выражены чисто, потому что все знаменатели используют простые множители 10. Напротив, 1 / 3, 1/6 и 1/7 - все повторяющиеся десятичные дроби, потому что их знаменатели используют простой множитель 3 или 7. В двоичном (или базовом 2) единственном простом множителе является 2. Таким образом, вы можете выражать только те дроби, которые содержат только 2 как главный фактор. В двоичном коде 1/2, 1/4, 1/8 все будет выражено чисто в десятичном виде. В то время как 1/5 или 1/10 будут повторять десятичные дроби. Таким образом, 0,1 и 0,2 (1/10 и 1/5), в то время как чистые десятичные дроби в системе Base 10 повторяют десятичные дроби в системе Base 2, в которой работает компьютер. Когда вы выполняете математику для этих повторяющихся десятичных знаков,
Десятичные числа, такие как 0.1
, 0.2
и 0.3
не представлены точно в двоично-кодированных типах с плавающей запятой. Сумма аппроксимаций для 0.1
и 0.2
отличается от аппроксимации, использованной для 0.3
, следовательно, ложь, 0.1 + 0.2 == 0.3
как можно увидеть здесь более четко:
#include <stdio.h>
int main() {
printf("0.1 + 0.2 == 0.3 is %s\n", 0.1 + 0.2 == 0.3 ? "true" : "false");
printf("0.1 is %.23f\n", 0.1);
printf("0.2 is %.23f\n", 0.2);
printf("0.1 + 0.2 is %.23f\n", 0.1 + 0.2);
printf("0.3 is %.23f\n", 0.3);
printf("0.3 - (0.1 + 0.2) is %g\n", 0.3 - (0.1 + 0.2));
return 0;
}
Вывод:
0.1 + 0.2 == 0.3 is false
0.1 is 0.10000000000000000555112
0.2 is 0.20000000000000001110223
0.1 + 0.2 is 0.30000000000000004440892
0.3 is 0.29999999999999998889777
0.3 - (0.1 + 0.2) is -5.55112e-17
Чтобы эти вычисления были оценены более надежно, вам необходимо использовать десятичное представление для значений с плавающей запятой. Стандарт C не определяет такие типы по умолчанию, но как расширение, описанное в техническом отчете .
В _Decimal32
, _Decimal64
и _Decimal128
типы могут быть доступны в вашей системе (например, GCC поддерживает их на выбранные цели , но Clang не поддерживает их на OS X ).
Math.sum (javascript) .... вид замены оператора
.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;
}
},
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);
}
}
});
идея состоит в том, чтобы использовать Math вместо операторов, чтобы избежать ошибок с плавающей точкой
Math.sum автоматически определяет точность использования
Math.sum принимает любое количество аргументов
Рассмотрим следующие результаты:
error = (2**53+1) - int(float(2**53+1))
>>> (2**53+1) - int(float(2**53+1))
1
Мы можем четко видеть точку останова, когда 2**53+1
- все работает нормально, пока 2**53
.
>>> (2**53) - int(float(2**53))
0
Это происходит из-за двоичного двоичного формата IEEE 754 с двойной точностью двоичного формата: двоичная64
Со страницы Википедии для формата с плавающей запятой двойной точности :
Бинарная с плавающей запятой двойной точности является широко используемым форматом на ПК благодаря более широкому диапазону с плавающей запятой одинарной точности, несмотря на его производительность и стоимость полосы пропускания. Как и в формате с плавающей запятой одинарной точности, ему не хватает точности для целых чисел по сравнению с целочисленным форматом того же размера. Это обычно называют просто двойной. Стандарт IEEE 754 определяет двоичный код 64 как имеющий:
- Бит знака: 1 бит
- Экспонент: 11 бит
- Значительная точность: 53 бита (52 явно хранятся)
Реальное значение, принятое данным 64-битным датумом двойной точности с заданным смещенным показателем и 52-битной дробью, равно
или
Спасибо @a_guest за указание на это мне.
Другой вопрос был назван как дубликат этого:
В C ++, почему результат cout << x
отличается от значения, для которого отображается отладчик x
?
В x
вопросе это float
переменная.
Одним из примеров будет
float x = 9.9F;
Отладчик показывает 9.89999962
, вывод cout
операции есть 9.9
.
Получается, что ответом является cout
точность по умолчанию для float
6, поэтому он округляется до 6 десятичных цифр.
Смотрите здесь для справки