Почему BigDecimal возвращает странное значение?
Я пишу код, который будет касаться валют, сборов и т.д. Я собираюсь использовать класс BigDecimal для математики и хранения, но мы столкнулись с чем-то странным с ним.
Это утверждение:
1876.8 == BigDecimal('1876.8')
возвращает false.
Если я запустил эти значения через строку форматирования "%.13f"
, я получаю:
"%.20f" % 1876.8 => 1876.8000000000000
"%.20f" % BigDecimal('1876.8') => 1876.8000000000002
Обратите внимание на дополнительный 2
из BigDecimal в последней десятичной запятой.
Я думал, что BigDecimal должен был противостоять неточностям хранения реальных чисел непосредственно в собственной плавающей точке компьютера. Где находится этот 2
?
Ответы
Ответ 1
Вы правы, BigDecimal должен правильно его хранить, я думаю, что:
- BigDecimal правильно сохраняет значение
- Когда передается функция форматирования строк, BigDecimal передается как значение с плавающей запятой более низкой точности, создавая... 02.
- При сравнении непосредственно с поплавком, float имеет дополнительное десятичное место намного выше 20, которое вы видите (классические поплавки не сравниваются с поведением).
В любом случае вы вряд ли получите точные результаты, сравнивающие поплавок с BigDecimal.
Ответ 2
Это не даст вам большого контроля над количеством десятичных знаков, но механизм обычного формата для BigDecimal выглядит следующим образом:
a.to_s('F')
Если вам нужно больше контроля, подумайте об использовании драгоценного камня Money, предполагая, что проблема вашего домена в основном связана с валютой.
gem install money
Ответ 3
Не сравнивать фракции десятичной строки FPU для равенства
Проблема заключается в том, что сравнение равенства плавающего или двойного значения с десятичной константой, содержащей фракцию, редко бывает успешным.
Очень мало фракций десятичной дроби имеют точные значения в двоичном представлении FP, поэтому сравнения сравнений обычно обречены. *
Чтобы ответить на ваш точный вопрос, 2
исходит из немного другого преобразования фракции десятичной строки в формат Float
. Поскольку фракция не может быть представлена точно, возможно, что в двух вычислениях будут рассмотрены разные значения точности в промежуточных вычислениях и, в конечном итоге, округлить результат до 52-битной мантиссы с двойной точностью IEEE 754 по-разному. Это вряд ли имеет значение, потому что в любом случае нет точного представления, но, вероятно, оно более ошибочно, чем другое.
В частности, ваш 1876.8
не может быть точно представлен объектом FP, фактически, между 0,01 и 0,99, только 0,25, 0,50 и 0,75 имеют точные двоичные представления. Все остальные, включают 1876.8, повторяются навсегда и округляются до 52 бит. Это примерно половина причины, по которой BigDecimal существует. (Другая половина причины - фиксированная точность данных FP: иногда вам нужно больше.)
Итак, результат, который вы получаете при сравнении фактического значения машины с константой десятичной строки, зависит от каждого отдельного бита в двоичной дробной части... до 1/2 52... и даже тогда требуется округление.
Если есть хоть малейший бит (хе-хе, бит, извините), несовершенный в отношении процесса, который произвел номер, или код преобразования ввода или что-то еще, он не будет выглядеть ровно.
Можно даже аргументировать, что сравнение всегда должно терпеть неудачу, потому что ни один из FPU в формате IEEE даже не может точно представлять это число. Они действительно не равны, хотя они выглядят так. Слева ваша десятичная строка была преобразована в двоичную строку, и большинство чисел просто не преобразуются точно. Справа она все равно десятичная строка.
Так что не смешивайте float с BigDecimal, просто сравните один BigDecimal с другим BigDecimal. (Даже если оба операнда являются поплавками, тестирование на равенство требует большой осторожности или нечеткого теста. Кроме того, не доверяйте каждой форматированной цифре: форматирование вывода будет содержать остатки с правой стороны фракции, поэтому вы обычно не начинаете видя нули, вы увидите только значения мусора.)
* Проблема: машинные числа x/2 n но десятичные константы x/(2 n * 5 т). Ваше значение как знака, экспонента и мантиссы бесконечно повторяется 0 10000001001 1101010100110011001100110011001100110011001100110011...
По иронии судьбы, арифметика FP совершенно точна, а сравнения сравнений отлично работают, когда значение не имеет доли.
Ответ 4
как сказал Дэвид, BigDecimal сохраняет его правильно
p (BigDecimal('1876.8') * 100000000000000).to_i
возвращает 187680000000000000
Итак, да, форматирование строки разрушает его
Ответ 5
Если вам не нужны дробные центы, подумайте о хранении и управлении валютой в качестве целого числа, а затем разделите ее на 100, когда она отобразится. Я считаю, что это проще, чем иметь дело с неизбежными проблемами точности хранения и манипулирования в плавающей точке.
Ответ 6
В Mac OS X я запускаю ruby 1.8.7 (2008-08-11 patchlevel 72) [i686-darwin9]
irb(main):004:0> 1876.8 == BigDecimal('1876.8') => true
Однако, будучи Ruby, я думаю, вам следует подумать о сообщениях, отправленных объектам. Что это вам вернет:
BigDecimal('1876.8') == 1876.8
Оба не эквивалентны, и если вы пытаетесь использовать функцию BigDecimal для определения точного десятичного равенства, он должен быть получателем сообщения, спрашивающего о равенстве.
По той же причине я не думаю, что форматирование BigDecimal путем отправки сообщения формата в строку формата является правильным подходом.