30 Вопрос: Математика с плавающей точкой нарушена?

вопрос создан в Mon, Apr 29, 2019 12:00 AM

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

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

Почему возникают эти неточности?

    
2655
  1. Переменные с плавающей точкой обычно имеют такое поведение. Это связано с тем, как они хранятся в оборудовании. Для получения дополнительной информации ознакомьтесь с статьей Википедии о числах с плавающей запятой .
    2009-02-25 21: 41: 51Z
  2. JavaScript обрабатывает десятичные дроби как числа с плавающей запятой , что означает, что такие операции, как сложение, могут быть подвержены ошибке округления. Возможно, вы захотите взглянуть на эту статью: Что должен знать каждый компьютерный ученый Арифметика с плавающей точкой
    2009-02-25 21: 42: 49Z
  3. Только для информации, ВСЕ числовые типы в javascript - это двойные значения IEEE-754.
    2010-04-11 13: 01: 33Z
  4. Поскольку JavaScript использует стандарт IEEE 754 для математики, он использует 64-битные плавающие числа. Это приводит к ошибкам точности при выполнении вычислений с плавающей запятой (десятичной), короче, из-за компьютеров, работающих в Base 2 , в то время как десятичная дробь равна Base 10 .
    2018-05-07 04: 57: 28Z
  5. 2018-11-15 16: 10: 43Z
30 ответов                              30                         

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

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

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

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

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

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

