探寻 JavaScript 精度问题
阅读完本文可以了解到 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),根据上述公式,S 为 0(1 bit),E 为 -4 + 1023,对应的二进制为 01111111011(11 bit),M 为 1001100110011001100110011001100110011001100110011010(52 bit,另外注意末尾的进位),0.1 的存储示意图如下:

同理,0.2 转化为二进制为 0.001100110011...,用科学计数法表示为 1.100110011... x 2^(-3),根据上述公式,E 为 -3 + 1023,对应的二进制为 01111111100, M 为 1001100110011001100110011001100110011001100110011010, 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 精度问题的更多相关文章
- 探寻 JavaScript 逻辑运算符(与、或)的真谛
十二月已经过半,冬季是一个美妙的季节,寒冷的空气逼得人们不得不躲在安逸舒适的环境里生活.冬季会给人一种安静祥和的氛围,让人沉浸在其中,仿佛是一个旧的阶段的结束,同时也是一个新的阶段的开始.这么说来,西 ...
- javascript精度问题与调整
一个经典的问题: 0.1+0.2==0.3 答案是:false 因为:0.1+0.2=0.30000000000000004 第一次看到这个结果就是无比惊讶,下巴碰到地上,得深入了解下问题出在哪里,该 ...
- WEB前端学习资源清单
常用学习资源 JS参考与基础学习系列 [MDN]JS标准参考 es6教程 JS标准参考教程 编程类中文书籍索引 深入理解JS系列 前端开发仓库 <JavaScript 闯关记> JavaS ...
- [转] WEB前端学习资源清单
常用学习资源 JS参考与基础学习系列 [MDN]JS标准参考 es6教程 JS标准参考教程 编程类中文书籍索引 深入理解JS系列 前端开发仓库 <JavaScript 闯关记> JavaS ...
- JavaScript 入门教程四 语言基础【2】
一.数据类型介绍: undefined null NaN 1.判断当前变量是否为 undefined: if (i === undefined) 或者 if (typeof (i) === " ...
- 详细解析 JavaScript 获取元素的坐标
引言 最近突然看到了有关图片懒加载的问题,大致意思就是初始状态下页面只加载浏览器可视区域的图片,剩余图片在当浏览器可视区域滚动到其位置时才开始加载.貌似现在许多大型网站都有实现懒加载,所以我便就此问题 ...
- JavaScript 基础入门11 - 运动框架的封装
目录 JavaScript 运动原理 运动基础 简单运动的封装 淡入淡出 不同属性的设置 多属性值同时运动 运动回调,链式运动 缓冲运动 加入缓冲的运动框架 案例1 多图片展开收缩 运动的留言本 Ja ...
- 2016年工作中遇到的问题41-50:Dubbo注册中心奇葩问题,wifi热点坑了
41.获得JSON中的变量.//显示json串中的某个变量,name是变量名function json(json,name){ var jsonObj = eval(json); return jso ...
- JavaScript数字精度丢失问题总结
本文分为三个部分 JS 数字精度丢失的一些典型问题 JS 数字精度丢失的原因 解决方案(一个对象+一个函数) 一.JS数字精度丢失的一些典型问题 1. 两个简单的浮点数相加 0.1 + 0.2 != ...
随机推荐
- Docker系列08—搭建使用私有docker registry
本文收录在容器技术学习系列文章总目录 1.了解Docker Registry 1.1 介绍 registry 用于保存docker 镜像,包括镜像的层次结构和元数据. 启动容器时,docker dae ...
- oracle数据库密码过期修改注意事项
近期的工作中,因数据库密码临近过期,需要进行修改,因对oracle数据库底层结构不了解,导致安装网上的教程操作是出现一些问题,特记录下来 传统的修改语句为 输入:win+R进入cmd 输入sqlpl ...
- Java开发笔记(六十五)集合:HashSet和TreeSet
对于相同类型的一组数据,虽然Java已经提供了数组加以表达,但是数组的结构实在太简单了,第一它无法直接添加新元素,第二它只能按照线性排列,故而数组用于基本的操作倒还凑合,若要用于复杂的处理就无法胜任了 ...
- js autocomplete输入延迟触发执行事件
需求:延迟查询,autocomplete延迟触发执行事件.当有下一个事件开始时,本次事件中断.目的是为了防止调用服务器过于频繁. var timeout = 0;//延时处理 $("#cus ...
- 零基础学Python--------第2章 Python语言基础
第2章 Python语言基础 2.1 Python语法特点 2.11注释 在Python中,通常包括3种类型的注释,分别是单行注释.多行注释和中文编码声明注释. 1.单行注释 在Python中,使用 ...
- 关于我的博客(About My Blogs)
本文算是第一篇文章,记录工作/学习/生活中的觉得值得分享的东西 When,Where,Who,What,How,Why,尽可能每篇博文当作一个事件来描述. 也就是触发想写这篇文章的事件. 要坚持下来写 ...
- Android连续点击多次事件的实现
有时候我们需要实现这样的场景,类似进入开发者模式,即多次点击后执行操作. 首先我们先看一个方法: System提供的一个静态方法arraycopy(),我们可以使用它来实现数组之间的复制. publi ...
- H-ui框架信息图标点击跳出页面问题
在html中为消息a标签添加id: 在static/h-ui/js/H-ui.min.js添加事件:
- 测者的测试技术手册:自动化单元工具EvoSuie的代码覆盖报告
EvoSuite是由Sheffield等大学联合开发的一种开源工具,用于自动生成测试用例集,生成的测试用例均符合Junit的标准,可直接在Junit中运行.得到了Google和Yourkit的支持. ...
- Spark之Pipeline处理模式
一.简介 Pipeline管道计算模式:只是一种计算思想,在数据处理的整个流程中,就想水从管道流过一下,是顺序执行的. 二.特点 1.数据一直在管道中,只有在对RDD进行持久化[cache,persi ...