JavaScript 数字不靠谱?大数小数精度问题与解决方案

60 阅读2分钟

JavaScript 中所有数字都是 Number 类型(IEEE 754 双精度浮点数) ,64-bit 浮点数表示:

  • 1 bit 符号
  • 11 bit 指数
  • 52 bit 小数位(有效位)

这导致 大数和小数都可能丢精度

1.小数精度问题

原因

  • 十进制小数无法精确表示为二进制
  • 内存中只能存储近似值

示例

0.1 + 0.2 // 0.30000000000000004
0.1 + 0.7 // 0.7999999999999999

常见场景

  • 金额计算
  • 百分比或比例
  • 累加循环

解决方案

  1. 整数化计算
(0.1*100 + 0.2*100) / 100 // 0.3 ✅
  1. 精度库:Big.js、Decimal.js
import Big from 'big.js';
Big(0.1).plus(0.2).toString() // "0.3"
  1. 展示用 toFixed 或允许误差比较
Math.abs(a-b) < 1e-10

2.大整数精度问题

原因

  • JS Number 最大安全整数:Number.MAX_SAFE_INTEGER = 2^53 - 1 ≈ 9e15
  • 超过该范围会四舍五入丢精度

示例

9007199254740992  // 正确
9007199254740993  // 变成 9007199254740992 ❌

常见场景

  • 数据库自增 ID
  • 雪花 ID
  • 时间戳(毫秒)
  • 订单号、流水号

Network 上看到丢精度的原因

  1. Response 标签显示原始文本,正确

  2. Previewres.json() 解析时,浏览器自动 JSON.parse 转 Number

    • 超过 2^53-1 的整数丢精度
    • Network Preview 显示的就是解析后的对象

解决方案

  1. 后端返回字符串(推荐)
{"id":"9223372036854775807"}
  1. 前端用 text() 获取原始文本
const rawText = await fetch("/api/big-number").then(res => res.text());
  1. JSON-bigint 安全解析
const data = JSONbig.parse(rawText);
const idBigInt = BigInt(data.id.toString());

3.对比总结

类型触发条件示例解决方法
小数十进制无法精确转二进制0.1 + 0.2 → 0.30000000000000004整数化计算 / 精度库 / toFixed / 允许误差比较
大整数> 2^53-19223372036854775807 → 9223372036854775800字符串 / BigInt / JSON-bigint

4.开发实践建议

  1. 金额、ID、时间戳等精度关键数据,避免直接使用 Number
  2. 后端大整数统一返回字符串,前端解析安全
  3. 小数运算使用整数化或精度库,避免累积误差
  4. 数值比较不要直接用 === 浮点数,使用误差范围
  5. Network Preview 上看到丢失精度 是浏览器解析造成,不代表后端错误

一句话总结

大数和小数精度问题都源自 Number 类型的表示限制,前端必须使用字符串、BigInt 或精度库保证精确值。