Примечание. Все позиционные (базовые 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 может быть что-то вроде 1/2 ^ 16 (0.0000152587890625). В Javascript значение Number.EPSILON предоставляется для использования в качестве допуска.

    
2002
2019-05-17 22: 22: 06Z
  1. 'Некоторая константа ошибки', также известная как значение Epsilon.
    2010-04-09 12: 47: 06Z
  2. Я думаю, что "некоторая константа ошибки" более правильная, чем "Эпсилон", потому что нет "Эпсилона", который можно было бы использовать во всех случаях. Различные эпсилоны должны использоваться в разных ситуациях. И машина epsilon почти никогда не является хорошей константой для использования.
    2010-09-04 23: 33: 57Z
  3. Это не вполне верно, что вся математика с плавающей запятой основана на стандарте IEEE [754]. Например, все еще используются некоторые системы, которые имеют старую шестнадцатеричную FP от IBM, и есть графические карты, которые не поддерживают арифметику IEEE-754. Однако это справедливо для разумного приближения.
    2013-01-03 23: 36: 29Z
  4. Cray отказался от соответствия стандарту IEEE-754 по скорости. Java также ослабила свою приверженность в качестве оптимизации.
    2013-02-12 03: 12: 57Z
  5. Я думаю, вы должны добавить кое-что к этому ответу о том, как всегда должны выполняться вычисления на деньгах с арифметикой с фиксированной запятой по целым числам , потому что деньги квантуется. (Возможно, имеет смысл проводить расчеты внутреннего учета в крошечных долях цента, или какова бы ни была ваша наименьшая денежная единица - это часто помогает, например, уменьшить ошибку округления при преобразовании «29,99 долларов в месяц» в дневную ставку - но это должно по-прежнему быть арифметикой с фиксированной точкой.)
    2014-05-12 22: 23: 27Z

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

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

1. Обзор

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

2. Стандарты

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

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

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

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

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

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

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

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

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

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

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

6. Резюме

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

    
560
2018-04-13 16: 42: 24Z
  1. (3) неверно. Ошибка округления в делении составляет не менее одной единицы в последнем месте, но не более половины единицы в последнем месте.
    2014-04-23 22: 31: 22Z
  2. @ gnasher729 Хороший улов. Большинство основных операций также имеют ошибку менее 1/2 от одного устройства в последнем месте, используя режим округления IEEE по умолчанию. Отредактировал объяснение, а также отметил, что ошибка может быть больше, чем 1/2 от одного ulp, но меньше, чем 1 ulp, если пользователь отменяет режим округления по умолчанию (это особенно верно во встроенных системах).
    2014-04-24 11: 17: 14Z
  3. (1) числа с плавающей запятой не имеют ошибок. Каждое значение с плавающей запятой - это именно то, что есть. Большинство (но не все) операций с плавающей запятой дают неточные результаты. Например, нет двоичного значения с плавающей запятой, которое точно равно 1,0 /10,0. Некоторые операции (например, 1.0 + 1.0) do дают точные результаты с другой стороны.
    2014-06-10 16: 31: 54Z
  4. "Основной причиной ошибки в делении с плавающей запятой являются алгоритмы деления, используемые для вычисления фактора", - очень вводит в заблуждение сказать. Для деления, соответствующего стандарту IEEE-754, причиной only ошибки при делении с плавающей точкой является невозможность точного представления результата в формате результата; один и тот же результат вычисляется независимо от используемого алгоритма.
    2015-02-23 20: 23: 31Z
  5. @ Matt Извините за поздний ответ. Это в основном связано с ресурсами /временемnd компромиссы. Есть способ сделать длинное деление /более «нормальное» деление, это называется SRT Division с основанием два. Тем не менее, это многократно сдвигает и вычитает делитель из делимого и занимает много тактов, так как вычисляет только один бит отношения за такт. Мы используем таблицы обратных ссылок, чтобы мы могли вычислить больше битов отношения за цикл и сделать эффективный компромисс между производительностью и скоростью.
    2016-02-01 15: 33: 30Z

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

    
422
2009-02-25 22: 07: 00Z
  1. Отличный и короткий ответ. Повторяющийся шаблон выглядит как 0,00011001100110011001100110011001100110011001100110011 ...
    2012-06-16 14: 22: 35Z
  2. Это не объясняет, почему не используется лучший алгоритм, который не преобразуется в двоичные файлы.
    2016-05-10 14: 43: 53Z
  3. Потому что производительность. Использование двоичного кода происходит в несколько тысяч раз быстрее, поскольку оно является родным для машины.
    2016-05-10 19: 30: 37Z
  4. Существуют методы ARE, которые дают точные десятичные значения. BCD (двоично-десятичное число) или различные другие формы десятичного числа. Тем не менее, они оба медленнее (много медленнее) и занимают больше памяти, чем при использовании двоичной плавающей запятой. (Например, упакованный BCD хранит 2 десятичных цифры в байте. Это 100 возможных значений в байте, которые могут хранить 256 возможных значений, или 100/256, что приводит к потере около 60% возможных значений байта.)
    2016-06-21 16: 43: 02Z
  5. @ Jacksonkr, о котором вы все еще думаете в Base-10. Компьютеры базы-2.
    2016-11-14 16: 03: 08Z

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

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

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

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

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

Для чисел с двойной точностью (то есть точности, которая позволяет вам вдвое сократить вашу пиццу в 53 раза), числа, которые сразу меньше и больше 0,1, равны 0,09999999999999999167332731531132594682276248931884765625 и 0,10000000000000000555111512312578270211815834045410125. Последний немного ближе к 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,300000000000000044408920985009944363 599 099 099 099 899 099 099 899 099 899 0991 5991 5771995 0881 5991 5999 0991 599 0991 599 0991 599 0991 599 599 599 599 637 637 637 637 601 0991 599 599 599 637 637 637 601 0991 599 0)))))))))))))))))))))))))).

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

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

    
274
2014-11-22 04: 44: 18Z
  1. Обратите внимание, что в некоторых языках есть точная математика. Одним из примеров является схема, например, через GNU Guile. См. draketo.de/english/exact-math-to-the-rescue - они сохраняют математику в виде дроби и только в конце нарезаются.
    2014-11-20 06: 40: 16Z
  2. @ FloatingRock На самом деле очень немногие основные языки программирования имеют встроенные рациональные числа. Арне, как и я, - интриган, так что это то, на чем мы избалованы.
    2014-11-25 16: 56: 48Z
  3. @ ArneBabenhauserheide Я думаю, стоит добавить, что это будет работать только с рациональными числами. Так что, если вы выполняете некоторую математику с иррациональными числами, такими как число пи, вам придется хранить ее как кратное число числа пи. Конечно, любое вычисление с использованием числа pi не может быть представлено как точное десятичное число.
    2015-03-11 13: 06: 23Z
  4. @ connexo Хорошо. Как бы вы запрограммировали ротатор для пиццы на 36 градусов? Что такое 36 градусов? (Подсказка: если вы можете определить это точно, у вас также есть резак для пиццы с точностью до десятой доли.) Другими словами, у вас не может быть 1/360 (градус) или 1 /10 (36 градусов) только с двоичной плавающей точкой.
    2015-08-13 14: 50: 56Z
  5. @ connexo Кроме того, "каждый идиот" не может повернуть пиццу на точно 36 градусов. Люди слишком склонны к ошибкам, чтобы делать что-то более точное.
    2015-08-13 14: 51: 44Z

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

    
204
2009-02-25 21: 41: 23Z
  1. компьютерам не нужно бесконечное количество памяти, чтобы получить правильную 0,1 + 0,2 = 0,3
    2011-10-15 16: 27: 52Z
  2. @ Pacerier Конечно, они могут использовать два целых числа с неограниченной точностью для представления дроби или использовать кавычки. Это определенное понятие «двоичный» или «десятичный» делает это невозможным - идея, что у вас есть последовательность двоичных /десятичных цифр и где-то там точка радиуса. Чтобы получить точные рациональные результаты, нам нужен лучший формат.
    2011-10-15 19: 45: 03Z
  3. @ Pacerier: ни двоичная, ни десятичная с плавающей точкой не могут точно хранить 1/3 или 1/13. Десятичные типы с плавающей точкой могут точно представлять значения в форме M /10 ^ E, , но они менее точны, чем двоичные числа одинакового размера, когда речь идет о представлении большинства других дробей . Во многих приложениях более полезно иметь более высокую точность с произвольными дробями, чем иметь идеальную точность с несколькими «особыми».
    2014-04-24 16: 43: 59Z
  4. @ Pacerier Они делают , если они хранят числа как двоичные числа с плавающей запятой, что и было точкой ответа.
    2014-08-14 22: 04: 46Z
  5. @ chux: разница в точности между двоичными и десятичными типами невелика, но разница 10: 1 в точности для наилучшего и худшего случаев для десятичной дроби Типы намного больше, чем разница 2: 1 с двоичными типами. Мне любопытно, создал ли кто-нибудь аппаратное или письменное программное обеспечение для эффективной работы с любым из десятичных типов, поскольку ни один из них не поддается эффективной реализации в аппаратном или программном обеспечении.
    2015-08-26 19: 47: 05Z

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

