Ruby BigDecimal проверка работоспособности (с плавающей запятой newb)

Насколько я понимаю, что с типами Ruby BigDecimal (даже с различной точностью и масштабами) должен точно вычисляться или я должен ожидать синонимов с плавающей запятой?

Все мои значения в приложении Rails имеют тип BigDecimal, и я вижу некоторые ошибки (у них разные десятичные длины), надеясь, что это только мои методы, а не мои типы объектов.

Ответ 1

При работе с арифметикой с плавающей запятой существует две распространенные ошибки.

Первая проблема заключается в том, что плавающие точки Ruby имеют фиксированную точность. На практике это будет либо 1) не проблема для вас, либо 2) катастрофическая, либо 3) что-то среднее между ними. Рассмотрим следующее:

# float
1.0e+25 - 9999999999999999900000000.0
#=> 0.0

# bigdecimal
BigDecimal("1.0e+25") - BigDecimal("9999999999999999900000000.0")
#=> 100000000

Точная разница в 100 миллионов! Довольно серьезно, правильно?

Кроме того, ошибка точности составляет всего около 0,000000000000001% от исходного числа. Это действительно зависит от вас, чтобы решить, является ли это проблемой или нет. Но проблема устраняется с помощью BigDecimal, поскольку она имеет произвольную точность. Ваш единственный предел - это память, доступная Ruby.

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

Это может иметь большое значение, когда вы округляете числа. Рассмотрим:

# float
(0.29 * 50).round
#=> 14  # not correct

# bigdecimal
(BigDecimal("0.29") * 50).round
#=> 15  # correct

A BigDecimal может точно описывать десятичные дроби. Однако существуют фракции, которые не могут быть точно описаны с десятичной дробью. Например, 1/9 - вечно повторяющаяся десятичная дробь (0.1111111111111...).

Опять же, это укусит вас, когда вы округлите число. Рассмотрим:

# bigdecimal
(BigDecimal("1") / 9 * 9 / 2).round
#=> 0  # not correct

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

Некоторые выводы:

  • Десятичные поплавки являются удивительными, если вы выполняете вычисления с десятичными дробями (например, деньги).
  • Ruby BigDecimal также хорошо работает, если вам нужны плавающие точки с произвольной точностью, и не волнует, являются ли они десятичными или двоичными плавающими точками.
  • Если вы работаете с (научными) данными, вы обычно имеете дело с фиксированными точками точности; Рубины встроенных поплавков, вероятно, будут достаточными.
  • Вы никогда не можете ожидать, что арифметика с любой плавающей точкой будет точной во всех ситуациях.