Как справиться с точностью числа с плавающей запятой в JavaScript?

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

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

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

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

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

30 ответов


с Руководство С Плавающей Запятой:

что я могу сделать, чтобы избежать этой проблемы?

Это зависит от того, какой вы делаете расчеты.

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

обратите внимание, что первый пункт применяется только в том случае, если вам действительно нужна конкретная точная decimal поведение. Большинство людей не нуждаются в этом, они просто раздражены тем, что их программы не работают правильно с числами, как 1/10, не понимая, что они даже не моргнут при той же ошибке, если это произошло с 1/3.

Если первый пункт действительно относится к вам, используйте BigDecimal для JavaScript, что совсем не элегантно, но на самом деле решает проблему, а не обеспечивает несовершенное обходное решение.


Мне нравится решение Педро Ладарии и использовать что-то подобное.

function strip(number) {
    return (parseFloat(number).toPrecision(12));
}

В отличие от решения Pedros это округлит 0.999...повторять с точностью до плюс/минус один на наименее значащей цифры.

Примечание: при работе с 32 или 64 бит терки, вы должны использовать toPrecision(7) и toPrecision(15) для достижения наилучших результатов. См.этот вопрос для получения информации о том, почему.


для математически склонных:http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

рекомендуемый подход - использовать поправочные коэффициенты (умножить на подходящую степень 10, чтобы арифметика происходила между целыми числами). Например, в случае 0.1 * 0.2, поправочный коэффициент 10, а вы выполняете расчет:

> var x = 0.1
> var y = 0.2
> var cf = 10
> x * y
0.020000000000000004
> (x * cf) * (y * cf) / (cf * cf)
0.02

A (очень быстрое) решение выглядит примерно так:

var _cf = (function() {
  function _shift(x) {
    var parts = x.toString().split('.');
    return (parts.length < 2) ? 1 : Math.pow(10, parts[1].length);
  }
  return function() { 
    return Array.prototype.reduce.call(arguments, function (prev, next) { return prev === undefined || next === undefined ? undefined : Math.max(prev, _shift (next)); }, -Infinity);
  };
})();

Math.a = function () {
  var f = _cf.apply(null, arguments); if(f === undefined) return undefined;
  function cb(x, y, i, o) { return x + f * y; }
  return Array.prototype.reduce.call(arguments, cb, 0) / f;
};

Math.s = function (l,r) { var f = _cf(l,r); return (l * f - r * f) / f; };

Math.m = function () {
  var f = _cf.apply(null, arguments);
  function cb(x, y, i, o) { return (x*f) * (y*f) / (f * f); }
  return Array.prototype.reduce.call(arguments, cb, 1);
};

Math.d = function (l,r) { var f = _cf(l,r); return (l * f) / (r * f); };

In этот случай:

> Math.m(0.1, 0.2)
0.02

Я определенно рекомендую использовать проверенные библиотеки типа SinfulJS


вы только выполняете умножение? Если это так, то вы можете использовать в своих интересах аккуратный секрет о десятичной арифметике. Вот оно что!--0-->. То есть, если у нас есть 0.123 * 0.12 тогда мы знаем, что будет 5 десятичных знаков, потому что 0.123 имеет 3 знака после запятой, а 0.12 имеет два. Таким образом, если JavaScript дал нам число, подобное 0.014760000002 можно смело округлить до 5-го десятичного знака, без страха потерять точность.


вы ищете sprintf реализация для JavaScript, так что вы можете выписывать поплавки с небольшими ошибками в них (так как они хранятся в двоичном формате) в формате, который вы ожидаете.

попробовать javascript-sprintf, вы бы назвали это так:

var yourString = sprintf("%.2f", yourNumber);

чтобы распечатать свой номер в виде поплавка с двумя десятичными знаками.

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


var times = function (a, b) {
    return Math.round((a * b) * 100)/100;
};

---или---

var fpFix = function (n) {
    return Math.round(n * 100)/100;
};

fpFix(0.1*0.2); // -> 0.02

---тоже---

var fpArithmetic = function (op, x, y) {
    var n = {
            '*': x * y,
            '-': x - y,
            '+': x + y,
            '/': x / y
        }[op];        

    return Math.round(n * 100)/100;
};

--- в ---

fpArithmetic('*', 0.1, 0.2);
// 0.02

fpArithmetic('+', 0.1, 0.2);
// 0.3

fpArithmetic('-', 0.1, 0.2);
// -0.1

fpArithmetic('/', 0.2, 0.1);
// 2

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

function multFloats(a,b){
  var atens = Math.pow(10,String(a).length - String(a).indexOf('.') - 1), 
      btens = Math.pow(10,String(b).length - String(b).indexOf('.') - 1); 
  return (a * atens) * (b * btens) / (atens * btens); 
}

Я нашел BigNumber.js отвечает моим потребностям.

библиотека JavaScript для десятичной и десятичной арифметики произвольной точности.

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

у того же автора есть 2 другие подобные библиотеки:

большой.js

небольшой, быстрый JavaScript библиотека для десятичной арифметики произвольной точности. Маленькая сестра bignumber.js.

и Decimal.js

десятичный тип произвольной точности для JavaScript.

вот код, использующий BigNumber:

$(function(){

  
  var product = BigNumber(.1).times(.2);  
  $('#product').text(product);

  var sum = BigNumber(.1).plus(.2);  
  $('#sum').text(sum);


});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<!-- 1.4.1 is not the current version, but works for this example. -->
<script src="http://cdn.bootcss.com/bignumber.js/1.4.1/bignumber.min.js"></script>