Например:

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

    
113
2010-09-05 02: 02: 26Z
  1. Проблема в том, что само преобразование является неточным. 16,08 * 100 = 1607,999999999998. Нужно ли прибегать к разделению числа и преобразованию отдельно (как в 16 * 100 + 08 = 1608)?
    2011-10-07 19: 13: 23Z
  2. Здесь решение состоит в том, чтобы делать все ваши вычисления в целых числах, а затем делить на вашу пропорцию (в данном случае 100) и округлять только при представлении данных. Это гарантирует, что ваши расчеты всегда будут точными.
    2011-12-08 21: 38: 04Z
  3. Просто немного придираться: целочисленная арифметика точна только с плавающей точкой до точки (каламбур предназначен). Если число больше 0x1p53 (если использовать шестнадцатеричную нотацию с плавающей запятой в Java 7, = 9007199254740992), то ulp равен 2 в этой точке, поэтому 0x1p53 + 1 округляется до 0x1p53 (а 0x1p53 + 3 округляется до 0x1p53 + 4, из-за округления до четности). :-D Но, конечно, если ваше число меньше 9 квадриллионов, у вас все будет хорошо. -Р
    2014-12-03 13: 28: 49Z
  4. Итак, как получить .1 + .2 для отображения .3?
    2015-06-21 05: 58: 32Z
  5. Джейсон, тебе нужно просто округлить результат (int) (16.08 * 100 + 0.5)
    2015-12-23 09: 10: 39Z

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

Преамбула

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

  

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

в 64 битах:

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

