Вопрос

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

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

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

Это было полезно?

Решение

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

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

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

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

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

Достаточно полное рассмотрение вопросов арифметики с плавающей запятой Что должен знать каждый ученый-компьютерщик об арифметике с плавающей запятой.Более понятное объяснение см. с плавающей запятой-guide.de.

Примечание:Все позиционные системы счисления (с основанием N) с точностью сталкиваются с этой проблемой.

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

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

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

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

Боковое примечание:Работа с числами с плавающей запятой в программировании

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

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

Делать нет делать if (float1 == float2) { ... }

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

myToleranceValue необходимо выбрать для вашего конкретного приложения - и это будет во многом зависеть от того, сколько «пространства для маневра» вы готовы предоставить, и каким может быть наибольшее число, которое вы собираетесь сравнивать (из-за потери точности проблемы).Остерегайтесь констант стиля «double.Epsilon» на выбранном вами языке (Number.EPSILON в Javascript).Это нет использоваться в качестве значений допуска.

Дополнительная информация о допусках:

(Бесстыдная самореклама со стороны редактора - извините за угон)

Я собрал более подробное объяснение того, как выбрать допуск и почему следует избегать Number.EPSILON и ему подобных на странице https://dev.to/alldanielscott/how-to-compare-numbers-correctly-in-javascript-1l4i

Другие советы

Взгляд разработчика аппаратного обеспечения

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

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 бита частного за раз (основание 4), будет выглядеть так: 2+2= 4 биты (плюс несколько дополнительных битов).

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

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

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

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

5.Повторяющиеся операции

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

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

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

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

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

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

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

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

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


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

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

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

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

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

В частности, 0,1 + 0,2 на самом деле равно 0,1000000000000000055511151231257827021181583404541015625 + 0,2000000000000000111022302462515654042363166809. 08203125 = 0,3000000000000000444089209850062616169452667236328125, тогда как число, ближайшее к 0,3, на самом деле равно 0,299999999999999998889776975374843459576368331 9091796875.


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

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

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


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

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

Преамбула

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

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

в 64 битах:

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

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

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


Преобразование числа двойной точности в двоичное

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

Преобразование показателей степени в десятичный, удаление смещения и повторное добавление подразумеваемого 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н * 1.{bbb} увеличиваем показатель степени на единицу и сдвигаем десятичную дробь (двоичный) укажите, чтобы получить:

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

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

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

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

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

ТЛ;ДР

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

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

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

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

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

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

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

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

Было опубликовано много хороших ответов, но я хотел бы добавить еще один.

Не все числа могут быть представлены через плавает/парный разрядНапример, число «0,2» будет представлено как «0,200000003» с одинарной точностью в стандарте с плавающей запятой IEEE754.

Модель для хранения действительных чисел под капотом представляет числа с плавающей запятой как

enter image description here

Хотя вы можете ввести 0.2 легко, FLT_RADIX и DBL_RADIX равно 2;не 10 для компьютера с FPU, который использует «Стандарт IEEE для двоичной арифметики с плавающей запятой (ISO/IEEE Std 754-1985)».

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

Немного статистики, связанной с этим знаменитым вопросом двойной точности.

При добавлении всех значений (а + б) с шагом 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)

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

Как это произошло?

Когда мы пишем десятичные дроби, каждая дробь (в частности, каждая конечная десятичная дробь) – рациональное число вида

           а/(2н х 5м)

В двоичном формате мы получаем только 2н термин, то есть:

           а/2н

Итак, в десятичной форме мы не можем представить 1/3.Поскольку в число 10 входит 2 в качестве простого множителя, каждое число можно записать в виде двоичной дроби. также можно записать в виде десятичной дроби.Однако почти ничего мы не пишем в качестве основы10 дробь представима в двоичном формате.В диапазоне от 0,01, 0,02, 0,03...0,99, всего три числа могут быть представлены в нашем формате FP:0,25, 0,50 и 0,75, потому что они равны 1/4, 1/2 и 3/4, все числа с простым делителем используют только 2.н срок.

В базе10 мы не можем представлять 1/3.Но в двоичном формате мы не можем сделать 1/10 или 1/3.

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

Как справиться с этим

Разработчикам обычно поручают сделать < эпсилон при сравнении лучше всего было бы округлить значения до целых (в библиотеке C:round() и roundf(), т. е. оставайтесь в формате FP), а затем сравнивайте.Округление до определенной длины десятичной дроби решает большинство проблем с выводом.

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

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

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

Заключение

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

Вы пробовали решение с помощью клейкой ленты?

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

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

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

Эти странные числа появляются потому, что компьютеры используют для вычислений двоичную систему счисления (по основанию 2), а мы используем десятичную (по основанию 10).

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

Для того, чтобы предложить лучшее решение Могу сказать, что я открыл следующий метод:

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

Позвольте мне объяснить, почему это лучшее решение.Как уже упоминалось в ответах выше, для решения проблемы рекомендуется использовать готовую к использованию функцию Javascript toFixed().Но, скорее всего, вы столкнетесь с некоторыми проблемами.

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

Учитывая, что об этом никто не упомянул...

