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

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

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

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

2318
задан Cato Johnston 26 февр. '09 в 0:39
источник поделиться

35 ответов

  • 1
  • 2

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

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

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

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

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

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

1761
ответ дан Brian R. Bondy 26 февр. '09 в 0:40
источник поделиться

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

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

1. Обзор

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

2. Стандарты

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

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

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

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

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

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

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

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

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

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

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

6. Резюме

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

503
ответ дан KernelPanik 18 апр. '13 в 14:52
источник поделиться

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

365
ответ дан Joel Coehoorn 26 февр. '09 в 0:43
источник поделиться

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

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

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


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

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

233
ответ дан Chris Jester-Young 20 нояб. '14 в 5:39
источник поделиться

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

199
ответ дан Devin Jeanpierre 26 февр. '09 в 0:41
источник поделиться

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

Например:

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) ).

99
ответ дан Daniel Vassallo 09 апр. '10 в 15:25
источник поделиться

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

Преамбула

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

value = (-1) ^ s * (1.m 51 m 50... m 2 m 1 m 0) 2 * 2 e-1023

в 64 бит:

  • Первый бит - это бит знака: 1, если число отрицательно, 0 иначе 1.
  • Следующие 11 бит - это exponent, который offset на 1023. Другими словами, после чтения битов экспоненты из числа с двойной точностью 1023 необходимо вычесть для получения мощности двух.
  • Остальные 52 бита - это significand (или мантисса). В мантиссе "подразумеваемый" 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), но вот несколько примеров С# код для получения представления IEEE 754 для числа двойной точности (я разделяю три части с двоеточиями (:):

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

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

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

Достижение точки: исходный вопрос

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

Катон Джонстон (вопросник) спросил, почему 0.1 + 0.2!= 0.3.

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

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

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

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

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

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

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

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

sum = 2^-2 * 1.0011001100110011001100110011001100110011001100110011(1)

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

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

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

sum = 2^-2 * 1.0011001100110011001100110011001100110011001100110100

TL; DR

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

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

Преобразованные обратно в десятичные, следующие значения:

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

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

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

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

82
ответ дан Wai Ha Lee 23 февр. '15 в 20:15
источник поделиться

Решение, чтобы убрать неприглядное переполнение,

function strip(number) {
    return (parseFloat(number.toPrecision(12)));
}

Использование toPrecision(12) оставляет конечные нули, которые parseFloat() удаляет. Предположим, что он имеет точность до плюс/минус на наименьшей значащей цифре.

53
ответ дан linux_mike 03 дек. '17 в 0:09
поделиться

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

Если компьютер работал в базе 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 is 1 x 2⁻¹ и 0.25 is 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. Номера с плавающей запятой, как правило, округлены для отображения.

50
ответ дан Mark Ransom 16 марта '16 в 8:27
источник поделиться

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

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

41
ответ дан Brett Daniel 26 февр. '09 в 0:42
источник поделиться

Мое обходное решение:

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

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

30
ответ дан Justineo 26 дек. '12 в 9:51
источник поделиться

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

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

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

введите описание изображения здесь

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

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

27
ответ дан bruziuz 05 окт. '14 в 21:39
источник поделиться

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

