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


620

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

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

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

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

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


118
На самом деле, ошибка в том, что нет способа сопоставить 0.1конечное двоичное число с плавающей запятой.
Аарон Дигулла

10
Большинство дробей не могут быть преобразованы в десятичную с точной точностью. Хорошее объяснение здесь: docs.python.org/release/2.5.1/tut/node16.html
Нейт

7
возможный дубликат Math JavaScript сломан?
epascarello

53
@SalmanA: То, что ваша среда выполнения JavaScript скрывает эту проблему от вас, не означает, что я ошибаюсь.
Аарон Дигулла

5
Не согласен с Аароном, есть способы идеально и полностью кодировать 0.1 в двоичном виде. Но IEEE 754 не обязательно определяет это. Представьте себе представление, в котором вы бы закодировали целочисленную часть в двоичном коде, с одной стороны, десятичную часть - с другой стороны, до n десятичных знаков, в двоичном виде, например, как нормальное целое число> 0, и, наконец, положение десятичной точки. , Ну, вы бы идеально представляли 0,1 без ошибок. Кстати, поскольку JS использует конечное число десятичных знаков внутри, разработчики могли бы с таким же успехом кодировать кишки, чтобы не допустить этой ошибки в последних десятичных числах.
Фабьен Хаддади

Ответы:


469

Из руководства с плавающей точкой :

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

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

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

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

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


11
Я заметил твою мертвую ссылку для BigDecimal и, ища зеркало, я нашел альтернативу под названием BigNumber: jsfromhell.com/classes/bignumber
Jacksonkr

4
@ bass-t: Да, но числа с плавающей точкой могут точно представлять целые числа вплоть до длины значения и, в соответствии со стандартом ECMA, это 64-разрядное число с плавающей точкой. Таким образом, он может точно представлять целые числа до 2 ^ 52
Майкл Боргвардт

5
@Karl: десятичная дробь 1/10 не может быть представлена ​​в виде конечной двоичной дроби в базе 2, и это то, чем являются числа Javascript. Так что это на самом деле точно такая же проблема.
Майкл Боргвардт

12
Сегодня я узнал, что даже целые числа имеют проблемы с точностью в JavaScript. Рассмотрим , что console.log(9332654729891549)собственно отпечатки 9332654729891548(т.е. от одного!)
mlathe

12
@mlathe: Doh .. ;P... Между 2⁵²= 4,503,599,627,370,496и 2⁵³= 9,007,199,254,740,992представимые числа являются точно целыми числами . Для следующего диапазона, от 2⁵³до 2⁵⁴, все умножается на2 , так представимых чисел являются даже те , и т.д. С другой стороны , для предыдущего диапазона от 2⁵¹до 2⁵², дистанционирование 0.5, и т.д. Это связано с простым увеличением | уменьшение базы | radix 2 | двоичный показатель в / из 64-битного значения с плавающей запятой (что, в свою очередь, объясняет редко документированное «неожиданное» поведение toPrecision()для значений между 0и 1).
GitaarLAB

126

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

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

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

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


21
Есть причина, почему вы выбрали 12?
qwertymk

18
toPrecisionвозвращает строку вместо числа. Это не всегда может быть желательным.
SStanley

7
parseFloat (1.005) .toPrecision (3) => 1,00
Питер

5
@ user2428118, я знаю, я хотел показать ошибку округления, результат 1,00 вместо 1,01
Питер

9
То, что сказал @ user2428118, может быть недостаточно очевидным: (9.99*5).toPrecision(2)= 50 вместо 49,95, потому что toPrecision считает целое число, а не только десятичные дроби. Затем вы можете использовать toPrecision(4), но если ваш результат> 100, то вам снова не повезло, потому что это позволит первым трем числам и одному десятичному знаку сместить точку и сделать ее более или менее непригодной. Я закончил тем, что использовал toFixed(2)вместо этого
aexl

79

Для математически склонных: 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

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

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


1
Мне нравится этот элегантный обходной путь, но, похоже, он не идеален: jsfiddle.net/Dm6F5/1 Math.a (76.65, 38.45) возвращает 115.10000000000002
nicolallias

3
Math.m (10,2332226616) дает мне «-19627406800», что является отрицательным значением ... Я надеюсь, что должен быть верхний предел - возможно, это является причиной этой проблемы. Пожалуйста, предложите
Шива Komuravelly

1
Все это выглядит великолепно, но, кажется, где-то есть ошибка или две.
MrYellow

