Как справиться с точностью числа с плавающей запятой в 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 другие подобные библиотеки:
небольшой, быстрый JavaScript библиотека для десятичной арифметики произвольной точности. Маленькая сестра bignumber.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 × .2 = <span id="product"></span><br>
.1 + .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 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);
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"));
возможно, Вам понадобится рефакторинг этого кода, потому что я не очень хорошо программирую:)