一、背景介绍
在 JavaScript 中,如果数值超过了 Number
类型的最大值(即 Number.MAX_SAFE_INTEGER = 2^53 - 1 = 9007199254740991
),就会出现精度丢失或错误计算的问题。
console.log(9007199254740992 === 9007199254740993); // true(错误)
常见于以下场景:
1. 大数据的计算
如:ID 编码、订单号、时间戳、区块链交易编号等。
2. 格式展示
展示如“1 亿”、“1 千万”之类大数字时,不仅要防止精度问题,还要注意可读性与格式化展示。
3. 用户输入
表单或输入框中,用户可能输入超出 JS 可安全表示范围的数字,前端需要合理限制和校验。
4. 大数据处理与数据分析
尤其是金融、科学计算等领域,需要更高精度的数据类型支持。
二、解决方案
1. 使用
BigInt
ES2020 引入了原生的
BigInt
类型,能够表示任意精度的整数。const big1 = BigInt("9007199254740991000000"); const big2 = BigInt("12345678901234567890"); const result = big1 + big2; console.log(result.toString());
注意:
BigInt
值不能和普通Number
直接混用运算,必须强制转换;
BigInt
结尾要加n
,或者用BigInt()
构造;let a = 9007199254740991n; // OK let b = BigInt("9007199254740991000000"); // OK
2. 使用第三方高精度库
这些库适用于需要进行小数运算、高精度加减乘除的场景(如金融应用)。
a. decimal.js
支持任意精度的小数运算
用法简洁
安装:pnpm add decimal.js
import Decimal from 'decimal.js'; let a = new Decimal("9007199254740991000000.123"); let b = new Decimal("1.456"); console.log(a.plus(b).toString()); // 精确加法
const Decimal = require('decimal.js'); let a = new Decimal('0.1'); let b = new Decimal('0.2'); console.log(a.plus(b).toString()); // "0.3"
b. big.js
更轻量的高精度库,适用于只需要加减乘除的场景
const Big = require('big.js'); let x = new Big('123456789.123456789'); let y = new Big('0.000000001'); console.log(x.plus(y).toString()); // 精确结果
3. 格式化展示大数字(提高可读性)
适合用于 UI 展示层,将大数字格式化为中文数字、货币格式等:
function formatNumber(n) { if (n >= 1e8) return (n / 1e8).toFixed(2) + '亿'; if (n >= 1e4) return (n / 1e4).toFixed(2) + '万'; return n.toLocaleString(); // 加千分位 } formatNumber(123456789); // 输出 "1.23亿" formatNumber(456789); // 输出 "4.57万"
4. 用户输入处理与表单校验
限制输入字符为数字(支持正则)
检查长度,例如不允许超过 20 位整数
对超长数字用字符串或 BigInt 处理,避免精度损失
function isValidLongNumber(input) { return /^\d{1,20}$/.test(input); // 最多 20 位数字 }
三、常见提问方式
问题样式 | 实际目的 |
---|---|
“JavaScript 的 Number 类型最大值是多少?” | 考察对 Number 精度上限的了解 |
“9999999999999999 + 1 等于多少?为什么?” | 考察浮点数精度丢失 |
“你怎么处理超过 Number.MAX_SAFE_INTEGER 的数字?” | 考察 BigInt 或库的使用 |
“你了解 BigInt 吗?什么时候用?” | 考察是否跟进 ES2020 新特性 |
-
理解并能说出
Number.MAX_SAFE_INTEGER
是2^53 - 1
JavaScript 中的
Number
类型基于 IEEE 754 双精度浮点数。浮点数只能安全表示 53 位二进制整数(包括隐含的 1 位尾数)。
所以:
Number.MAX_SAFE_INTEGER = Math.pow(2, 53) - 1 = 9007199254740991
超过这个值会导致整数精度丢失(不可再被唯一准确表示)。
-
能解释为什么 JS 数字存在精度丢失
JavaScript 中的数字存在精度丢失,根本原因是它采用的是 IEEE 754 双精度浮点数格式(64 位)来表示所有的数字(Number 类型),而这种格式不能精确表示所有的十进制数字,特别是一些小数或过大的整数。
常见精度丢失场景:
1. 小数无法精确表示(0.1 + 0.2 ≠ 0.3)
console.log(0.1 + 0.2); // 输出 0.30000000000000004
原因:0.1 和 0.2 在二进制中是无限循环小数,不能精确表示。
2. 大整数精度丢失
console.log(9999999999999999); // 输出:10000000000000000
原因:超出了
Number.MAX_SAFE_INTEGER = 9007199254740991
的表示范围,JavaScript 自动对尾部进行舍入。
-
至少熟悉
BigInt
用法和限制 -
有能力实现字符串大数加法/减法
面试回答:
“JS 中的
Number
类型基于 IEEE 754 双精度浮点数,因此只能安全表示2^53 - 1
,也就是9007199254740991
,即Number.MAX_SAFE_INTEGER
。一旦超过这个值就会出现精度丢失。另外,由于某些十进制小数(比如 0.1)在二进制中无法精确表示,也会导致小数计算不准确。
在需要处理任意大整数的场景中,可以使用
BigInt
类型,但它不能与普通数字混合运算,也不支持小数。如果项目中涉及超大数的加减计算,我也可以通过字符串模拟的方式实现高精度加减法。”
四、面试延伸题(经典)
实现一个函数,接收两个大整数(字符串表示),返回它们的和(也为字符串)。
function bigAdd(a, b) {
let res = '', carry = 0, i = a.length - 1, j = b.length - 1;
while (i >= 0 || j >= 0 || carry) {
const x = i >= 0 ? +a[i--] : 0;
const y = j >= 0 ? +b[j--] : 0;
const sum = x + y + carry;
res = (sum % 10) + res;
carry = Math.floor(sum / 10);
}
return res;
}