惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 三生石上(FineUI控件)
月光博客
月光博客
人人都是产品经理
人人都是产品经理
Google DeepMind News
Google DeepMind News
M
MIT News - Artificial intelligence
Vercel News
Vercel News
MyScale Blog
MyScale Blog
爱范儿
爱范儿
博客园 - 司徒正美
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
IT之家
IT之家
H
Help Net Security
Last Week in AI
Last Week in AI
阮一峰的网络日志
阮一峰的网络日志
酷 壳 – CoolShell
酷 壳 – CoolShell
L
LangChain Blog
罗磊的独立博客
Stack Overflow Blog
Stack Overflow Blog
宝玉的分享
宝玉的分享
博客园 - 聂微东
云风的 BLOG
云风的 BLOG
J
Java Code Geeks
博客园 - 叶小钗
D
Docker

博客园 - 前端路引

还在痛苦前端的拖拽排序吗?SortableJS 轻松搞定 浏览器 15 个常见指纹特征,使用插件 FingerprintJS 生成浏览器指纹 Web前端入门第 90 问:JavaScript 也能无中生有的创建音频 Web前端入门第 89 问:总结 8 种跨域通信处理方案 Web前端入门第 88 问:引入 JavaScript 的 script 标签究竟有多少用法? Web前端入门第 87 问:JavaScript 中 setInterval 和 setTimeout 细节 Web前端入门第 86 问:JavaScript 中的 Web Worker 为什么能提升代码性能? Web前端入门第 85 问:JavaScript 一个简单的 IndexedDB 数据库入门示例 Web前端入门第 84 问:JavaScript sessionStorage 那些容易踩坑的地方 Web前端入门第 83 问:JavaScript localStorage 有大小限制吗?溢出会怎样? Web前端入门第 82 问:JavaScript cookie 有大小限制吗?溢出会怎样? Web前端入门第 81 问:JavaScript cookie 的读写操作 Web前端入门第 80 问:JavaScript 哪些地方需要 try...catch 异常捕获 Web前端入门第 79 问:JavaScript async & await 的异步任务进化之路 Web前端入门第 78 问:JavaScript 比较少见的模版字符串用法 Web前端入门第 77 问:JavaScript 由程序触发绑定事件的几种方式 Web前端入门第 76 问:JavaScript 鼠标事件(mouse) enter/leave 和 over/out 区别 Web前端入门第 75 问:JavaScript 性能优化之事件委托(事件代理)原理 Web前端入门第 74 问:JavaScript 事件冒泡与事件捕获
高精度计算插件 decimal.js 处理 JS 浮点数精度问题(0.1 + 0...
前端路引 · 2026-03-17 · via 博客园 - 前端路引

JS 中的浮点数计算经常会看到这样问题:为什么 0.1 + 0.2 不等于 0.3 ?

在浏览器控制台执行 0.1 + 0.2 会得到一个奇怪的结果:0.30000000000000004

1

为何结果会是这么奇怪的一个数字?人类瞄一眼就知道的结果,为啥交给 JS 会得出这么奇怪的结果?

都知道程序的世界就是二进制的天下,在电脑的 CPU 运算时,所有的十进制都需要转为二进制计算。

十进制转二进制

十进制整数转二进制一般使用除 2 取余法,比如 35 换算二进制:

35 / 2 = 17 余数 1
17 / 2 = 8 余数 1
8 / 2 = 4 余数 0
4 / 2 = 2 余数 0
2 / 2 = 1 余数 0
1 / 2 = 0 余数 1

最终结果将余数倒序排列得到 100011


而小数换算二进制则使用乘 2 取整法,比如 0.03125 :

0.03125 * 2 = 0.0625 取整 0
0.0625 * 2 = 0.125 取整 0
0.125 * 2 = 0.25 取整 0
0.25 * 2 = 0.5 取整 0
0.5 * 2 = 1 取整 1

将最终的计算结果正序排列得到 0.00001


除了整数和小数外,还涉及到负数转换,有兴趣可以了解下 IEEE 754 标准(JS 数值运算基于 IEEE 754 标准)。

0.1 与 0.2

各种编程语言的 0.1 + 0.2 结果:https://0.30000000000000004.com/#ada

至于为什么会是 0.30000000000000004 这个结果,百度一搜一大把的文章,本文就不再赘述。

一句话总结就是 0.1 和 0.2 在转为 二进制 后是一个无限循环的结果(类似十进制中的 1/3),而 IEEE 754 标准中储存位数是有限的,在处理这种无限循环的时,会进行舍入处理,就会造成计算精度丢失,在一系列 舍入规格化数 后就得出了这么一个结果。

处理办法

同一个作者,写了三个这种运算模块,npm 的周下载量都在千万级别:

big.js github 地址:https://github.com/MikeMcl/big.js
npm 周下载量 2 千万左右:

bignumber.js github 地址:https://github.com/MikeMcl/bignumber.js
npm 周下载量 1.5 千万左右:

decimal.js github 地址:https://github.com/MikeMcl/decimal.js
npm 周下载量: 2 千万左右。

三者区别:

官方文档:https://github.com/MikeMcl/big.js/wiki

总结:

包体积 big.js < bignumber.js < decimal.js

big.js 适合基础的十进制运算,比如简单的金融计算等。
bignumber.js 支持二进制运算,适合一些加密计算场景。
decimal.js 支持二进制和三角函数运算,适合一些科学计算场景。


其他作者写的数学计算模块:
https://github.com/josdejong/mathjs

star 数量: 14762
npm 周下载量: 1 百万左右

瞅了一眼,好像内部也是依赖的 decimal.js


使用示例:

<script src="https://registry.npmmirror.com/big.js/7.0.1/files/big.js"></script>
<script src="https://registry.npmmirror.com/bignumber.js/9.3.0/files/bignumber.js"></script>
<script src="https://registry.npmmirror.com/decimal.js/10.5.0/files/decimal.js"></script>

<script>
(() => {
  // 加法
  console.log('原生 JS 计算:', 0.1 + 0.2) // 0.30000000000000004
  // 减法
  console.log('原生 JS 计算:', 0.3 - 0.1) // 0.19999999999999998
  // 乘法
  console.log('原生 JS 计算:', 0.2 * 0.1) // 0.020000000000000004
  // 除法
  console.log('原生 JS 计算:', 0.3 / 0.1) // 2.9999999999999996
})();
(() => {
  const x = new Big(0.1)
  const y = new Big(0.2)
  const z = new Big(0.3)
  // 加法
  console.log('big.js 插件计算:', x.plus(y).toNumber()) // 0.3
  console.log('big.js 插件计算:', x.plus(y).toString()) // '0.3'
  // 减法
  console.log('big.js 插件计算:', z.minus(x).toNumber()) // 0.2
  // 乘法
  console.log('big.js 插件计算:', x.times(y).toNumber()) // 0.02
  // 除法
  console.log('big.js 插件计算:', z.div(x).toNumber()) // 3
})();
(() => {
  const x = new BigNumber(0.1)
  const y = new BigNumber(0.2)
  const z = new BigNumber(0.3)
  // 加法
  console.log('bignumber.js 插件计算:', x.plus(y).toNumber()) // 0.3
  console.log('bignumber.js 插件计算:', x.plus(y).toString()) // '0.3'
  // 减法
  console.log('bignumber.js 插件计算:', z.minus(x).toNumber()) // 0.2
  // 乘法
  console.log('bignumber.js 插件计算:', x.times(y).toNumber()) // 0.02
  // 除法
  console.log('bignumber.js 插件计算:', z.div(x).toNumber()) // 3
})();
(() => {
  const x = new Decimal(0.1)
  const y = new Decimal(0.2)
  const z = new Decimal(0.3)
  // 加法
  console.log('decimal.js 插件计算:', x.plus(y).toNumber()) // 0.3
  console.log('decimal.js 插件计算:', x.plus(y).toString()) // '0.3'
  // 减法
  console.log('decimal.js 插件计算:', z.minus(x).toNumber()) // 0.2
  // 乘法
  console.log('decimal.js 插件计算:', x.times(y).toNumber()) // 0.02
  // 除法
  console.log('decimal.js 插件计算:', z.div(x).toNumber()) // 3
})();
</script>

也可以使用 npm 安装使用

// npm install big.js
// const Big = require('big.js');
import Big from 'big.js';

const x = new Big(0.1)

原生 JS 要处理浮点数计算也能做,只是比较麻烦,需要将计算的小数转为整数之后再进行计算,最后除以倍数。比如 0.1 + 0.2 可以转为 (1+2) / 10。


JS 支持的 最大整数 也是有边界的,在 −2的53次方 + 12的53次方 − 1 之间(即 -9007199254740991 到 9007199254740991)。
超出这个边界计算也会出问题,比如:

(() => {
  console.log('超出边界计算', 2**53) // 9007199254740992
  console.log('超出边界计算', 2**53 - 1) // 9007199254740991
  console.log('超出边界计算', 2**53 - 1.1) // 9007199254740991
  console.log('超出边界计算', 2**53 + 1) // 9007199254740992
  console.log('超出边界计算', 2**53 + 1.1) // 9007199254740994
})();

超大数计算也可以使用 big.js 处理:

(() => {
  const x = (new Big(2)).pow(53)
  const y = new Big(1.1)
  console.log('加法超出边界计算', x.plus(y).toString()) // 9007199254740993.1
  console.log('减法超出边界计算', x.minus(y).toString()) // 9007199254740990.9
})();

写在最后

凡使用 IEEE 754 标准的编程语言,都存在浮点数计算问题,只是其他语言有内置的解决方案,而 JS 最初的设计思想是用于浏览器交互,所以没有内置解决方案,好在有热心作者开源的插件用于解决浮点数计算问题。

在使用 JS 计算时,需特别小心浮点数问题,能交给后端处理就交出去~~