谈谈 js 小数加法精度问题

一、问题现象

我们从一个最常见的例子说起:

js 复制代码
console.log(0.1 + 0.2); 

这行代码:0.1 加 0.2,应该是等于0.3的,但实际上并不是

js 复制代码
console.log(0.1 + 0.7 === 0.8); 

这行代码:0.1 加 0.7,应该是等于0.8的,打印应该为true,实际并不是

这种微小的偏差看似没什么大问题,但如果你用它来作条件判断

js 复制代码
console.log(0.1 + 0.7 === 0.8); // 输出:false

这就可能直接导致业务逻辑错误,是个严重的bug。

二、为什么会出现精度丢失

这个问题的根源在于计算机内部使用二进制来表示数字,而许多十进制小数无法在二进制中精确表示。

在二进制中,0.1 实际上是一个无限循环的小数,就像十进制中 1/3 = 0.333... 一样。IEEE 754 浮点数标准中,只能保留有限的位数,因此这种无限循环的二进制小数会被截断,从而造成精度损失。

当我们写下 0.1 时,计算机实际存储的是最接近 0.1 的二进制表示,这个值略微不等于真正的 0.1。就像我们无法用十进制精确表示 1/3 一样,计算机也无法用二进制精确表示某些十进制小数。

简单来说:

  • 十进制的 0.1 在二进制中表示为:0.00011001100110011...(循环)
  • 二进制截断后转换为十进制,就是我们看到的 0.10000000000000000555...

多个这种带误差的数相加,自然就出现了结果偏差。

三、常见解决方案及代码示例

1. 使用 toFixed toPrecision 限制精度

js 复制代码
let result = (0.1 + 0.2).toFixed(2); // '0.30' 字符串
console.log(result); // 输出:'0.30'
console.log(Number(result)); // 输出:0.3

优点: 简单直观
缺点: 返回的是字符串,需手动转为数字;四舍五入可能带来新的误差


2. 放大整数再计算,再缩小回去

这是金融类系统中常用的思路。比如先将 0.1 和 0.2 变为 10 和 20,相加后再除以 10。

js 复制代码
function add(a, b) {
  let base = Math.pow(10, Math.max(decimalLength(a), decimalLength(b)));
  return (a * base + b * base) / base;
}

function decimalLength(num) {
  const parts = num.toString().split('.');
  return parts[1] ? parts[1].length : 0;
}

console.log(add(0.1, 0.2)); // 输出:0.3

优点: 精度高,适用于加减法
缺点: 实现稍复杂,乘除法需额外处理


3. 使用第三方库(推荐)

对于需要高精度计算的场景,建议使用成熟库来处理,比如:

✅ decimal.js(体积更大 几十KB)
js 复制代码
npm install decimal.js
js 复制代码
import Decimal from 'decimal.js';

const result = new Decimal(0.1).plus(0.2);
console.log(result.toNumber()); // 输出:0.3
✅ big.js 体积更小 几KB)
js 复制代码
npm install big.js
js 复制代码
import Big from 'big.js';

const result = new Big(0.1).plus(0.2);
console.log(result.toNumber()); // 输出:0.3
两者对比
特性 decimal.js big.js
精度控制 支持任意精度,功能更丰富 精度控制简单,但够用
API 接口 .plus(), .minus(), .times() 同样使用 .plus()等一致方法名
文件体积 较大(几十 KB) 更轻量(几 KB)
功能 支持三角函数、开方、对数等复杂运算 更专注于基本加减乘除
精度表现 非常高 同样可靠

如果只是简单加减,没有复杂的运算,推荐big.js,因为体积更小。

总结

js 中小数相加产生精度丢失,并非语言缺陷,而是由于底层 IEEE 754 双精度浮点数的表示机制所致。0.1 + 0.2 !== 0.3 的问题,在日常开发中常常被忽视,却可能在关键业务场景中引起问题,从而出现bug,所以在日常开发中掌握其解决办法,可以提前避免相关问题。

相关推荐
东方老白12 小时前
iOS 面试题
前端
hunterandroid12 小时前
Compose LazyColumn 性能优化:从卡顿到流畅的大列表治理
前端
sakiko_12 小时前
Swift学习笔记39-实战注意事项
前端·笔记·学习·swift
海带紫菜菠萝汤12 小时前
MSE (Media Source Extensions) 实战:流媒体分块加载与自适应码率
前端·javascript·音视频
Tsuki_tl13 小时前
【面试高频】 HashMap, HashTable,ConcurrentHashMap 之间的区别
面试·hashmap·synchronized·哈希表·多线程并发·hashtable·分段锁
@小匠13 小时前
Spring Boot Nacos绑定 Map 时中文 key 导致启动失败:一次从复现到源码的排查实录
java·开发语言·前端
黄华SJ520it13 小时前
顶俏洗衣液模式制度开发介绍:S2B2C社交分销+多门店核销系统全解析
前端·数据库·小程序·零售·系统开发
张元清13 小时前
React useMount Hook:只在挂载时执行一次的正确姿势 (2026)
javascript·react.js
晓说前端13 小时前
TypeScript 核心语法应用 —— Vue 3 中的使用(上)
前端·typescript
breeze jiang13 小时前
JavaScript 单例模式:用静态属性保证 Popup 只创建一次
开发语言·javascript·单例模式