JavaScript中精度丢失的处理
前言
在 JavaScript
中,可以使用 toFixed()
方法来限制浮点数的小数位数,但是这种方式只能限制小数点后面的位数,并不能解决精度丢失的问题。比较好的解决方案是使用第三方库,如 BigNumber.js 或 decimal.js,这些库提供了高精度计算的支持,可以避免精度丢失的问题。
BigNumber.js的原理
最小化实现
以下是一个简单的实现,使用一个数组来存储大数,每个元素都是一个 10 进制数,从而避免了数字超出范围的问题:
function BigNumber(num) {
this.parts = [];
num = num.toString();
for (var i = num.length - 1; i >= 0; i -= 9) {
var part = num.slice(Math.max(i - 8, 0), i + 1);
this.parts.push(parseInt(part, 10));
}
}
BigNumber.prototype.toString = function () {
var str = "";
for (var i = this.parts.length - 1; i >= 0; i--) {
str += this.parts[i].toString();
}
return str.replace(/^0+/, "");
};
BigNumber.prototype.plus = function (other) {
var result = new BigNumber(0);
var carry = 0;
for (var i = 0; i < Math.max(this.parts.length, other.parts.length); i++) {
var x = this.parts[i] || 0;
var y = other.parts[i] || 0;
var sum = x + y + carry;
result.parts.push(sum % 1000000000);
carry = Math.floor(sum / 1000000000);
}
if (carry) {
result.parts.push(carry);
}
return result;
};
BigNumber.prototype.times = function (other) {
var result = new BigNumber(0);
var carry = 0;
for (var i = 0; i < this.parts.length; i++) {
for (var j = 0; j < other.parts.length; j++) {
var prod = this.parts[i] * other.parts[j] + carry;
var k = i + j;
while (prod > 0) {
if (k >= result.parts.length) {
result.parts.push(0);
}
prod += result.parts[k];
result.parts[k] = prod % 1000000000;
prod = Math.floor(prod / 1000000000);
k++;
}
carry = 0;
}
}
return result;
};
这个实现将大数分成了每个 9 位一组,每个元素都是一个 10 进制数,使用数组来存储每个组,这样就避免了数字超出范围的问题。同时,该实现还支持加法和乘法运算,其他运算也可以类似实现。
使用
var a = new BigNumber("12345678901234567890");
var b = new BigNumber("98765432109876543210");
console.log(a.plus(b).toString()); // "111111111111111111100"
console.log(a.times(b).toString()); // "12193263113702179522395810392542705102900"
decimal.js的原理
最小化实现
decimal.js
的实现原理是基于 BigInt,这是一个 ES2020 引入的内置类型,用于处理超大整数。因此,decimal.js 可以直接利用 BigInt 实现数字的精确计算,从而避免了精度丢失的问题。
class Decimal {
constructor(value) {
this.value = BigInt(value);
}
plus(other) {
return new Decimal(this.value + BigInt(other));
}
toString() {
return this.value.toString();
}
}
这里定义了一个 Decimal
类,它包含一个 value
属性,用来存储 BigInt
类型的数值。类中定义了两个方法:plus
用于执行加法运算,返回一个新的 Decimal
实例;toString
用于将 Decimal
转换为字符串表示。
使用
var a = new Decimal("12345678901234567890");
var b = new Decimal("98765432109876543210");
console.log(a.plus(b).toString()); // "111111111111111111100"
实现差异
虽然 BigNumber.js
和 decimal.js
都是用来解决数字精度问题的库,但它们的实现原理不同。
decimal.js
的实现原理是基于 BigInt
,这是一个 ES2020 引入的内置类型,用于处理超大整数。因此,decimal.js 可以直接利用 BigInt 实现数字的精确计算,从而避免了精度丢失的问题。
相比之下,BigNumber.js
的实现原理则是将数字拆分成多个部分存储,每个部分都是一个 10 进制数
。这种数据结构的优点是可以处理超过 Number 类型表示范围的数字,但它的性能可能不如直接使用 BigInt。
在使用这两个库时,需要根据具体情况选择。如果只需要处理整数,且运行环境支持 BigInt,则可以考虑使用 decimal.js。如果需要处理超过 Number 范围的数字,或者运行环境不支持 BigInt,则可以使用 BigNumber.js。