解决 JS 大整数精度丢失?一文读懂 BigInt 的底层逻辑与实战规则

一、BigInt 出现的背景

要理解 BigInt 的底层原理,首先需要明确它解决的核心问题 ------Number 类型的精度瓶颈。

JavaScript 中的 Number 基于 IEEE 754 双精度浮点数标准(64 位)设计,其存储结构分为三部分:

  • 符号位(1 位):表示数字的正负;

  • 指数位(11 位):控制数字的量级,范围为 -1023 ~ 1024;

  • 尾数位(52 位):存储数字的有效精度,最多可表示 2^53 - 1 个不同的整数(即 9007199254740991)。

尾数部分只有 52 位,这意味着它只能精确地表示 53 位(包括一个隐含的开头的 1)的整数 1 。当一个整数的绝对值超过 2^53 - 1 时,JavaScript 的 Number 类型就无法精确地表示所有的整数了。

具体来说,为了表示更大的数字,浮点数会通过调整指数来"移动"小数点,但尾数部分的位数是固定的。当数字太大时,尾数无法存储所有的有效数字,导致一些低位的数字被舍弃,从而造成精度丢失。

例如: Number.MAX_SAFE_INTEGER + 1 可以正确表示,但 Number.MAX_SAFE_INTEGER + 2 就会出现精度问题,结果仍然是 Number.MAX_SAFE_INTEGER + 1 4 。

隐含的开头的 1 :

由于在二进制中,任何非零的规范化浮点数的尾数总是以 1. 开头(例如 1.01101 * 2^5 ),这个开头的 1 是 固定不变 的。既然它总是 1 ,那么在存储的时候就没有必要显式地存储它。通过不存储这个隐含的 1 ,而是假定它总是存在,我们就可以将尾数部分的存储空间 多用于存储一位小数部分 。对于 52 位的尾数存储空间来说,加上这个隐含的 1 ,实际上就相当于有了 53 位的精度。

为解决 "大整数精确表示" 这一痛点,BigInt 应运而生。它不依赖浮点数标准,BigInt 采用变长字节数组(Variable-Length Byte Array)作为底层存储结构。它不限制存储长度,会根据整数的实际大小动态分配存储空间。例如,存储一个 100 位的整数时,BigInt 会自动分配足够的字节来容纳所有数字位,确保每一位都能被精确存储。这种动态适配的存储方式,从根本上打破了 Number 的精度限制,让 BigInt 能够轻松处理任意大小的整数,无论是几百位还是几千位的超大整数,都能保持完整的精度。

二、BigInt 的存储结构:如何实现 "无限精度"?

ECMAScript规范中定义的BigInt方法:tc39.es/proposal-bi...

BigInt在V8中通过 BigIntBase 类的实例存储,主要包含三个关键部分:

位域存储符号和长度

c++ 复制代码
/** 存储符号和长度的位域,使用原子操作确保并发安全 
*/
std::atomic_uint32_t bitfield_;

这个32位的原子整数巧妙地存储了两个关键信息:

  • 1个位用于存储 符号 (SignBits):0表示正数,1表示负数
  • 30个位用于存储 长度 (LengthBits):表示BigInt包含的digit数量
    位域设计允许并发读取长度,并通过原子操作确保线程安全。

灵活数组成员存储数字数据

c++ 复制代码
/** 灵活数组成员,存储BigInt的实际数字数据 */
FLEXIBLE_ARRAY_MEMBER(UnalignedValueMember<digit_t>, raw_digits);

这是BigInt存储的核心,一个可变长度的数组,其中:

  • digit_t 是基本数字类型,定义为 uintptr_t ,在64位平台上为64位,32位平台上为32位
  • 采用 最低有效位优先的方式存储数字
  • 每个digit包含 kDigitBits = kDigitSize * kBitsPerByte 个位

可选的内存对齐填充

c++ 复制代码
/** 在需要对齐的平台上添加的填充 */
#ifdef BIGINT_NEEDS_PADDING
    char padding_[4];
#endif

在某些64位平台上,为确保数字数组正确对齐(按8字节)而添加的填充。

2.4 数字表示方式