5
Очень быстрое решение, он сказал ... сломанное исправление никто никогда не говорил.
Cozzbie

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

49

Вы только выполняете умножение? Если так, то вы можете использовать в своих интересах секрет секретной десятичной арифметики. Это так NumberOfDecimals(X) + NumberOfDecimals(Y) = ExpectedNumberOfDecimals. То есть, если у нас есть, 0.123 * 0.12то мы знаем, что будет 5 десятичных знаков, потому что 0.123имеет 3 десятичных знака и 0.12имеет два. Таким образом, если JavaScript дал нам число, подобное, 0.014760000002мы можем безопасно округлить до 5-го знака после запятой, не боясь потерять точность.


6
... и как получить точное количество знаков после запятой.
линия-орто

7
0,5 * 0,2 = 0,10; Вы все еще можете урезать до 2 десятичных знаков (или меньше). Но никогда не будет числа с каким-либо математическим значением за пределами этого закона.
Нейт Заугг,

3
У вас есть цитата для этого? Также обратите внимание, что то же самое не верно для деления.
Гриффин

3
@NateZaugg вы не можете усечь переполненные десятичные дроби, вы должны округлить сумму, потому что 2090.5 * 8.61 - 17999,205, а в плавающем - 17999,204999999998
Lostfields

3
@Lostfields - Вы правы! Я обновил свой ответ.
Нейт

29

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

Попробуйте javascript-sprintf , вы бы назвали это так:

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

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

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


4
Я думаю, что это самое чистое решение. Если вам действительно не нужен результат, равный 0,02, небольшая ошибка незначительна. Похоже, что важно, чтобы ваш номер отображался хорошо, а не чтобы вы имели произвольную точность.
Лонг Оуянг

2
Для отображения это действительно лучший вариант, для сложных вычислений, проверьте ответ Боргвардта.
Недоступно

4
Но с другой стороны это вернет точно такую ​​же строку, что и yourNumber.toFixed (2).
Роберт

27

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

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

У него хорошая документация, и автор очень старательно реагирует на отзывы.

Этот же автор имеет 2 другие похожие библиотеки:

Big.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>


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

1
По этой ссылке github.com/MikeMcl/big.js/issues/45 bignumber.js -> финансовый decimal.js -> научный big.js -> ???
ви

20
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

4
Я думаю, что это даст ту же проблему в результате. Вы возвращаете число с плавающей запятой, поэтому велика вероятность, что возвращаемое значение также будет «неправильным».
Гертьян

1
Очень умно и полезно +1.
Джонатас Уокер,

18

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

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); 
}

Еа. Да, давайте преобразуем числа в строки для математики с плавающей запятой, а также предложим это в качестве ответа.
Андрей

17

Удивительно, но эта функция еще не была опубликована, хотя у других есть похожие варианты. Это из веб-документов 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'>

ОБНОВЛЕНИЕ: 20 августа 2019 г. Только что заметил эту ошибку. Я полагаю, что это связано с ошибкой точности с плавающей точкой с Math.round ().

precisionRound(1.005, 2) // produces 1, incorrect, should be 1.01

Эти условия работают правильно:

precisionRound(0.005, 2) // produces 0.01
precisionRound(1.0005, 3) // produces 1.001
precisionRound(1234.5, 0) // produces 1235
precisionRound(1234.5, -1) // produces 1230

Fix:

function precisionRoundMod(number, precision) {
  var factor = Math.pow(10, precision);
  var n = precision < 0 ? number : 0.01 / factor + number;
  return Math.round( n * factor) / factor;
}

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


неверный ответ. 10.2 всегда вернет 10.19. jsbin.com/tozogiwide/edit?html,js,console,output
Žilvinas

@ Žilvinas Ссылка JSBin, которую вы разместили, не использует функцию MDN, указанную выше. Я думаю, что ваш комментарий направлен не на того человека.
HelloWorldPeace

13

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

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

Поэтому, по крайней мере, вы должны сделать глобальную переменную / константу с помощью 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 <0,1) * 0,2 <0,02 + EPS).


Вы также можете добавить 0,5 для округления бедного человека: Math.floor (x * PREC_LIM + 0.5) / PREC_LIM
cmroanirgo

Обратите внимание, что, например, Math.floor(-2.1)есть -3. Так что, возможно, используйте, например,Math[x<0?'ceil':'floor'](x*PREC_LIM)/PREC_LIM
MikeM

Почему floorвместо round?
Куинн Комендант

12

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

11

