Ruby BigDecimal проверка работоспособности (с плавающей точкой, новичок) - PullRequest
20 голосов
/ 14 июня 2010

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

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

1 Ответ

33 голосов
/ 15 июня 2010

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

Первая проблема заключается в том, что плавающие точки 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's BigDecimal также хорошо работает, если вам нужны числа с плавающей запятой произвольной точности, и вам все равно, десятичные они илидвоичные числа с плавающей запятой.
  • Если вы работаете с (научными) данными, вы обычно имеете дело с числами с фиксированной точностью;Вероятно, будет достаточно встроенных в Ruby плавающих чисел.
  • Нельзя ожидать, что арифметика с любым типом с плавающей точкой будет точной во всех ситуациях.
...