Как избежать проблем с использованием странных десятичных вычислений в JavaScript - PullRequest
31 голосов
/ 18 февраля 2011

I только что прочитал на MDN , что одна из особенностей обработки JS чисел из-за того, что все это «значения IEEE 754 с 64-разрядным форматом двойной точности» , это когда вы сделать что-то вроде .2 + .1, вы получите 0.30000000000000004 (это то, что читается в статье, но я получаю 0.29999999999999993 в Firefox). Поэтому:

(.2 + .1) * 10 == 3

оценивается как false.

Кажется, это было бы очень проблематично. Итак, что можно сделать, чтобы избежать ошибок из-за неточных десятичных вычислений в JS?

Я заметил, что если вы сделаете 1.2 + 1.1, вы получите правильный ответ. Так стоит ли вам избегать математических операций со значениями меньше 1? Потому что это кажется очень непрактичным. Есть ли какие-либо опасности для математики в JS?

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

Ответы [ 6 ]

29 голосов
/ 18 февраля 2011

1,2 + 1,1 может быть в порядке, но 0,2 + 0,1 может быть не в порядке.

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

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

(0.2 + 0.1).toFixed(4) === 0.3.toFixed(4) // true

или вы можете преобразовать их в числа после этого:

+(0.2 + 0.1).toFixed(4) === 0.3 // true

или используя Math.round:

Math.round(0.2 * X + 0.1 * X) / X === 0.3 // true

где X - это некоторая степень 10, например 100 или 10000 - в зависимости от того, какая точность вам нужна.

Или вы можете использовать центы вместо долларов при подсчете денег:

cents = 1499; // $14.99

Тото, как вы работаете только с целыми числами, и вам не нужно беспокоиться о десятичных и двоичных дробях.

2017 Обновление

Ситуация представления чисел в JavaScript может быть немного сложнеечем раньше. раньше было то, что у нас был только один числовой тип в JavaScript:

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

См. также

Другой соответствующий ответ с некоторымипримеры того, как обрабатывать вычисления:

21 голосов
/ 18 февраля 2011

В подобных ситуациях вы бы предпочли использовать оценку эпсилона.

Что-то вроде (псевдокод)

if (abs(((.2 + .1) * 10) - 3) > epsilon)

, где эпсилон - что-то вроде 0,00000001, или с какой бы точностью вы ни указалиrequire.

Быстро прочитайте Сравнение чисел с плавающей запятой

9 голосов
/ 23 ноября 2012
(Math.floor(( 0.1+0.2 )*1000))/1000

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

.1+.2 =
0.30000000000000004

после предложенной операции вы получите 0.3 Но любое значение между:

0.30000000000000000
0.30000000000000999

также будет считаться 0,3

5 голосов
/ 27 сентября 2013

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

Это округление может привести к неожиданным ответам:

Math.floor(Math.log(1000000000) / Math.LN10) == 8 // true

Это всего порядка . Это ошибка округления!

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

Это будет частью стандарта EcmaScript в ближайшем будущем. А пока вы можете рассчитать его следующим образом:

function epsilon() {
    if ("EPSILON" in Number) {
        return Number.EPSILON;
    }
    var eps = 1.0; 
    // Halve epsilon until we can no longer distinguish
    // 1 + (eps / 2) from 1
    do {
        eps /= 2.0;
    }
    while (1.0 + (eps / 2.0) != 1.0);
    return eps;
}

Затем вы можете использовать его, что-то вроде этого:

function numericallyEquivalent(n, m) {
    var delta = Math.abs(n - m);
    return (delta < epsilon());
}

Или, поскольку ошибки округления могут накапливаться тревожно, вы можете использовать delta / 2 или delta * delta вместо delta.

3 голосов
/ 27 июля 2017

Существуют библиотеки, которые пытаются решить эту проблему , но если вы не хотите включать одну из них (или не можете по какой-то причине, например, работать внутри GTM-переменной * 1004) *) тогда вы можете использовать эту маленькую функцию, которую я написал:

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

var a = 194.1193;
var b = 159;
a - b; // returns 35.11930000000001
doDecimalSafeMath(a, '-', b); // returns 35.1193

Вот функция:

function doDecimalSafeMath(a, operation, b, precision) {
    function decimalLength(numStr) {
        var pieces = numStr.toString().split(".");
        if(!pieces[1]) return 0;
        return pieces[1].length;
    }

    // Figure out what we need to multiply by to make everything a whole number
    precision = precision || Math.pow(10, Math.max(decimalLength(a), decimalLength(b)));

    a = a*precision;
    b = b*precision;

    // Figure out which operation to perform.
    var operator;
    switch(operation.toLowerCase()) {
        case '-':
            operator = function(a,b) { return a - b; }
        break;
        case '+':
            operator = function(a,b) { return a + b; }
        break;
        case '*':
        case 'x':
            precision = precision*precision;
            operator = function(a,b) { return a * b; }
        break;
        case '÷':
        case '/':
            precision = 1;
            operator = function(a,b) { return a / b; }
        break;

        // Let us pass in a function to perform other operations.
        default:
            operator = operation;
    }

    var result = operator(a,b);

    // Remove our multiplier to put the decimal back.
    return result/precision;
}
3 голосов
/ 18 февраля 2011

Вам нужно немного контроля ошибок.

Сделайте метод двойного сравнения:

int CompareDouble(Double a,Double b) {
    Double eplsilon = 0.00000001; //maximum error allowed

    if ((a < b + epsilon) && (a > b - epsilon)) {
        return 0;
    }
    else if (a < b + epsilon)
        return -1;
    }
    else return 1;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...