Неверные результаты деления

У меня есть калькулятор времени, который достаточно хорошо работает уже несколько лет. Одна вещь, которая всегда беспокоила меня, заключалась в том, что если использовать дробные секунды, результаты станут жертвой «ошибок» с плавающей запятой. Итак, недавно я переключился на использование этой библиотеки BigDecimal.

Теперь я получаю математические ошибки. Вот упрощенный тестовый пример из отчета об ошибке, который я получил сегодня: 27436 / 30418 возвращает 1 вместо ожидаемого 0.9019659412190151.

Чтобы проиллюстрировать мою проблему, вот сеанс консоли Javascript в Chrome:

> first = 27436
27436
> second = 30418
30418
> first / second
0.9019659412190151   // expected result, but using JS numbers, not BigDecimals
> firstB = new BigDecimal(first.toString())
o
> secondB = new BigDecimal(second.toString())
o
> firstB / secondB
0.9019659412190151 // this is a JS number, not a BigDecimal, so it's susceptible to the problems associated with floating-point.
> firstB.divide(secondB)
o  // BigDecimal object
> firstB.divide(secondB).toString()
"1"  // huh? Why 1?
> firstB.divideInteger(secondB).toString()
"0"

Как видите, метод divide() не дает ожидаемого результата. Что мне нужно сделать по-другому?

Обновлять

Вот еще некоторые подробности, в ответ на комментарии.

Во-первых, несколько человек предположили, что использование BigDecimal было излишним. Возможно, но я думаю, что прежде чем принимать такое решение, необходимо больше деталей. Это приложение представляет собой калькулятор времени. в BigDecimal. Во-первых, поскольку это калькулятор, важно показать пользователю правильный ответ. Если пользователь вводит 0.1 s + 0.2 s, он ожидает, что ответ будет 0.3 s, а не ответ, который ему покажет Javascript (0.30000000000000004).

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

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

Наконец, я не привязан конкретно к BigDecimal. Я открыт для других предложений, при условии, что я могу использовать их с моими недостаточными математическими способностями.


person Scott Severance    schedule 05.01.2012    source источник
comment
Возможно, это как-то связано с точностью. Например, если точность слишком мала, она просто округляется до ближайшего целого числа. Кроме того, кажется излишним использовать BigDecimal просто из-за ошибки с плавающей запятой. Вы также можете просто хранить числа как точные дроби.   -  person karnok    schedule 05.01.2012
comment
Да, использование BigDecimal мне тоже кажется излишним. Особенно, если вы выполняете вычисления с фиксированной точностью, например время, почему бы вам просто не использовать целые числа (секунды? миллисекунды? ..?), а затем выполнить все соответствующие преобразования..?   -  person redShadow    schedule 05.01.2012
comment
0,1 — довольно большая разница для ошибки округления, также проблема остается в firebug. возможно, вы захотите отправить отчет об ошибке.   -  person Samuel Herzog    schedule 05.01.2012
comment
@ j08691: Не могли бы вы объяснить, почему вы разместили эту ссылку и как она помогает в моей ситуации? Я уже знаю причину ошибок с плавающей запятой, как я объяснил в своем посте. Я ищу решение для моей конкретной проблемы, и я не вижу, как ваша ссылка решает мою проблему.   -  person Scott Severance    schedule 05.01.2012


Ответы (1)


Я еще не использовал BigDecimal ни в одном производственном коде, но нашел этот вопрос интересным, поэтому я попробую. Вы правы в необходимости MathContext в качестве параметра для функции деления. Вот что я сделал, основываясь на вашем примере:

console.log(firstB.divide(secondB, new MathContext(100)).toString());

Создание контекста, который указывает BigDecimal использовать 100 цифр в выходных данных научного режима:

0.9019659412190150568742192123085015451377473864159379314879347754618975606548754027220724570977710566

Также есть опции для управления различными режимами вывода PLAIN, SCIENTIFIC и ENGINEERING + различные режимы округления.

Полный пример на jsfiddle

Обновление. Выходной формат по умолчанию — SCIENTIFIC, а не PLAIN. Примеры здесь

Обновление 2. Создан крошечный тест производительности здесь. примерно в 10000 раз медленнее, чем собственное разделение javascript.

person eolsson    schedule 06.01.2012
comment
Спасибо. После обновления моей копии BigDecimal до последней версии все заработало. - person Scott Severance; 06.01.2012
comment
Ну, производительность не так критична. Я провожу операцию только с одним подразделением. Всего мое приложение выполняет около пяти вычислений в ответ на ввод пользователя, поэтому разница в скорости не будет заметной. Мы говорим о разнице, вероятно, в несколько мс. - person Scott Severance; 06.01.2012