Как бороться с точностью чисел с плавающей точкой в ​​JavaScript? - PullRequest
511 голосов
/ 22 сентября 2009

У меня есть следующий фиктивный скрипт:

function test(){
    var x = 0.1 * 0.2;
    document.write(x);
}
test();

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

У кого-нибудь есть хорошее решение, чтобы в таком случае я получил правильный результат 0.02? Я знаю, что есть такие функции, как toFixed, иначе возможна округление, но я бы хотел напечатать целое число без обрезки и округления Просто хотел узнать, есть ли у кого-нибудь хорошее, элегантное решение.

Конечно, иначе я округлю до 10 или около того цифр.

Ответы [ 34 ]

7 голосов
/ 09 апреля 2018

Удивительно, но эта функция еще не была опубликована, хотя у других есть похожие варианты. Это из веб-документов MDN для Math.round (). Это сжато и допускает переменную точность.

function precisionRound(number, precision) {
  var factor = Math.pow(10, precision);
  return Math.round(number * factor) / factor;
}

console.log (precisionRound (1234.5678, 1)); // ожидаемый результат: 1234.6

console.log (precisionRound (1234.5678, -1)); // ожидаемый результат: 1230

var inp = document.querySelectorAll('input');
var btn = document.querySelector('button');

btn.onclick = function(){
  inp[2].value = precisionRound( parseFloat(inp[0].value) * parseFloat(inp[1].value) , 5 );
};

//MDN function
function precisionRound(number, precision) {
  var factor = Math.pow(10, precision);
  return Math.round(number * factor) / factor;
}
button{
display: block;
}
<input type='text' value='0.1'>
<input type='text' value='0.2'>
<button>Get Product</button>
<input type='text'>
6 голосов
/ 09 августа 2010

Чтобы избежать этого, вы должны работать с целочисленными значениями, а не с плавающей точкой. Поэтому, когда вы хотите иметь точность в 2 позиции, работайте со значениями * 100, для 3 позиций используйте 1000. При отображении вы используете форматер, чтобы вставить разделитель.

Многие системы опускают работу с десятичными знаками таким образом. Именно поэтому многие системы работают с центами (как целые числа) вместо долларов / евро (как с плавающей запятой).

6 голосов
/ 09 августа 2010

Результат, который вы получили, является правильным и достаточно согласованным для реализаций с плавающей запятой на разных языках, процессорах и операционных системах - единственное, что изменяется, это уровень погрешности, когда значение с плавающей запятой на самом деле двойное (или выше).

0,1 в двоичных числах с плавающей запятой подобен 1/3 в десятичной (т. Е. 0,3333333333333 ... навсегда), точного способа справиться с этим просто нет.

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

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

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

5 голосов
/ 27 июля 2017

Задача

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

Ввод и вывод для значений с плавающей запятой

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

  • Округлите ввод с ожидаемой точностью или убедитесь, что значения не могут быть введены с более высокой точностью.
  • Добавьте к выходам небольшое значение перед округлением / форматированием, которое меньше или равно 1/4 требуемой точности и больше максимальной ожидаемой ошибки, вызванной ошибками округления на входе и во время расчета. Если это невозможно, комбинации точности используемого типа данных недостаточно, чтобы обеспечить желаемую точность вывода для ваших расчетов.

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

Дискретные функции или операторы (например, по модулю)

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

Лучше избегать проблем

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

5 голосов
/ 04 марта 2015

Вы можете использовать parseFloat() и toFixed(), если хотите обойти эту проблему для небольшой операции:

a = 0.1;
b = 0.2;

a + b = 0.30000000000000004;

c = parseFloat((a+b).toFixed(2));

c = 0.3;

a = 0.3;
b = 0.2;

a - b = 0.09999999999999998;

c = parseFloat((a-b).toFixed(2));

c = 0.1;
5 голосов
/ 12 февраля 2015

0,6 * 3 это круто!)) Для меня это прекрасно работает:

function dec( num )
{
    var p = 100;
    return Math.round( num * p ) / p;
}

Очень, очень просто))

3 голосов
/ 22 сентября 2009

Посмотрите на Арифметика с фиксированной точкой . Вероятно, это решит вашу проблему, если диапазон чисел, с которыми вы хотите работать, невелик (например, валюта). Я бы округлил его до нескольких десятичных значений, что является самым простым решением.

3 голосов
/ 08 мая 2015

enter image description here

    You can use library https://github.com/MikeMcl/decimal.js/. 
    it will   help  lot to give proper solution. 
    javascript console output 95 *722228.630 /100 = 686117.1984999999
    decimal library implementation 
    var firstNumber = new Decimal(95);
    var secondNumber = new Decimal(722228.630);
    var thirdNumber = new Decimal(100);
    var partialOutput = firstNumber.times(secondNumber);
    console.log(partialOutput);
    var output = new Decimal(partialOutput).div(thirdNumber);
    alert(output.valueOf());
    console.log(output.valueOf())== 686117.1985
3 голосов
/ 06 октября 2009

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

3 голосов
/ 05 августа 2010

Вы не можете представлять большинство десятичных дробей точно с двоичными типами с плавающей запятой (это то, что ECMAScript использует для представления значений с плавающей запятой). Таким образом, не существует элегантного решения, если вы не используете арифметические типы произвольной точности или тип с плавающей запятой в десятичной форме. Например, приложение Calculator, поставляемое с Windows, теперь использует арифметику произвольной точности для решения этой проблемы .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...