1 - IEEE 754 допускает концепцию нулевой подписи - +0 и -0 трактуются по-разному: 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 (наибольшее денормальное число, для которого мантисса целиком состоит из 1s) равно 2 -1023 + 1 - 2 - 1023 - 51 (≈ 2,225 * 10 -308 ).

Превращение числа с двойной точностью в двоичное

Существует много онлайн-конвертеров для преобразования числа с плавающей запятой двойной точности в двоичное (например, по адресу binaryconvert.com ), но здесь приведен пример кода C # для получения представления IEEE 754 для числа с двойной точностью (я разделяю три части двоеточиями (:):

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

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

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

Суть вопроса: исходный вопрос

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

Катон Джонстон (задающий вопрос) спросил, почему 0,1 + 0,2! = 0,3.

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

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

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

Также обратите внимание, что мы можем уменьшить мощность в показателе степени на 52 и сместить точку в двоичном представлении вправо на 52 места (очень похоже на 10 -3 * 1.23 == 10 -5 * 123). Это тогда позволяет нам представлять двоичное представление как точное значение, которое оно представляет в форме a * 2 p . где «а» является целым числом.

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

0.1 => 2^-4 * [1].1001100110011001100110011001100110011001100110011010
0.2 => 2^-3 * [1].1001100110011001100110011001100110011001100110011010
or
0.1 => 2^-56 * 7205759403792794 = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794 = 0.200000000000000011102230246251565404236316680908203125

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

0.1 => 2^-3 *  0.1100110011001100110011001100110011001100110011001101(0)
0.2 => 2^-3 *  1.1001100110011001100110011001100110011001100110011010
sum =  2^-3 * 10.0110011001100110011001100110011001100110011001100111
or
0.1 => 2^-55 * 3602879701896397  = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794  = 0.200000000000000011102230246251565404236316680908203125
sum =  2^-55 * 10808639105689191 = 0.3000000000000000166533453693773481063544750213623046875

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

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

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

a = 2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875
  = 2^-2  * 1.0011001100110011001100110011001100110011001100110011

x = 2^-2  * 1.0011001100110011001100110011001100110011001100110011(1)

b = 2^-2  * 1.0011001100110011001100110011001100110011001100110100
  = 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125

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

sum = 2^-2  * 1.0011001100110011001100110011001100110011001100110100
    = 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125

в то время как двоичное представление 0.3 равно:

0.3 => 2^-2  * 1.0011001100110011001100110011001100110011001100110011
    =  2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875

, который отличается только от двоичного представления суммы 0,1 и 0,2 на 2 -54 .

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

TL; DR сильный> р>

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

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

Преобразованные обратно в десятичные, эти значения:

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

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

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

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

    
92
2019-04-09 10: 25: 28Z
  1. Мой ответ был отклонен вскоре после публикации. С тех пор я внес много изменений (в том числе явно отмечая повторяющиеся биты при записи 0.1 и 0.2 в двоичном формате, которые я опускал в оригинале). На случай, если избиратель увидит это, не могли бы вы дать мне обратную связь, чтобы я мог улучшить свой ответ? Я чувствую, что мой ответ добавляет что-то новое, поскольку обработка суммы в IEEE 754 не описывается в других ответах таким же образом. В то время как «То, что должен знать каждый специалист по информатике ...», охватывает примерно один и тот же материал, мой ответ касается конкретно случая 0,1 + 0,2.
    2015-02-24 07: 29: 45Z

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

