Начнем с примера
var number1 = 10000000000000000000000000 + 11111111111111111111111111 //理论上number1的值应该是21111111111111111111111111(javascript中会表示为科学计数法:2.111111111111111e+25)
var number2 = 21111111111111111111111000
console.log(number1 === number2) //true
Это не нужно вычислять, и вы видите, что результат вычисления неверен.Последние несколько цифр все должны быть 1, но почему вы получаете неправильное значение?
Проблема потери точности JavaScript Number
потому чтоJavaScriptизNumberТип - следоватьIEEE 754каноническое представление, а это означает, чтоJavaScriptЧисло, которое может быть представлено точно, ограничено.JavaScriptНаибольшее целое число, которое может быть точным до одной цифры, — это 9007199254740992, что равно 2 в 53-й степени.Если этот диапазон превышен, точность будет потеряна, что приведет кJavaScriptО размере судить нельзя, поэтому будут происходить следующие явления:
Math.pow(2, 53); // 9007199254740992
Math.pow(2, 53) === Math.pow(2, 53) + 1; // true
9007199254740992 === 9007199254740992 + 1; // true
Это видно из картинки нижеJavaScript NumberВерхние и нижние границы, которые могут быть представлены точно:
решение
Тогда при добавлении двух данных один или оба данных превышают этот диапазон точности, и результат прямого сложения будет неточным.Как решить эту проблему?
Ссылаясь на одну из часто используемых схем в Интернете, следуетNumberПеревести вString,ПотомStringПеревести вArray, и обратите внимание на то, чтобы дополнить более короткие массивы, добавить их длины один за другим, чтобы получить новый массив, а затем преобразовать новый массив, образованный суммой, в числа. Ниже приведен код реализации:
function sumString(a, b) {
a = '0' + a;
b = '0' + b; //加'0'首先是为了转为字符串,而且两个数相加后可能需要进位,这样保证了和的长度就是a、b中长的那个字符的长度
var arrA = a.split(''), //将字符串转为数组
arrB = b.split(''),
res = [], //相加结果组成的数组
temp = '', //相同位数相加的值
carry = 0, //同位数相加结果大于等于10时为1,否则为0
distance = a.length - b.length, //计算两个数字字符串的长度差
len = distance > 0 ? a.length : b.length; //和的长度
// 在长度小的那个值前加distance个0,保证两个数相加之前长度是想等的
if(distance > 0) {
for(let i = 0; i < distance; i++) {
arrB.unShift('0');
}
}else{
for(let i = 0; i < distance; i++) {
arrA.unShift('0');
}
}
// 现在得到了两个长度一致的数组,需要做的就是把他们想通位数的值相加,大于等于10的要进一
// 最终得到一个和组成的数组,将数组转为字符串,去掉前面多余的0就得到了最终的和
for(let i = len-1; i >= 0; i--) {
temp = Number(arrA[i]) + Number(arrB[i]) + carry;
if(temp >= 10) {
carry = 1;
res.unshift((temp + '')[1])
}
else{
carry = 0;
res.unshift(temp)
}
}
res = res.join('').replace(/^0/, '');
console.log(res);
}
Вопрос про 0.1+0.2 !== 0.3
Как упоминалось ранее, вJavaScript, используя стандарт с плавающей запятойIEEE 754При представлении чисел, при представлении десятичных дробей, некоторые числа не могут быть полностью преобразованы при преобразовании в двоичные, например 0,3.Преобразование в двоичное представляет собой очень длинный цикл числа, который превышаетJavaScriptДиапазон, который может быть представлен, поэтому он приблизительно равен 0,30000000000000004.
Это самая большая проблема с двоичными числами с плавающей запятой (не только JavaScript, но и все языки, соответствующие спецификации IEEE 754).
Как судить, хотят ли две ценности ждать
Здесь мы собираемся представить ES6 вNumberКрошечная константа, добавленная к объектуNumber.EPSILON. Он представляет собой разницу между 1 и наименьшим числом с плавающей запятой, большим 1, равным 2 в степени -52.
Number.EPSILON на самом деле является наименьшей точностью, которую может представить JavaScript. Если ошибка меньше этого значения, можно считать, что она не имеет смысла, то есть ошибки нет.
Таким образом, вы можете использовать это, чтобы определить, хотят ли два числа с плавающей запятой ждать:
function numIsEqual(lef, rig) {
return Math.abs(lef - rig) < Number.EPSILON
}
Если вы считаете совместимость браузера, вы можете написать это так:
function numIsEqual(lef, rig) {
let EPSILON = Number.EPSILON ? Number.EPSILON : Math.pow(2,-52)
return Math.abs(lef - rig) < EPSILON
}
Конечно, если мы используем десятичные знаки каждый день, обычно достаточно быть с точностью до двух знаков после запятой, проблем не будет слишком много, но мы должны помнить об использованииtoFied()Метод сохраняет количество цифр после запятой и не может напрямую принимать вычисляемое значение, чтобы избежать ошибок.
Хвостик
оJavaScriptО проблеме представления данных в Geek Time я также слышал в Geek Time «Introduction to Computer Composition Principles», где подробно объясняется, почему числа с плавающей запятойIEEE 754Стандарт может представлять только ограниченное число, но я едва могу понять такую низкоуровневую вещь, ха-ха.