Неверные результаты деления - PullRequest
3 голосов
/ 05 января 2012

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

1 Ответ

4 голосов
/ 06 января 2012

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

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

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

0.9019659412190150568742192123085015451377473864159379314879347754618975606548754027220724570977710566

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

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

Обновление: Формат вывода по умолчанию - SCIENTIFIC, а не PLAIN. Примеры здесь

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

...