Если бы компьютер работал в базе 10, 0.1 был бы 1 x 10⁻¹, 0.2 был бы 2 x 10⁻¹, а 0.3, а 3 x 10⁻¹ был бы 0.1 + 0.2 был бы 0.3 был бы 0.5 был бы 060035050505000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 (если бы не было, если бы т. д.

Компьютеры обычно не работают в базе 10, они работают в базе 2. Вы все еще можете получить точные результаты для некоторых значений, например, 1 x 2⁻¹ - 0.25 и 1 x 2⁻² - это 3 x 2⁻², и их добавление приводит к 0.75 или 0.1. Точно.

Проблема возникает с числами, которые могут быть представлены точно в базе 10, но не в базе 2. Эти числа должны быть округлены до их ближайшего эквивалента. Предполагая, что очень распространенный IEEE 64-битный формат с плавающей запятой, ближайший номер к 3602879701896397 x 2⁻⁵⁵ равен 0.2, а ближайший номер к 7205759403792794 x 2⁻⁵⁵ - 10808639105689191 x 2⁻⁵⁵; сложение их вместе приводит к 0.3000000000000000444089209850062616169452667236328125 или к точному десятичному значению

function add(a, b, precision) {
    var x = Math.pow(10, precision || 2);
    return (Math.round(a * x) + Math.round(b * x)) / x;
}
. Числа с плавающей запятой обычно округляются для отображения.     
53
2018-01-20 05: 00: 08Z
  1. @ Mark Спасибо за это понятное объяснение, но тогда возникает вопрос, почему 0,1 + 0,4 точно добавляет до 0,5 (по крайней мере в Python 3). Кроме того, каков наилучший способ проверки равенства при использовании чисел в Python 3?
    2018-01-20 03: 15: 40Z
  2. @ user2417881 Операции IEEE с плавающей запятой имеют правила округления для каждой операции, и иногда округление может дать точный ответ, даже если два числа немного отклонены. Детали слишком длинны для комментариев, и я все равно не эксперт в них. Как вы видите в этом ответе, 0.5 является одним из немногих десятичных знаков, которые могут быть представлены в двоичном формате, но это просто совпадение. Для проверки равенства см. stackoverflow.com/questions/5595425/… .
    2018-01-20 04: 35: 35Z
  3. @ user2417881 Ваш вопрос заинтриговал меня, поэтому я превратил его в полный вопрос и ответ: 2018-01-22 04: 27: 16Z

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

  

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

    
44
2017-12-27 00: 38: 28Z

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

0.2

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

    
31
2011-12-26 06: 51: 53Z

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

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

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

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

Даже если вы можете легко набрать FLT_RADIX, DBL_RADIX и

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)
равно 2; не 10 для компьютера с FPU, который использует «Стандарт IEEE для двоичной арифметики с плавающей точкой (ISO /IEEE Std 754-1985)».

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

    
27
2017-12-27 06: 59: 19Z

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

При добавлении всех значений ( a + b ) с шагом 0,1 (от 0,1 до 100) у нас ~ 15% вероятность ошибки точности . Обратите внимание, что ошибка может привести к чуть большим или меньшим значениям. Вот несколько примеров:

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)

При вычитании всех значений ( a - b , где a > b ) с шагом 0,1 (от 100 до 0,1) мы получаем ~ 34% вероятность ошибки точности . Вот несколько примеров:

nextafter()

* 15% и 34% действительно огромны, поэтому всегда используйте BigDecimal, когда точность имеет большое значение. С двумя десятичными цифрами (шаг 0.01) ситуация несколько ухудшается (18% и 36%).

    
26
2017-08-04 08: 41: 25Z

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

  

Резюме

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

Даже простые числа, такие как 0,01, 0,02, 0,03, 0,04 ... 0,24, не могут быть представлены в виде двоичных дробей. Если вы посчитаете 0,01, 0,02, 0,03 ..., то только до 0,25 вы получите первую дробь, представленную в базе 2 . Если вы попробуете это с использованием FP, ваш 0.01 был бы немного не таким, так что единственный способ добавить 25 из них до точного 0.25 потребовал бы длинной цепочки причинно-следственных связей, включая защитные биты и округление. Трудно предсказать, поэтому мы вскидываем руки и говорим «FP - это неточно», но это не совсем так.

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

  

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

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

a /(2 n x 5 m )

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

a /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 . р>

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

  

Работа с ним

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

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

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

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

  