Функция round () на phpjs.org работает хорошо: http://phpjs.org/functions/round

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

2
@jrg По договоренности числа, оканчивающиеся на «5», округляются до ближайшего четного (потому что всегда округление в большую или меньшую сторону приведет к смещению ваших результатов). Следовательно, 4,725 с округлением до двух знаков после запятой действительно должно быть 4,72.
Марк А. Дарем,

9

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

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

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


Будет ли это работать с чем-то вроде 8.22e-8 * 1.3?
Пол Карлтон

0,6 х 3 = 1,8, код, который вы даете результаты 2 ... так что не хорошо.
Зё

@Zyo возвращает 1.8 в этом случае. Как вы это запустили?
Дренаи

Интересно. Вы можете поменять местами операторы умножения и деления, и это тоже работает.
Андрей

9

Обратите внимание, что для общего назначения такое поведение, вероятно, будет приемлемым.
Проблема возникает при сравнении этих значений с плавающей запятой для определения соответствующего действия.
С появлением 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


В моем случае Number.EPSILON было слишком маленьким, в результате чего, например,0.9 !== 0.8999999761581421
Tom

8

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

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

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

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

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


8

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

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


7

проблема

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

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

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

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

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

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

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

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

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


4

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


5
Проблема не в плавающей запятой, а в фиксированной, проблема в двоичной и десятичной.
Майкл Боргвардт

4

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


4

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


4

введите описание изображения здесь

    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

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

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


Вы правы, причина этого заключается в ограниченной точности чисел с плавающей запятой - на <pedant>самом деле, операционная система определила это, чтобы неточные операции с плавающей запятой, что неправильно</pedant>
ловко

3

У меня была проблема с ошибками округления с модом 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);

2

Используйте номер (1.234443). ToFixed (2); это напечатает 1.23

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

2

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: базы 2-64; варианты конфигурации; NaN; бесконечность; точность указана в десятичных разрядах; точность применяется только к делению; базовые префиксы.

decimal.js: базы 2-64; варианты конфигурации; NaN; бесконечность; нецелые степени, exp, ln, log; точность указана в значащих цифрах; точность всегда применяется; случайные числа.

ссылка на подробные сравнения


2

Элегантный, предсказуемый и многоразовый

Давайте разберемся с проблемой элегантным способом многократного использования. Следующие семь строк позволят вам получить желаемую точность с плавающей запятой для любого числа, просто добавив .decimalв конец числа, формулу или встроенную Mathфункцию.

// First extend the native Number object to handle precision. This populates
// the functionality to all math operations.

Object.defineProperty(Number.prototype, "decimal", {
  get: function decimal() {
    Number.precision = "precision" in Number ? Number.precision : 3;
    var f = Math.pow(10, Number.precision);
    return Math.round( this * f ) / f;
  }
});


// Now lets see how it works by adjusting our global precision level and 
// checking our results.

console.log("'1/3 + 1/3 + 1/3 = 1' Right?");
console.log((0.3333 + 0.3333 + 0.3333).decimal == 1); // true

console.log(0.3333.decimal); // 0.333 - A raw 4 digit decimal, trimmed to 3...

Number.precision = 3;
console.log("Precision: 3");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0.01
console.log((0.0008 + 0.0002).decimal); // 0.001

Number.precision = 2;
console.log("Precision: 2");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0.01
console.log((0.0008 + 0.0002).decimal); // 0

Number.precision = 1;
console.log("Precision: 1");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0
console.log((0.0008 + 0.0002).decimal); // 0

Number.precision = 0;
console.log("Precision: 0");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0
console.log((0.008 + 0.002).decimal); // 0
console.log((0.0008 + 0.0002).decimal); // 0

Ура!


2
Если вы решите понизить голосование, укажите хотя бы причину.
Бернесто

1

использование

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

4
Хм ... но обратите внимание, это всегда округляется до 2 десятичных знаков. Это, конечно, вариант, но как насчет расчета 0,55 * 0,55 (поскольку я заранее не знаю точных чисел. Это дало бы 0,3 вместо 0,3025. Конечно, я мог бы тогда использовать Math.round(x*Math.pow(10,4))/Math.pow(10,4);. Округление всегда вариант, но я просто хотел узнать, есть ли какое-нибудь лучшее решение
Юри

10,2 всегда возвращает 10.19 jsbin.com/tozogiwide/edit?html,js,console,output
Žilvinas


1

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

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

1
к сожалению, round_up (4.15,2) => 4.16.
Jrg

1

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

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).

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.