Некоторые языки высокого уровня, такие как Python и Java, содержат инструменты для преодоления ограничений двоичных чисел с плавающей запятой.Например:

  • Python decimal модуль и Java BigDecimal сорт, которые представляют числа внутри в десятичной системе счисления (в отличие от двоичной записи).Оба имеют ограниченную точность, поэтому они по-прежнему подвержены ошибкам, однако они решают наиболее распространенные проблемы с двоичной арифметикой с плавающей запятой.

    Десятичные дроби очень удобны при работе с деньгами:десять центов плюс двадцать центов всегда составляют ровно тридцать центов:

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

    Python decimal модуль основан на Стандарт IEEE 854-1987.

  • Python fractions модуль и Apache Common BigFraction сорт.Оба представляют рациональные числа как (numerator, denominator) пары, и они могут давать более точные результаты, чем десятичная арифметика с плавающей запятой.

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

Многие из многочисленных дубликатов этого вопроса спрашивают о влиянии округления с плавающей запятой на определенные числа.На практике легче понять, как это работает, глядя на точные результаты интересующих расчетов, а не просто читая об этом.Некоторые языки предоставляют способы сделать это, например, преобразование float или double к BigDecimal на Яве.

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

Применяя это к числам в вопросе, рассматриваемым как двойные:

0,1 преобразуется в 0,1000000000000000055511151231257827021181583404541015625,

0,2 преобразуется в 0,200000000000000011102230246251565404236316680908203125,

0,3 преобразуется в 0,2999999999999999988897769753748434595763683319091796875, и

0.30000000000000004 преобразуется в 0.3000000000000000444089209850062616169452667236328125.

Сложение первых двух чисел вручную или в десятичном калькуляторе, например Калькулятор полной точности, показывает, что точная сумма фактических входных данных равна 0,3000000000000000166533453693773481063544750213623046875.

Если бы оно было округлено до эквивалента 0,3, ошибка округления составила бы 0,0000000000000000277555756156289135105907917022705078125.Округление до эквивалента 0,30000000000000004 также дает ошибку округления 0,0000000000000000277555756156289135105907917022705078125.Применяется тай-брейк от раунда до чета.

Возвращаясь к конвертеру с плавающей запятой, необработанное шестнадцатеричное число для 0,30000000000000004 равно 3fd3333333333334, которое заканчивается четной цифрой и, следовательно, является правильным результатом.

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

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

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

Если вам нужна бесконечная точность (например, используя число π вместо одного из его многочисленных более коротких аналогов), вам следует вместо этого написать или использовать программу символьных вычислений.

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

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

К сожалению, 0,3 находится в зазоре.

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

Взгляни на https://posithub.org/ например, который демонстрирует числовой тип под названием posit (и его предшественник unum), который обещает обеспечить лучшую точность при меньшем количестве бит.Если я правильно понимаю, это также устраняет проблемы, о которых идет речь.Довольно интересный проект, за ним стоит математик. ДокторДжон Густавсон.Все это с открытым исходным кодом, со множеством реальных реализаций на C/C++, Python, Julia и C# (https://hastlayer.com/arithmetics).

Представьте себе, что вы работаете с десятичной системой счисления, скажем, с точностью до 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

Мат.сумма (джаваскрипт) ....вид замены оператора

.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(A-B,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);
        }
    }
});

идея состоит в том, чтобы использовать Math вместо операторов, чтобы избежать ошибок с плавающей запятой

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

также обратите внимание, что Math.diff и Math.sum автоматически определяют используемую точность.

Math.sum принимает любое количество аргументов

На самом деле это довольно просто.Если у вас есть система с основанием 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), хотя и являются чистыми десятичными дробями в системе с основанием 10, являются повторяющимися десятичными дробями в системе с основанием 2, в которой работает компьютер.Когда вы выполняете математические вычисления с этими повторяющимися десятичными дробями, вы получаете остатки, которые переносятся при преобразовании компьютерного числа по основанию 2 (двоичного) в более удобочитаемое число по основанию 10.

От https://0.30000000000000004.com/

Другой вопрос был назван дубликатом этого вопроса:

Почему в C++ результат cout << x отличается от значения, которое показывает отладчик x?

А x в вопросе есть float переменная.

Одним из примеров может быть

float x = 9.9F;

Отладчик показывает 9.89999962, выход cout операция 9.9.

Ответ оказывается, что coutточность по умолчанию для float равно 6, поэтому оно округляется до 6 десятичных цифр.

Видеть здесь для справки

На самом деле это было задумано как ответ на этот вопрос -- который был закрыт как дубликат этот вопрос, пока Я собирал этот ответ, поэтому теперь не могу опубликовать его там...поэтому вместо этого я напишу здесь!


Краткое содержание вопроса:

На рабочем листе 10^-8/1000 и 10^-11 оценить как Равный а в VBA их нет.

На рабочем листе числа по умолчанию указаны в научной нотации.

Если вы измените ячейки на числовой формат (Ctrl+1) из Number с 15 десятичных знаков, вы получаете:

=10^-11 returns 0.000000000010000
=10^(-8/1000) returns 0.981747943019984

Так что это точно не одно и то же...один примерно равен нулю, а другой примерно 1.

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


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

Преимущество представления с плавающей запятой перед представлением с фиксированной точкой заключается в том, что оно может поддерживать более широкий диапазон значений.Например, представление с фиксированной запятой, имеющее 5 десятичных цифр с десятичной запятой, расположенной после третьей цифры, может представлять числа. 123.34, 12.23, 2.45, и т. д.тогда как представление с плавающей запятой с точностью до 5 цифр может представлять собой 1,2345, 12345, 0,00012345 и т. д.Аналогичным образом, представление с плавающей запятой также позволяет выполнять вычисления в широком диапазоне величин, сохраняя при этом точность.Например,

img


Другие ссылки:

Десятичные дроби, такие как 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).

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top