Заключение р>

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

    
24
2018-10-10 22: 43: 27Z
  1. Округление до ближайшего целого числа не является безопасным способом решения проблемы сравнения во всех случаях. Округления 0,4999998 и 0,500001 округляются до различных целых чисел, поэтому вокруг каждой точки отсечения округления существует «опасная зона». (Я знаю, что эти десятичные строки, вероятно, не совсем представимы как двоичные числа IEEE.)
    2016-12-09 03: 31: 33Z
  2. Кроме того, хотя с плавающей запятой является "устаревшим" форматом, он очень хорошо спроектирован. Я не знаю ничего, что кто-то изменил бы, если бы сейчас его переделывали. Чем больше я узнаю об этом, тем больше я думаю, что он действительно хорошо разработан. например смещенный показатель означает, что последовательные двоичные числа с плавающей точкой имеют последовательные целочисленные представления, поэтому вы можете реализовать
     if( (n * 0.1) < 100.0 ) { return n * 0.1 - 0.000000000000001 ;}
                        else { return n * 0.1 + 0.000000000000001 ;}    
    
    с целочисленным приращением или уменьшением двоичного представления числа с плавающей запятой IEEE. Кроме того, вы можете сравнить числа с плавающей точкой как целые числа и получить правильный ответ, за исключением случаев, когда они оба отрицательны (из-за величины знака против дополнения 2).
    2016-12-09 03: 35: 01Z
  3. Я не согласен, числа с плавающей точкой должны храниться как десятичные числа, а не как двоичные, и все проблемы решены.
    2017-02-19 19: 32: 15Z
  4. Не должно " x /(2 ^ n + 5 ^ n) " быть " x /(2 ^ n * 5 ^ п) "?
    2018-02-05 07: 34: 25Z
  5. @ RonenFestinger - как насчет 1/3?
    2018-08-15 03: 32: 39Z

Вы пробовали решение для клейкой ленты?

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

float

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

    
18
2013-06-19 18: 50: 36Z

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

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

    
15
2013-10-14 16: 45: 17Z
  1. Я совсем не понимаю ваш второй абзац.
    2017-12-27 00: 19: 54Z
  2. @ Nae Я бы перевел второй абзац как "Большинство фракций не могут быть представлены точно в десятичном или двоичном. Поэтому большинство результатов будет округляется - хотя они все равно будут точными с количеством бит /цифр, свойственных используемому представлению. "
    2018-03-09 14: 19: 58Z

Многие из многочисленных дубликатов этого вопроса спрашивают о влиянии округления чисел с плавающей запятой на конкретные числа. На практике легче понять, как это работает, рассматривая точные результаты вычислений, а не просто читая об этом. Некоторые языки предоставляют способы сделать это - например, преобразовать double или BigDecimal в (1/3+1/3=2/3)=true в Java.

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

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

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

0.2 преобразуется в 0.200000000000000011102230246251565404236316680908203125,

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

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

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

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

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

    
13
2017-11-22 16: 18: 30Z
  1. Человеку, редактирование которого я только что откатил: я считаю кавычки кода подходящими для цитирования кода. Этот ответ, не зависящий от языка, вообще не содержит кода в кавычках. Числа могут использоваться в английских предложениях, и это не превращает их в код.
    2017-11-22 16: 22: 08Z
  2. Это вероятно почему кто-то форматировал ваши числа как код - не для форматирования, а для удобства чтения.
    2018-01-12 18: 24: 54Z
  3. ... также, округление до четного относится к представлению двоичного , не представление десятичное . См. этот или, например, this .
    2018-01-12 19: 33: 27Z

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

parseFloat((0.1 + 0.2).toFixed(10)) => Will return 0.3
в базе 2 вы усекаете его до 0,333 + 0,333 = 0,666 и, вероятно, округляете до 0,667, что также будетнеточно.

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

    
13
2018-03-26 22: 00: 40Z
  1. Поскольку люди используют десятичные числа, я не вижу веской причины, по которой числа с плавающей точкой не представлены в виде десятичного числа, поэтому у нас есть точные результаты.
    2017-02-19 19: 27: 48Z
  2. Люди используют множество основ, отличных от 10 (десятичные дроби), причем двоичные - это та, которую мы чаще всего используем для вычислений ... «веская причина» заключается в том, что вы просто не можете представлять каждое дробь в каждой базе ..
    2017-02-20 08: 59: 52Z
  3. @ Бинарную арифметику RonenFestinger легко реализовать на компьютерах, поскольку она требует только восьми основных операций с цифрами: скажем, $a $, $b $за $0,1 $- все, что вам нужно нужно знать, что это $\operatorname {xor} (a, b) $и $\operatorname {cb} (a, b) $, где xor является исключительным, а cb - это «бит переноса», который во всех случаях равен $0 $. кроме случаев, когда $a = 1 = b $, и в этом случае у нас есть один (на самом деле коммутативность всех операций экономит вам $2 $случаев, и все, что вам нужно, это $6 $правил). Десятичное расширение требует хранения 10 $× 11 $(в десятичной системе счисления) случаев и различных состояний $10 $для каждого бита и хранения отходов на переносе.
    2018-03-25 06: 36: 05Z

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

