Почему BigDecimal возвращает странное значение? - PullRequest
14 голосов
/ 23 апреля 2009

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

Это утверждение:

1876.8 == BigDecimal('1876.8')

возвращает false.

Если я пропущу эти значения через строку форматирования "%.13f" я получу:

"%.20f" % 1876.8 => 1876.8000000000000
"%.20f" % BigDecimal('1876.8') => 1876.8000000000002

Обратите внимание на дополнительные 2 из BigDecimal в последнем десятичном знаке.

Я думал, что BigDecimal должен был противостоять неточностям хранения действительных чисел непосредственно в собственной плавающей точке компьютера. Откуда это 2 исходит?

Ответы [ 6 ]

9 голосов
/ 24 апреля 2009

Это не даст вам такого большого контроля над количеством десятичных знаков, но обычный механизм форматирования для BigDecimal выглядит так:

a.to_s('F')

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

gem install money
8 голосов
/ 23 апреля 2009

Вы правы, BigDecimal должен правильно хранить его, мое лучшее предположение:

  • BigDecimal правильно хранит значение
  • При передаче в функцию форматирования строки BigDecimal приводится к значению с плавающей запятой с меньшей точностью, создавая ... 02.
  • Если сравнивать непосредственно с поплавком, у поплавка есть дополнительное десятичное число, намного превышающее 20, которые вы видите (классические поплавки нельзя сравнивать по поведению).

В любом случае, вы вряд ли получите точные результаты, сравнивая float с BigDecimal.

7 голосов
/ 25 декабря 2012

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

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


* Проблема: номера машин х / 2 n , а десятичные константы х / (2 n * 5 м ). Ваше значение как знак, показатель степени и мантисса - это бесконечно повторяющаяся 0 10000001001 1101010100110011001100110011001100110011001100110011... По иронии судьбы, арифметика FP идеально точна, и сравнения равенства работают превосходно, когда значение не имеет дроби.

3 голосов
/ 23 апреля 2009

как сказал Дэвид, BigDecimal хранит его правильно

 p (BigDecimal('1876.8') * 100000000000000).to_i

возвращает 187680000000000000

так что, да, форматирование строки разрушает его

2 голосов
/ 24 апреля 2009

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

1 голос
/ 23 апреля 2009

В 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 путем отправки сообщения форматирования в строку форматирования также является правильным подходом.

...