资讯专栏INFORMATION COLUMN

探寻 JavaScript 精度问题以及解决方案

YanceyOfficial / 1977人阅读

摘要:推导为何等于在中所有数值都以标准的双精度浮点数进行存储的。先来了解下标准下的双精度浮点数。精度位总共是,因为用科学计数法表示,所以首位固定的就没有占用空间。验证完成的最大安全数是如何来的根据双精度浮点数的构成,精度位数是。

阅读完本文可以了解到 0.1 + 0.2 为什么等于 0.30000000000000004 以及 JavaScript 中最大安全数是如何来的。

十进制小数转为二进制小数方法

拿 173.8125 举例如何将之转化为二进制小数。

①. 针对整数部分 173,采取除 2 取余,逆序排列;

173 / 2 = 86 ... 1
86 / 2 = 43 ... 0
43 / 2 = 21 ... 1   ↑
21 / 2 = 10 ... 1   | 逆序排列
10 / 2 = 5 ... 0    |
5 / 2 = 2 ... 1     |
2 / 2 = 1 ... 0
1 / 2 = 0 ... 1

得整数部分的二进制为 10101101

②. 针对小数部分 0.8125,采用乘 2 取整,顺序排列;

0.8125 * 2 = 1.625  |
0.625 * 2 = 1.25    | 顺序排列
0.25 * 2 = 0.5      |
0.5 * 2 = 1         ↓

得小数部分的二进制为 1101

③. 将前面两部的结果相加,结果为 10101101.1101;

小心,二进制小数丢失了精度!

根据上面的知识,将十进制小数 0.1 转为二进制:

0.1 * 2 = 0.2
0.2 * 2 = 0.4 // 注意这里
0.4 * 2 = 0.8
0.8 * 2 = 1.6
0.6 * 2 = 1.2
0.2 * 2 = 0.4 // 注意这里,循环开始
0.4 * 2 = 0.8
0.8 * 2 = 1.6
0.6 * 2 = 1.2
...

可以发现有限十进制小数 0.1 却转化成了无限二进制小数 0.00011001100...,可以看到精度在转化过程中丢失了!

能被转化为有限二进制小数的十进制小数的最后一位必然以 5 结尾(因为只有 0.5 * 2 才能变为整数)。所以十进制中一位小数 0.1 ~ 0.9 当中除了 0.5 之外的值在转化成二进制的过程中都丢失了精度。

推导 0.1 + 0.2 为何等于 0.30000000000000004

在 JavaScript 中所有数值都以 IEEE-754 标准的 64 bit 双精度浮点数进行存储的。先来了解下 IEEE-754 标准下的双精度浮点数。

这幅图很关键,可以从图中看到 IEEE-754 标准下双精度浮点数由三部分组成,分别如下:

sign(符号): 占 1 bit, 表示正负;

exponent(指数): 占 11 bit,表示范围;

mantissa(尾数): 占 52 bit,表示精度,多出的末尾如果是 1 需要进位;

推荐阅读 JavaScript 浮点数陷阱及解法,阅读完该文后可以了解到以下公式的由来。

精度位总共是 53 bit,因为用科学计数法表示,所以首位固定的 1 就没有占用空间。即公式中 (M + 1) 里的 1。另外公式里的 1023 是 2^11 的一半。小于 1023 的用来表示小数,大于 1023 的用来表示整数。

指数可以控制到 2^1024 - 1,而精度最大只达到 2^53 - 1,两者相比可以得出 JavaScript 实际可以精确表示的数字其实很少。

0.1 转化为二进制为 0.0001100110011...,用科学计数法表示为 1.100110011... x 2^(-4),根据上述公式,S0(1 bit),E-4 + 1023,对应的二进制为 01111111011(11 bit),M1001100110011001100110011001100110011001100110011010(52 bit,另外注意末尾的进位),0.1 的存储示意图如下:

同理,0.2 转化为二进制为 0.001100110011...,用科学计数法表示为 1.100110011... x 2^(-3),根据上述公式,E-3 + 1023,对应的二进制为 01111111100, M1001100110011001100110011001100110011001100110011010, 0.2 的存储示意图如下:

0.1 + 0.2 即 2^(-4) x 1.1001100110011001100110011001100110011001100110011010 与 2^(-3) x 1.1001100110011001100110011001100110011001100110011010 之和

// 计算过程
0.00011001100110011001100110011001100110011001100110011010
0.0011001100110011001100110011001100110011001100110011010