0.2

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

Представьте, что вы собираетесь сложить два числа с плавающей точкой, такие как 0.7 и 0.2 + 0.7 = 0.8999999999999999, вот оно: 0.9.

Ваш ожидаемый результат был (0.2 + 0.7).tofixed(1), это означает, что в этом случае вам нужен результат с точностью до 1 цифры. Таким образом, вы должны были использовать

`0.22 + 0.7 = 0.9199999999999999`
но вы не можете просто дать определенный параметр toFixed (), так как он зависит от заданного числа, например toFixed(2)

В этом примере вам нужна точность в 2 цифры, поэтому она должна быть

(0.2 + 0.7).toFixed(10) => Result will be "0.9000000000"
, так какой же должен быть параметр, чтобы соответствовать каждому заданному числу с плавающей точкой?

Тогда вы можете сказать, пусть это будет 10 в каждой ситуации:

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

Черт! Что вы собираетесь делать с этими нежелательными нулями после 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

Вы можете использовать это следующим образом:

var x = (0.2 * 10 + 0.1 * 10) / 10;       // x will be 0.3

Как W3SCHOOLS также предлагает другое решение, которое можно умножить и разделить для решения проблема выше:

(0.2 + 0.1) * 10 / 10

Имейте в виду, что decimal не будет работать вообще, хотя выглядит одинаково! Я предпочитаю первое решение, так как я могу применить его как функцию, которая преобразует входной float в точный выходной float.

    
13
2018-10-13 08: 27: 16Z

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

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

  • Python BigDecimal модуль и Java

    >>> 0.1 + 0.2 == 0.3
    False
    >>> Decimal('0.1') + Decimal('0.2') == Decimal('0.3')
    True
    
    class , которые представляют числа внутри с десятичной нотой(в отличие от двоичной записи). Оба имеют ограниченную точность, поэтому они все еще подвержены ошибкам, однако они решают наиболее распространенные проблемы с двоичной арифметикой с плавающей запятой.

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

    decimal

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

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

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

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

    
12
2015-08-21 15: 03: 35Z

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

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

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

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

    
9
2016-07-03 07: 45: 02Z

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

Код печатает двоичное представление с плавающей точкой в ​​3 отдельных группах

float x = 999...

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

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

#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);
}
, будет равна заданному числу.

В действительности эта сумма является лишь приблизительной. Для числа 999 999 999 компилятор вставит в битовое представление числа с плавающей точкой число 1 000 000 000

После кода я присоединяю сеанс консоли, в котором я вычисляю сумму терминов для обеих констант (за исключением PI и 999999999), которые действительно существуют в аппаратных средствах, вставленных туда компилятором.

bc

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

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

Вот и все. Значение 999999999 на самом деле

bc

Вы также можете проверить с scale, что -3.14 также нарушен. Не забудьте установить коэффициент bc в scale.

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

1/3 + 2 / 3 == 1
равным 15. Математически, с бесконечной точностью, кажется, что он равен 1 000 000 000.     
8
2017-12-27 02: 00: 04Z

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

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

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

    
5
2017-12-19 22: 48: 08Z

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

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

    
4
2018-04-12 17: 26: 20Z

Представьте, что вы работаете в базовой десятке, скажем, с 8 цифрами точности. Вы проверяете,

false

и узнайте, что это возвращает

0.33333333 + 0.66666666 = 0.99999999
. Почему? Ну, а реальные числа у нас есть

1/3 = 0,333 .... и 2/3 = 0,666 ....