При добавлении всех значений (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, когда точность имеет большое значение. С 2 десятичными цифрами (шаг 0,01) ситуация несколько ухудшается (18% и 36%).

23
ответ дан Konstantinos Chalkias 03 янв. '15 в 15:12
источник поделиться

Нет, не разбит, но большинство десятичных дробей должно быть аппроксимировано

Резюме

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

Даже простые числа, такие как 0.01, 0.02, 0.03, 0.04... 0.24, не представляются точно как двоичные дроби, даже если у вас были тысячи бит точности в мантиссе, даже если у вас были миллионы. Если вы отсчитываете с шагом 0,01, пока вы не достигнете 0,25, вы получите первую фракцию (в этой последовательности), представленную в базе 10 и base 2. Но если вы попытались использовать FP, ваш 0,01 был бы слегка отключен, поэтому единственный способ добавить 25 из них до хорошего точного 0.25 потребовал бы длинной цепи причинности, включающей защитные биты и округление. Трудно предсказать, чтобы мы бросили руки и сказали: "FP неточен".

Мы постоянно даем аппарату FP нечто, что кажется простым в базе 10, но является повторяющейся долей в базе 2.

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

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

          x/(2 n + 5 n).

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

          x/2 n

Итак, в десятичной форме мы не можем представить 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.

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

Работа с ним

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

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

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

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

Заключение

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

22
ответ дан DigitalRoss 03 февр. '16 в 2:49
источник поделиться

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

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

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

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

20
ответ дан workoverflow 01 авг. '12 в 10:02
источник поделиться

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

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

15
ответ дан Piyush S528 14 окт. '13 в 19:45
источник поделиться

Могу я просто добавить; люди всегда предполагают, что это проблема с компьютером, но если вы считаете своими руками (база 10), вы не можете получить (1/3+1/3=2/3)=true, если у вас нет бесконечности, чтобы добавить 0,3333... 0,333... так, как с (1/10+2/10)!==3/10 проблема в базе 2, вы усекаете ее до 0.333 + 0.333 = 0.666 и, вероятно, округлите ее до 0.667, что также будет технически неточным.

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

14
ответ дан user1641172 18 марта '16 в 3:38
источник поделиться

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

Так как это языковой агностический вопрос, ему нужны языковые агностические инструменты, такие как Decimal to Floating-Point Converter.

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

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

13
ответ дан Patricia Shanahan 21 дек. '15 в 14:15
источник поделиться

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

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

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

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

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

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

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

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

12
ответ дан Andrea Corbellini 21 авг. '15 в 17:53
источник поделиться

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

function getFloat(int) {
    var num = new Number(int);
    return parseFloat(num.toPrecision(2));
}
12
ответ дан Daniel Lee 03 дек. '17 в 0:09
поделиться

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

Синтаксис JavaScript: Номер

11
ответ дан Gary Willoughby 03 дек. '17 в 0:09
поделиться

Просто для удовольствия я играл с представлением поплавков, следуя определениям из Стандарта C99, и я написал код ниже.

Код печатает двоичное представление поплавков в трех выделенных группах

SIGN EXPONENT FRACTION

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

Поэтому, когда вы пишете float x = 999..., компилятор преобразует это число в битовое представление, напечатанное функцией xx, так что сумма, напечатанная функцией yy, будет равна заданному числу.

В действительности эта сумма является лишь приближением. Для номера 999,999,999 компилятор будет вставлять в битовое представление float номер 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);
}

Вот сеанс консоли, в котором я вычисляю действительное значение float, которое существует в аппаратном обеспечении. Я использовал 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.

9
ответ дан alinsoar 29 дек. '17 в 13:29
источник поделиться

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

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

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

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

9
ответ дан Blair Houghton 05 окт. '15 в 18:55
источник поделиться

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

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 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.

5
ответ дан Mohammad lm71 07 авг. '18 в 12:34
источник поделиться

Короче говоря коротко...

Для тех, кто использует Java и имеет такие проблемы: Используйте класс BigDecimal.

5
ответ дан Christian 03 дек. '17 в 0:09
поделиться

Я взял это из документации PHP:

http://php.net/manual/en/language.types.float.php#language.types.float.comparison

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

$a и $b равны 5 цифрам точности.

<?php
    $a = 1.23456789;
    $b = 1.23456780;
    $epsilon = 0.00001;

    if(abs($a-$b) < $epsilon) {
        echo "true";
    }
3
ответ дан Alex Calm1Kov 03 дек. '17 в 0:09
поделиться

Для тех, кто читает этот поток, стремясь получить точность до определенного количества десятичных знаков, а не цифр, вместо num.toPrecision(2) вы можете использовать num.toFixed(2 ).

3
ответ дан IcedDante 03 дек. '17 в 0:09
поделиться

Еще один способ взглянуть на это: Используются 64 бита для представления чисел. Как следствие, не может быть представлено более 2 ** 64 = 18 446 744 073 709 551 616 различных чисел.

Тем не менее, Math говорит, что существует уже бесконечное число десятичных знаков между 0 и 1. IEE 754 определяет кодировку для эффективного использования этих 64 бит для гораздо большего количества пробелов плюс NaN и +/- Infinity, поэтому между четко представленными числа, заполненные числами, только приближены.

К сожалению, 0,3 сидит в промежутке.

3
ответ дан noiv 20 дек. '17 в 1:37
источник поделиться

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

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

3
ответ дан Piedone 22 дек. '17 в 19:39
источник поделиться

Почти все объяснили и правильно ответили с разных точек зрения о том, почему это происходит. Если вам когда-нибудь понадобится работать с десятичными знаками с помощью Javascript и не попадать в эти проблемы, вы можете использовать модуль Big.js: https://github.com/MikeMcl/big.js. Он позволяет выполнять операции с произвольная точность десятичной арифметики.

var Big = require('big.js');
x = Big(.1);
console.log(x.plus(.2).toString());

Результат возвращает 0.3

3
ответ дан Luis Lobo Borobia 03 дек. '17 в 0:09
поделиться
  • 1
  • 2

Другие вопросы по меткам