Проблема потери точности JS

JavaScript

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

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

Некоторые числа не могут быть ограничены из-за бинарной реализации и количества бинарных реализаций компьютера. Так же как нельзя ограничивать некоторые неразумные числа, например окружность 3,1415926..., 1,3333... и т.д. JS следует спецификации IEEE 754, используя хранилище двойной точности (Double Precision), занимающее 64 бита.

  • Биты символов, используемые для представления
  • 11 бит используются для представления экспоненты
  • 52 бита для мантиссы

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

0.1 >> 0.0001 1001 1001 1001…(1001无限循环)
0.2 >> 0.0011 0011 0011 0011…(0011无限循环)

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

Та же проблема существует с большими целыми числами, потому что мантисса имеет только 52 бита, поэтому самое большое целое число, которое может быть точно представлено в JS, — это Math.pow(2, 53), что составляет 9007199254740992 в десятичном виде.

9007199254740992     >> 10000000000000...000 // 共计 53 个 0
9007199254740992 + 1 >> 10000000000000...001 // 中间 52 个 0
9007199254740992 + 2 >> 10000000000000...010 // 中间 51 个 0
9007199254740992 + 1 // 丢失        //9007199254740992 
9007199254740992 + 2 // 未丢失      //9007199254740994   
9007199254740992 + 3 // 丢失        //9007199254740992 
9007199254740992 + 4 // 未丢失      //9007199254740996  

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

В Java, C и C++ существуют специальные методы обработки значений с плавающей запятой, например, для решения этой проблемы с плавающей запятой используется тип Java BigDecimal.

Распространенные ошибки

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

0.1 + 0.2 != 0.3 // true

Вычисление и сравнение больших целых чисел:

Обычные целочисленные арифметические сравнения менее подвержены ошибкам, если расчет не выходит за рамки Math.pow(2, 53)

9999999999999999 == 10000000000000001 // true

Преобразование многозначных символов:

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

parseFloat(0.9);    //0.9
parseFloat(9999999999999999.9)    //10000000000000000
parseInt("9999999999999999");    //10000000000000000
parseFloat(9.999999999999999);   //10

toFixed не округляет:

var num = 1.335;
num.toFixed(2);   //1.33

решение

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

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

//浮点数转换为整数
function toInt(num){
    var rel = {};
    var str,pos,len,times;
    str = (num < 0) ? -num + '' : num + ''; 
    pos = str.indexOf('.');
    len = str.substr(pos+1).length;
    times = Math.pow(10, len+1); //补充:当小数位数较多时,避免出错,所以多扩大一倍,提高精度
    rel.times = times;
    rel.num =  num;
    return rel;
}

//计算过程
function operate(a,b,op){
    var d1 = toInt(a);
    var d2 = toInt(b);
    var max = d1.times > d2.times ? d1.times : d2.times;
    var rel;
    switch(op){
        case "+" :
            rel = (d1.num * max + d2.num * max) / max;
            break;
        case "-" :
            rel = (d1.num * max - d2.num * max) / max;
            break;
        case "*" :
            rel = ((d1.num * max) * (d2.num * max)) / (max * max);
            break;
        case "/" :
            rel = (d1.num * max) / (d2.num * max);
            break;
    }
    return rel;
}

var rel = operate(0.3,0.1,"+");   //0.4

Преобразование многозначных чисел:

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

Ремонт ТОФИКС:

function toFixed(num, s) {
    var times = Math.pow(10, s)
    var des = num * times + 0.5
    des = parseInt(des, 10) / times
    return des + ''
}