Обрезая до восьми десятичных знаков, мы получаем

1.00000000

который, конечно, отличается от 0.00000001 ровно на

0.0001100 + 0.0011001 = 0.0100101
.

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

1/10 = 0,0001100110011001100 ... (база 2)

и р>

1/5 = 0,0011001100110011001 ... (база 2)

Если бы мы урезали их, скажем, до семи бит, то мы получили бы

0.0100110

в то время как с другой стороны,

3/10 = 0,01001100110011 ... (база 2)

который урезан до семи битов, это 0.0000001, и они точно отличаются на 0.0001100.

Точная ситуация немного сложнее, потому что эти цифры обычно хранятся в научной записи. Так, например, вместо сохранения 1/10 как 1.10011 * 2^-4 мы можем сохранить его как что-то вроде math.isclose(), в зависимости от того, сколько битов мы выделили для показателя степени и мантиссы. Это влияет на то, сколько цифр точности вы получите для своих расчетов.

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

    
3
2018-12-20 18: 27: 35Z

Начиная с Python 3.5 вы можете использовать функцию

>>> 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
    
3
2019-02-26 22: 12: 40Z

Math.sum (javascript) .... вид замены оператора

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.diff(0.2, 0.11) == 0.09 // true
0.2 - 0.11 == 0.09 // false

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

cout << x

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

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

    
2
2018-04-21 12: 13: 46Z

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

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

float в вопросе является переменной

float x = 9.9F;
.

Одним из примеров будет

9.89999962

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

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

См. здесь для справки

    
2
2018-06-15 13: 26: 07Z
  

На самом деле все довольно просто. Когда у вас есть система 10-й базы (как наша), она может выражать только дроби, которые используют основной множитель базы. Первичные множители 10 равны 2 и 5. Таким образом, 1/2, 1/4, 1/5, 1/8 и 1/10 могут быть выражены чисто, потому что все знаменатели используют простые множители 10. Напротив, 1 /3, 1/6 и 1/7 - все повторяющиеся десятичные дроби, потому что их знаменатели используют простой множитель 3 или 7. В двоичном (или базовом 2) единственном простом множителе является 2. Таким образом, вы можете выражать только те дроби, которые содержат только 2 как главный фактор. В двоичном коде 1/2, 1/4, 1/8 все будет выражено чисто в десятичном виде. В то время как 1/5 или 1/10 будут повторять десятичные дроби. Таким образом, 0,1 и 0,2 (1/10 и 1/5), в то время как чистые десятичные дроби в системе Base 10, являются повторяющимися десятичными знаками в системе Base 2, в которой работает компьютер. Когда вы выполняете математику с этими повторяющимися десятичными знаками, вы получаете остатки которые переносятся, когда вы преобразуете двоичное (двоичное) число компьютера в более удобочитаемое число 10.

С https://0.30000000000000004.com/

    
2
2019-05-07 20: 34: 37Z

Это было задуман как ответ на этот вопрос , закрытый как дубликат этот вопрос, в то время как я собирал этот ответ, так что теперь я не могу опубликовать его там ... поэтому я буду публиковать здесь вместо этого! р>

  

Сводка вопроса:

     

На листе 10^-11 и Number оцените как Равный , а в VBA - нет.

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

Если вы измените ячейки на числовой формат ( Ctrl + 1 ) с 15 с

=10^-11 returns 0.000000000010000
=10^(-8/1000) returns 0.981747943019984
десятичных точек, вы получите: 123.34

Таким образом, они определенно не одинаковы ... один почти равен нулю, а другой - примерно 1.

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

  

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

     

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

img

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

1
2018-10-02 03: 42: 06Z

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

Вывод:

_Decimal32

Чтобы эти вычисления были оценены более надежно, вам необходимо использовать десятичное представление для значений с плавающей запятой. Стандарт C не определяет такие типы по умолчанию, но как расширение, описанное в Технический отчет . Типы _Decimal64, _Decimal128 и gcc могут быть доступны в вашей системе (например, clang поддерживает их на выбранные цели , но не поддерживает их в OS /X).

    
0
2019-04-22 01: 12: 26Z
источник размещен Вот