У меня есть следующий тестовый скрипт:
function test() {
var x = 0.1 * 0.2;
document.write(x);
}
test();
У меня есть следующий тестовый скрипт:
function test() {
var x = 0.1 * 0.2;
document.write(x);
}
test();
Из путеводителя с плавающей точкой:
Что я могу сделать, чтобы избежать этой проблемы?
Это зависит от того, какие расчеты вы делаете.
- Если вам действительно нужно, чтобы ваши результаты точно суммировались, особенно когда вы работаете с деньгами: используйте специальный десятичный тип данных.
- Если вы просто не хотите видеть все эти дополнительные десятичные разряды: просто отформатируйте свой результат, округленный до фиксированного количества десятичных разрядов, при его отображении.
- Если у вас нет доступного десятичного типа данных, альтернативой является работа с целыми числами, например, производите денежные вычисления полностью в центах. Но это больше работы и имеет некоторые недостатки.
Обратите внимание, что первый пункт применяется только в том случае, если вам действительно нужно конкретное точное десятичное поведение. Большинству людей это не нужно, они просто раздражены тем, что их программы не работают правильно с числами, такими как 1/10, даже не осознавая, что они даже не будут мигать при той же ошибке, если она произошла с 1/3.
Если первая точка зрения действительно применима к вам, используйте BigDecimal для JavaScript, который совсем не элегантен, но фактически решает проблему, а не предоставляет несовершенный обходной путь.
Мне нравится решение Pedro Ladaria и используйте что-то подобное.
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); };
В этом случае:
> Math.m(0.1, 0.2)
0.02
Я определенно рекомендую использовать проверенную библиотеку, например SinfulJS
Вы только выполняете умножение? Если это так, вы можете использовать в своих интересах опрятный секрет о десятичной арифметике. Это значит, что NumberOfDecimals(X) + NumberOfDecimals(Y) = ExpectedNumberOfDecimals
. То есть, если мы имеем 0.123 * 0.12
, то мы знаем, что будет 5 десятичных знаков, потому что 0.123
имеет 3 десятичных знака, а 0.12
имеет два. Таким образом, если JavaScript дал нам число, подобное 0.014760000002
, мы можем безопасно округлить до пятого знака после запятой, не опасаясь потери точности.
Вы ищете реализацию sprintf
для JavaScript, чтобы вы могли выписывать поплавки с небольшими ошибками в них (поскольку они хранятся в двоичном формате) в ожидаемом формате.
Попробуйте javascript-sprintf, вы бы назвали это следующим образом:
var yourString = sprintf("%.2f", yourNumber);
чтобы распечатать ваш номер в виде поплавка с двумя знаками после запятой.
Вы можете также использовать Number.toFixed() для показа, если вы предпочитаете не включать больше файлов только для округления с плавающей запятой заданной точности.
Я нахожу BigNumber.js отвечает моим потребностям.
Библиотека JavaScript для десятичной и не десятичной арифметики с произвольной точностью.
У этого есть хорошая документация, и автор очень добросовестно реагирует на отзывы.
Тот же автор имеет две другие похожие библиотеки:
Небольшая, быстрая библиотека 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>
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);
}
Чтобы умножить a * b, где a = 5 и b = 0,0001:
Сдвиг десятичной точки результата, оставленного таким же количеством мест: 0.0005
function multiply (a, b) { // get number of decimal places to shift exp = b.toString().length - 2;
function makeInt (num) {
return num * Math.pow(10, exp);
}
function makeFloat(num) {
return num / Math.pow(100, exp);
}
return makeFloat(makeInt(a) * makeInt(b));
}
multiply (5, 0.0001);
Вам просто нужно решить, сколько десятичных цифр вы действительно хотите - не может иметь торт и съесть его: -)
Числовые ошибки накапливаются с каждой последующей операцией, и если вы не срежете ее раньше, она просто будет расти. Числовые библиотеки, которые представляют результаты, которые выглядят чистыми, просто отсекают последние 2 цифры на каждом шагу, числовые сопроцессоры также имеют "нормальную" и "полную" длину по той же причине. Cuf-offs дешевы для процессора, но очень дороги для вас в script (умножение и деление и использование 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-пик < 0,1 * 0,2 < 0,02 + eps).
Функция round() в phpjs.org работает красиво: http://phpjs.org/functions/round
num = .01 + .06; // yields 0.0699999999999
rnum = round(num,12); // yields 0.07
Удивительно, но эта функция еще не была опубликована, хотя у других есть похожие варианты. Это из веб-документов 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'>
Результат, который у вас есть, является правильным и довольно последовательным в реализации с плавающей точкой на разных языках, процессорах и операционных системах - единственное, что меняется, - это уровень неточности, когда float фактически является двойным (или более высоким).
0,1 в двоичных плавающих точках равно 1/3 в десятичном значении (т.е. 0,33333333333333... навсегда), просто нет точного способа его обработки.
Если вы имеете дело с поплавками, всегда ожидайте небольшие ошибки округления, поэтому вам также всегда придется округлять отображаемый результат до чего-то разумного. В ответ вы получаете очень быструю и мощную арифметику, потому что все вычисления находятся в нативной двоичной части процессора.
В большинстве случаев решение не переключается на арифметику с фиксированной точкой, главным образом потому, что она намного медленнее и в 99% случаев вам просто не нужна точность. Если вы имеете дело с вещами, которым необходим такой уровень точности (например, финансовые транзакции), Javascript, вероятно, не лучший инструмент для использования в любом случае (так как вы хотите использовать типы фиксированной точки, статический язык, вероятно, лучше).
Вы ищете элегантное решение, тогда я боюсь, что это так: поплавки бывают быстрыми, но имеют небольшие ошибки округления - всегда округляются до чего-то разумного при отображении их результатов.
Чтобы избежать этого, вы должны работать с целыми значениями вместо плавающих точек. Поэтому, когда вы хотите, чтобы точность двух позиций работала со значениями * 100, для трех позиций используется 1000. При отображении вы используете форматировщик для размещения разделителя.
Многие системы опускают работу с десятичными знаками таким образом. Вот почему многие системы работают с центами (как целое), а не долларами/евро (как с плавающей запятой).
0,6 * 3 это потрясающе!)) Для меня это прекрасно работает:
function dec( num )
{
var p = 100;
return Math.round( num * p ) / p;
}
Очень просто))
Вы можете использовать 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;
Обратите внимание, что для общего назначения такое поведение может быть приемлемым.
Проблема возникает при сравнении этих значений с плавающей запятой для определения соответствующего действия.
С появлением 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
Проблема
Плавающая точка не может точно хранить все десятичные значения. Поэтому при использовании форматов с плавающей запятой всегда будут ошибки округления на входных значениях. Ошибки на входах, конечно, приводят к ошибкам на выходе. В случае дискретной функции или оператора могут быть большие различия на выходе вокруг точки, где функция или оператор дискретна.
Ввод и вывод значений с плавающей запятой
Таким образом, при использовании переменных с плавающей запятой вы всегда должны знать об этом. И любой вывод, который вы хотите от вычисления с помощью плавающих точек, всегда должен быть отформатирован/подготовлен до отображения с учетом этого.
Когда используются только непрерывные функции и операторы, часто выполняется округление до нужной точности (не усекается). Стандартные функции форматирования, используемые для преобразования float в строку, обычно делают это за вас.
Поскольку округление добавляет ошибку, которая может привести к тому, что общая ошибка будет составлять более половины требуемой точности, результат должен быть скорректирован на основе ожидаемой точности входов и желаемой точности вывода. Вы должны
Эти две вещи обычно не выполняются, и в большинстве случаев различия, вызванные не их выполнением, слишком малы, чтобы быть важными для большинства пользователей, но у меня уже был проект, в котором результаты не были приняты пользователями без этих исправлений.
Дискретные функции или операторы (например, модулы)
При использовании дискретных операторов или функций могут потребоваться дополнительные исправления, чтобы гарантировать, что вывод будет таким, как ожидалось. Округление и добавление небольших исправлений до округления не могут решить проблему.
Может потребоваться специальная проверка/коррекция промежуточных результатов расчета, сразу после применения дискретной функции или оператора.
Для конкретного случая (оператор модуляции) см. Мой ответ на вопрос: Почему оператор модуля возвращает дробное число в javascript?
Лучше избегать проблемы
Часто бывает полезно избежать этих проблем, используя типы данных (целочисленные или фиксированные точечные форматы) для таких расчетов, которые могут хранить ожидаемый ввод без ошибок округления. Примером этого является то, что вы не должны использовать значения с плавающей запятой для финансовых расчетов.
Посмотрите Арифметика с фиксированной точкой. Вероятно, это решит вашу проблему, если диапазон чисел, который вы хотите использовать, невелик (например, валюта). Я бы включил его в несколько десятичных значений, что является самым простым решением.
Попробуйте мою хилиадическую арифметическую библиотеку, в которой вы можете увидеть здесь. Если вы хотите более позднюю версию, я могу вам помочь.
Вы не можете представлять большинство десятичных дробей точно с помощью двоичных типов с плавающей запятой (это то, что ECMAScript использует для представления значений с плавающей запятой). Поэтому нет элегантного решения, если вы не используете произвольные арифметические типы точности или тип с плавающей запятой, основанный на десятичной основе. Например, приложение Calculator, которое поставляется с Windows, теперь использует произвольную арифметику точности для решения этой проблемы.
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. Иногда, когда мне нужно было 0, я получал .000... 01. Это достаточно легко справиться, просто проверьте для < =.01. Но иногда я получаю 2.99999999999998. ОЙ!
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);
Используйте
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();
decimal.js, big.js или bignumber.js можно использовать, чтобы избежать проблем с манипулированием с плавающей точкой в Javascript:
0.1 * 0.2 // 0.020000000000000004
x = new Decimal(0.1)
y = x.times(0.2) // '0.2'
x.times(0.2).equals(0.2) // true
big.js: минималистский; легко использовать; точность указана в десятичных разрядах; точность применяется только к делению.
bignumber.js: bases 2-64; configuration options; NaN; Infinity; precision specified in decimal places; precision applied to division only; base prefixes.
decimal.js: bases 2-64; configuration options; NaN; Infinity; non-integer powers, exp, ln, log; precision specified in significant digits; precision always applied; random numbers.
Это работает для меня:
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})$/, '.$10');
} else if(two){ result = num.replace(/\.(\d{2})\d*/, '.$1');
} else { result = num*100; }
return result;
}
function test(){
var x = 0.1 * 0.2;
document.write(toFixedCurrency(x));
}
test();
Обратите внимание на вывод toFixedCurrency(x)
.