// 相加得
0.01001100110011001100110011001100110011001100110011001110

0.01001100110011001100110011001100110011001100110011001110 转化为十进制就是 0.30000000000000004。验证完成!

JavaScript 的最大安全数是如何来的

根据双精度浮点数的构成,精度位数是 53 bit。安全数的意思是在 -2^53 ~ 2^53 内的整数(不包括边界)与唯一的双精度浮点数互相对应。举个例子比较好理解:

Math.pow(2, 53) === Math.pow(2, 53) + 1 // true

Math.pow(2, 53) 竟然与 Math.pow(2, 53) + 1 相等!这是因为 Math.pow(2, 53) + 1 已经超过了尾数的精度限制(53 bit),在这个例子中 Math.pow(2, 53)Math.pow(2, 53) + 1 对应了同一个双精度浮点数。所以 Math.pow(2, 53) 就不是安全数了。

最大的安全数为 Math.pow(2, 53) - 1,即 9007199254740991
业务中碰到的精度问题以及解决方案

了解 JavaScript 精度问题对我们业务有什么帮助呢?举个业务场景:比如有个订单号后端 Java 同学定义的是 long 类型,但是当这个订单号转换成 JavaScript 的 Number 类型时候精度会丢失了,那没有以上知识铺垫那就理解不了精度为什么会丢失。

解决方案大致有以下几种:

1.针对大数的整数可以考虑使用 bigint 类型(目前在 stage 3 阶段);

2.使用 bigNumber,它的思想是转化成 string 进行处理,这种方式对性能有一定影响;

3.可以考虑使用 long.js,它的思想是将 long 类型的值转化成两个 32 位的双精度类型的值。

4.针对小数可以考虑 JavaScript 浮点数陷阱及解法 里面提到的方案;

相关链接

代码之谜系列

IEEE-754 进制转换图生成

JavaScript 浮点数陷阱及解法: 推荐阅读

javascript 里最大的安全的整数为什么是2的53次方减一

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/98141.html

相关文章

  • 「干货」细说 Javascript 中的浮点数精度丢失问题(内附好课推荐)

    摘要:前言最近,朋友问了我这样一个问题在中的运算结果,为什么是这样的虽然我告诉他说,这是由于浮点数精度问题导致的。由于可以用阶码移动小数点,因此称为浮点数。它的实现遵循标准,使用位精度来表示浮点数。 showImg(https://segmentfault.com/img/remote/1460000018981071); 前言 最近,朋友 L 问了我这样一个问题:在 chrome 中的运算...

    senntyou 评论0 收藏0
  • JavaScript 精度丢失问题

    摘要:排除直接使用的数太大或太小超出范围,出现这种问题的情况基本是浮点数的小数部分在转成二进制时丢失了精度,所以我们可以将小数部分也转换成整数后再计算。 // 1. 两数相加 // 0.1 + 0.2 = 0.30000000000000004 // 0.7 + 0.1 = 0.7999999999999999 // 0.2 + 0.4 = 0.6000000000000001 // 2.2...

    iOS122 评论0 收藏0
  • 如何解决0.1 +0.2===0.30000000000000004类问题

    摘要:方法使用定点表示法来格式化一个数,会对结果进行四舍五入。该数值在必要时进行四舍五入,另外在必要时会用来填充小数部分,以便小数部分有指定的位数。如果数值大于,该方法会简单调用并返回一个指数记数法格式的字符串。在环境中,只能是之间,测试版本为。 showImg(https://segmentfault.com/img/remote/1460000011913134?w=768&h=521)...

    yuanzhanghu 评论0 收藏0
  • 从 ++[[]][+[]]+[+[]]==10? 深入浅出弱类型 JS 的隐式转换

    摘要:与此相对,强类型语言的类型之间不一定有隐式转换。三为什么是弱类型弱类型相对于强类型来说类型检查更不严格,比如说允许变量类型的隐式转换,允许强制类型转换等等。在中,加性运算符有大量的特殊行为。 从++[[]][+[]]+[+[]]==10?深入浅出弱类型JS的隐式转换 本文纯属原创? 如有雷同? 纯属抄袭? 不甚荣幸! 欢迎转载! 原文收录在【我的GitHub博客】,觉得本文写的不算烂的...

    miya 评论0 收藏0

发表评论

0条评论

YanceyOfficial

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<