.1 &times; .2 = <span id="product"></span><br>
.1 &plus; .2 = <span id="sum"></span><br>

вы просто должны принять решение о том, сколько десятичных цифр вы на самом деле хотите - не может иметь торт и съесть его тоже : -)

числовые ошибки накапливаются с каждой последующей операцией, и если вы не отключите его рано, он просто будет расти. Численные библиотеки, которые представляют результаты, которые выглядят чистыми, просто отрезают последние 2 цифры на каждом шаге, числовые сопроцессоры также имеют "нормальную" и "полную" длину по той же причине. Cuf-offs дешевы для процессора, но очень дорого для вас в скрипте (умножение и деление и использование pov(...)). Хорошая математика lib обеспечит пол (x,n), чтобы сделать отсечку для вас.

поэтому, по крайней мере,вы должны сделать глобальный var/constant с pov(10, n) - это означает, что вы решили, какая точность вам нужна :-) затем сделайте:

Math.floor(x*PREC_LIM)/PREC_LIM  // floor - you are cutting off, not rounding

вы также можете продолжать делать математику и только отсечение в конце-предполагая, что вы только отображаете, а не делаете if - s с результатами. Если ты сможешь это сделать.toFixed(...) может быть более эффективным.

Если вы делаете if-s / сравнения и не хотите вырезать, вам также нужна небольшая константа, обычно называемая eps, которая на один десятичный знак выше, чем максимальная ожидаемая ошибка. Скажите, что ваша отсечка-последние две десятичные дроби - тогда ваш eps имеет 1 на 3-м месте от последнего (3-го наименее значимого), и вы можете использовать его для сравнения, находится ли результат в диапазоне eps ожидаемого (0.02-eps


функция Round() в phpjs.org работает: http://phpjs.org/functions/round

num = .01 + .06;  // yields 0.0699999999999
rnum = round(num,12); // yields 0.07

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

0.1 в двоичных плавающих точках, как 1/3 в десятичной (т. е. 0.3333333333333... forever), просто нет точного способа справиться с этим.

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

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

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


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

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


можно использовать 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;

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

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

0.6 * 3 это круто!)) Для меня это отлично работает:

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

очень очень простой))


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


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


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



использовать

var x = 0.1*0.2;
 x =Math.round(x*Math.pow(10,2))/Math.pow(10,2);

не элегантно, но делает работу (удаляет конечные нули)

var num = 0.1*0.2;
alert(parseFloat(num.toFixed(10))); // shows 0.02

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

конечно, это не очень поможет с иррациональными числами. Но вы можете оптимизировать свои вычисления в пути они вызовут наименьшую проблему (например, обнаружение ситуаций, таких как sqrt(3)^2).


Использовать Номер(1.234443).toFixed(2); он будет печатать 1.23

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

У меня была неприятная проблема с ошибкой округления с mod 3. Иногда, когда я должен был получить 0, я получал .000...01. Это достаточно легко обрабатывать, просто проверьте для

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

простое решение ирония:

x = Math.round(x*100);
// I only need 2 decimal places, if i needed 3 I would use 1,000, etc.
x = x / 100;
xB = new BigNumber(x);

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

обратите внимание, что для общего использования это поведение, вероятно, будет приемлемым.
Проблема возникает при сравнении этих значений с плавающими точками для определения соответствующего действия.
С появлением ES6 появилась новая константа Number.EPSILON определяется для определения допустимой погрешности:
Поэтому вместо выполнения сравнения, как это

0.1 + 0.2 === 0.3 // which returns false

вы можете определить пользовательскую функцию сравнения, как это :

function epsEqu(x, y) {
    return Math.abs(x - y) < Number.EPSILON;
}
console.log(epsEqu(0.1+0.2, 0.3)); // true

источник : http://2ality.com/2015/04/numbers-math-es6.html#numberepsilon


это работает для меня:

function round_up( value, precision ) { 
    var pow = Math.pow ( 10, precision ); 
    return ( Math.ceil ( pow * value ) + Math.ceil ( pow * value - Math.ceil ( pow * value ) ) ) / pow; 
}

round_up(341.536, 2); // 341.54

вывод с помощью следующей функции:

var toFixedCurrency = function(num){
    var num = (num).toString();
    var one = new RegExp(/\.\d{1}$/).test(num);
    var two = new RegExp(/\.\d{2,}/).test(num);
    var result = null;

    if(one){ result = num.replace(/\.(\d{1})$/, '.');
    } else if(two){ result = num.replace(/\.(\d{2})\d*/, '.');
    } else { result = num*100; }

    return result;
}

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

test();

обратите внимание на вывод toFixedCurrency(x).


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

приставка.log((parseFloat(0.1) + parseFloat(0.2)).toFixed(1) == parseFloat(0.3).toFixed(1));

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

Я написал это в блокноте

var toAlgebraic = function(f1, f2) {
    let f1_base = Math.pow(10, f1.split('.')[1].length);
    let f2_base = Math.pow(10, f2.split('.')[1].length);
    f1 = parseInt(f1.replace('.', ''));
    f2 = parseInt(f2.replace('.', ''));

    let dif, base;
    if (f1_base > f2_base) {
        dif = f1_base / f2_base;
        base = f1_base;
        f2 = f2 * dif;
    } else {
        dif = f2_base / f1_base;
        base = f2_base;
        f1 = f1 * dif;
    }

    return (f1 * f2) / base;
};

console.log(0.1 * 0.2);
console.log(toAlgebraic("0.1", "0.2"));

возможно, Вам понадобится рефакторинг этого кода, потому что я не очень хорошо программирую:)