Подтвердить что ты не робот

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

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

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

Ответ 1

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

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

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

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

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

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

Ответ 2

Мне нравится решение Pedro Ladaria и используйте что-то подобное.

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

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

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

Ответ 3

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

Ответ 4

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

Ответ 5

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

Попробуйте javascript-sprintf, вы бы назвали это следующим образом:

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

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

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

Ответ 6

Я нахожу BigNumber.js отвечает моим потребностям.

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

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

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

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>

Ответ 7

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

Ответ 8

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

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

Ответ 9

Чтобы умножить a * b, где a = 5 и b = 0,0001:

  • Сдвиг десятичной точки a до тех пор, пока номер не станет int: 0.0001 1
  • Сдвиг десятичной точки b справа на то же количество мест: 5 50000
  • Умножение: 1 * 50000
  • Сдвиг десятичной точки результата, оставленного таким же количеством мест: 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);
    

Ответ 10

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

Числовые ошибки накапливаются с каждой последующей операцией, и если вы не срежете ее раньше, она просто будет расти. Числовые библиотеки, которые представляют результаты, которые выглядят чистыми, просто отсекают последние 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).

Ответ 11

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

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

Ответ 12

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

Ответ 13

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

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

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

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

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

Ответ 14

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

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

Ответ 15

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

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

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

Ответ 16

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

Ответ 17

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

Ответ 18

Проблема

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

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

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

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

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

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

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

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

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

Ответ 19

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

Ответ 20

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

Ответ 21

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

Ответ 22

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

Ответ 23

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

Ответ 24

Используйте

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

Ответ 25

не элегантный, но выполняет работу (удаляет завершающие нули)

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

Ответ 26

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

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

Ответ 27

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

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

Ответ 28

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.

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

Ответ 29

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

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

Ответ 30

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

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