Как бороться с точностью чисел с плавающей точкой в ​​JavaScript? - PullRequest
511 голосов
/ 22 сентября 2009

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

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

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

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

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

Ответы [ 34 ]

0 голосов
/ 11 декабря 2017

Для обработки произвольного плавающего числа:

function shorten(num) {
    num += 0.000000001;// to deal with "12.03999999997" form
    num += '';
    return num.replace(/(\.\d*?)0{5,}\d+$/, '$1') * 1;
}

console.log(1.2+1.9===1.3+1.8);// false
console.log(shorten(1.2+1.9)===shorten(1.3+1.8));// true
0 голосов
/ 14 сентября 2012

У меня есть обходной путь здесь. Например, умножение на 10E ^ x не работает с 1.1.

function sum(a,b){
    var tabA = (a + "").split(".");
    var tabB = (b + "").split(".");
    decA = tabA.length>1?tabA[1].length:0;
    decB = tabB.length>1?tabB[1].length:0;
    a = (tabA[0]+tabA[1])*1.0;
    b = (tabB[0]+tabB[1])*1.0;
    var diff = decA-decB;
    if(diff >0){
        //a has more decimals than b
        b=b*Math.pow(10,diff);
        return (a+b)/Math.pow(10,decA);
    }else if (diff<0){
        //a has more decimals than b
        a=a*Math.pow(10,-diff);
                return (a+b)/Math.pow(10,decB);
    }else{
        return (a+b)/Math.pow(10,decA);
    }       
}

страшно, но работает :) 1004 *

0 голосов
/ 06 февраля 2019

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

class Decimal {
  constructor(value = 0, scale = 4) {
    this.intervalValue = value;
    this.scale = scale;
  }

  get value() {
    return this.intervalValue;
  }

  set value(value) {
    this.intervalValue = Decimal.toDecimal(value, this.scale);
  }

  static toDecimal(val, scale) {
    const factor = 10 ** scale;
    return Math.round(val * factor) / factor;
  }
}

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

const d = new Decimal(0, 4);
d.value = 0.1 + 0.2;              // 0.3
d.value = 0.3 - 0.2;              // 0.1
d.value = 0.1 + 0.2 - 0.3;        // 0
d.value = 5.551115123125783e-17;  // 0
d.value = 1 / 9;                  // 0.1111

Конечно, при работе с десятичной дробью есть предостережения:

d.value = 1/3 + 1/3 + 1/3;   // 1
d.value -= 1/3;              // 0.6667
d.value -= 1/3;              // 0.3334
d.value -= 1/3;              // 0.0001

В идеале вы хотели бы использовать высокую шкалу (например, 12), а затем конвертировать ее, когда вам нужно представить ее или сохранить где-нибудь. Лично я экспериментировал с созданием UInt8Array и пытался создать значение точности (очень похожее на тип десятичного числа SQL), но, поскольку Javascript не позволяет перегрузить операторы, он просто становится немного утомительным из-за невозможности использования базовых математических операторов (+, -, /, *) и использование вместо них таких функций, как add(), substract(), mult(). Для моих нужд это того не стоит.

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

0 голосов
/ 27 июня 2013

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

// using max number of 0s = 8, maximum remainder = 4 digits
x = 0.1048000000000051
parseFloat(x.toString().replace(/(\.[\d]+[1-9])0{8,}[1-9]{0,4}/, '$1'), 10)
// = 0.1048
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...