BigInt的数值表示遵循以下原则:

  1. 零值表示 :当 length() == 0 时,表示零值,此时符号位被忽略
  2. 负数表示 :通过符号位标记,内部数字数据始终以绝对值形式存储
  3. 位数限制 :BigInt支持的最大位数约为10亿位( kMaxLengthBits = 1 << 30 )
  4. 平台优化 :利用平台原生指针大小作为数字单位,优化性能和内存使用

Bigint的使用

运算规则

BigInt 作为专为大整数设计的类型,其运算规则围绕 "避免精度丢失" 和 "类型安全" 展开,与 Number 存在明显差异,核心约束主要体现在位运算限制混合运算限制两方面:

位运算限制:不支持无符号右移(>>>)

由于 BigInt 可表示任意长度的整数,且默认支持正负整数,而无符号右移(>>>)会将数值视为 "无符号数"(即忽略符号位,仅按绝对值处理),这与 BigInt 兼顾正负、无长度限制的设计理念冲突。因此,BigInt 明确不支持 >>> 操作,若强行使用会直接抛出语法错误。

混合运算限制:禁止 BigInt 与 Number 直接运算

为避免隐式转换导致的精度丢失,BigInt 与 Number 不能直接进行加减乘除等算术运算,必须先将其中一种类型显式转换为另一种类型。若直接混合运算,JavaScript 会抛出 TypeError,明确拒绝这种 "不安全" 的操作。

类型转换

BigInt 的类型转换规则远严于 Number,不支持隐式转换,所有跨类型操作均需显式处理,以此避免意外的精度丢失或逻辑错误。而 Number 则允许灵活的隐式转换,虽便捷但存在潜在风险。

  • 与 Number 比较:=== 会同时校验值和类型,因此 10n === 10 结果为 false;== 虽会尝试类型转换,但仍需注意精度(仅当 BigInt 在 Number 安全范围内时,10n == 10 才为 true)。
  • 转换为字符串:无隐式转换,需手动调用 toString() 方法,否则会抛出错误。
  • 转换为布尔值:仅 0n 转换为 false,其他 BigInt 值均转换为 true,但需显式使用 Boolean() 函数,无隐式转换(如 if (10n) 虽能正常判断,但本质是引擎对 "真值" 的判断,非类型转换)。
js 复制代码
const bigVal = 10n;
const zeroBigVal = 0n;

// 1. 与 Number 比较
console.log(bigVal === 10); // 结果:false(类型不同)
console.log(bigVal == 10);  // 结果:true(值相等且在安全范围内,隐式转换生效)
console.log(9007199254740993n == 9007199254740993); // 结果:false(BigInt 超出安全范围,转换后值不相等)

// 2. 转换为字符串
try {
  const str1 = '数值:' + bigVal; // 无隐式转换,报错
} catch (error) {
  console.error(error.message); // 输出:"Uncaught TypeError: Cannot convert a BigInt value to a string implicitly"
}
const str2 = '数值:' + bigVal.toString(); 
console.log(str2); // 结果:"数值:10"(显式转换成功)

// 3. 转换为布尔值
console.log(Boolean(bigVal));    // 结果:true
console.log(Boolean(zeroBigVal));// 结果:false
console.log(if (bigVal));        // 结果:true(引擎真值判断,非类型转换)
相关推荐
刺客_Andy3 小时前
React 第四十六节 Router中useInRouterContext的使用详细介绍及注意事项
前端·javascript·react.js
刺客_Andy3 小时前
React 第四十四节Router中 usefetcher的使用详解及注意事项
前端·javascript·react.js
该用户已不存在3 小时前
我的Python工具箱,不用加班的秘密
前端·后端·python
刺客_Andy3 小时前
React 第四十五节 Router 中 useHref() Hook的使用详解及注意事项
前端·javascript·react.js
好好好明天会更好3 小时前
Vue2中页面数据响应的问题
前端·javascript·vue.js
文心快码BaiduComate3 小时前
新手如何高效使用 Zulu 智能体?从入门到提效全指南
前端·后端
Renounce3 小时前
【Android】从早期 MVC 到现代 MVVM 的架构变迁
前端
lbr3 小时前
uniapp封装图片上传组件,使用v-model双向绑定
前端
拉不动的猪3 小时前
回顾前端项目打包时--脚本引入时机与环境类型的判断问题
前端·vue.js·面试