浅谈JavaScript中小数和大整数的精度丢失


Posted in Javascript onMay 31, 2016

先来看两个问题:

0.1 + 0.2 == 0.3; // false
9999999999999999 == 10000000000000000; // true

第一个问题是小数的精度问题,在业界不少博客里已有讨论。第二个问题,去年公司有个系统的数据库在做数据订正时,发现有部分数据重复的诡异现象。本文将从规范出发,对上面的问题做个小结。

最大整数

JavaScript 中的数字是用 IEEE 754 双精度 64 位浮点数 来存储的,其格式为:

s x m x 2^e

s 是符号位,表示正负。 m 是尾数,有 52 bits. e 是指数,有 11 bits. 在 ECMAScript 规范 里有给出 e 的范围为 [-1074, 971]. 这样,很容易推导出 JavaScript 能表示的最大整数为:

1 x (2^53 - 1) x 2^971 = 1.7976931348623157e+308

这个值正是 Number.MAX_VALUE

同理可推导出 Number.MIN_VALUE 的值为:

1 x 1 x 2^(-1074) = 5e-324

注意 MIN_VALUE 表示最接近 0 的正数,而不是最小的数。最小的数是 -Number.MAX_VALUE

小数的精度丢失

JavaScript 的数字都是双精度浮点数,在计算机里用二进制存储。当有效位数超过 52 位时,会存在精度丢失。比如:

十进制 0.1 的二进制为 0.0 0011 0011 0011 … (循环 0011)
十进制 0.2 的二进制为 0.0011 0011 0011 … (循环 0011)
0.1 + 0.2 相加可表示为:
   e = -4; m = 1.10011001100...1100(52 位)
 + e = -3; m = 1.10011001100...1100(52 位)
---------------------------------------------
   e = -3; m = 0.11001100110...0110
 + e = -3; m = 1.10011001100...1100
---------------------------------------------
   e = -3; m = 10.01100110011...001
---------------------------------------------
 = 0.01001100110011...001
 = 0.30000000000000004(十进制)

根据上面的演算,还可以得出一个结论:当十进制小数的二进制表示的有限数字不超过 52 位时,在 JavaScript 里是可以精确存储的。比如:

0.05 + 0.005 == 0.055 // true

进一步的规律,比如:

0.05 + 0.2 == 0.25 // true
0.05 + 0.9 == 0.95 // false

需要考虑 IEEE 754 的 Rounding modes, 有兴趣的可进一步研究。

大整数的精度丢失

这个问题鲜有人提及。首先得弄清楚问题是什么:

1. JavaScript 能存储的最大整数是什么?

该问题前面已回答,是 Number.MAX_VALUE, 非常大的一个数。

2. JavaScript 能存储的且不丢失精度的最大整数是什么?

根据 s x m x 2^e, 符号位取正,52 位尾数全填充 1, 指数 e 取最大值 971, 显然,答案依旧是 Number.MAX_VALUE.

我们的问题究竟是什么呢?回到起始代码:

9999999999999999 == 10000000000000000; // true

很明显,16 个 9 还远远小于 308 个 10. 这个问题与 MAX_VALUE 没什么关系,还得归属到尾数 m 只有 52 位上来。

可以用代码来描述:

var x = 1; // 为了减少运算量,初始值可以设大一点,比如 Math.pow(2, 53) - 10
while(x != x + 1) x++;
// x = 9007199254740992 即 2^53

也就是说,当 x 小于等于 2^53 时,可以确保 x 的精度不会丢失。当 x 大于 2^53 时,x 的精度有可能会丢失。比如:

x 为 2^53 + 1 时,其二进制表示为:
10000000000...001 (中间共有 52 个 0)
用双精度浮点数存储时:
e = 1; m = 10000..00(共 52 个 0,其中 1 是 hidden bit)
显然,这和 2^53 的存储是一样的。

按照上面的思路可以推出,对于 2^53 + 2, 其二进制为 100000…0010(中间 51 个 0),也是可以精确存储的。

规律:当 x 大于 2^53 且二进制有效位数大于 53 位时,就会存在精度丢失。这和小数的精度丢失本质上是一样的。

hidden bit 可参考:A tutorial about Java double type.

小结

小数和大整数的精度丢失,并不仅仅在 JavaScript 中存在。严格来说,使用了IEEE 754 浮点数格式来存储浮点类型的任何编程语言(C/C++/C#/Java 等等)都存在精度丢失问题。在 C#、Java 中,提供了 Decimal、BigDecimal 封装类来进行相应的处理,才避开了精度丢失。

注:ECMAScript 规范中,已有  decimal proposal,但目前尚未被正式采纳。

最后考考大家:

Number.MAX_VALUE + 1 == Numer.MAX_VALUE;
Number.MAX_VALUE + 2 == Numer.MAX_VALUE;
...
Number.MAX_VALUE + x == Numer.MAX_VALUE;
Number.MAX_VALUE + x + 1 == Infinity;
...
Number.MAX_VALUE + Number.MAX_VALUE == Infinity;
// 问题:
// 1. x 的值是什么?
// 2. Infinity - Number.MAX_VALUE == x + 1; 是 true 还是 false ?

以上这篇浅谈JavaScript中小数和大整数的精度丢失就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
多个iframe自动调整大小的问题
Sep 18 Javascript
JavaScript学习心得之概述
Jan 20 Javascript
学习JavaScript设计模式之享元模式
Jan 18 Javascript
WebGL利用FBO完成立方体贴图效果完整实例(附demo源码下载)
Jan 26 Javascript
jQuery使用contains过滤器实现精确匹配方法详解
Feb 25 Javascript
详解jQuery同步Ajax带来的UI线程阻塞问题及解决办法
Aug 09 jQuery
vue.js中引入vuex储存接口数据及调用的详细流程
Dec 14 Javascript
浅谈React高阶组件
Mar 28 Javascript
微信小程序template模版的使用方法
Apr 13 Javascript
基于vue实现滚动条滚动到指定位置对应位置数字进行tween特效
Apr 18 Javascript
JS插件amCharts实现绘制柱形图默认显示数值功能示例
Nov 26 Javascript
原生JS实现汇率转换功能代码实例
May 13 Javascript
javascript小数精度丢失的完美解决方法
May 31 #Javascript
javascript解决小数的加减乘除精度丢失的方案
May 31 #Javascript
javascript基本语法
May 31 #Javascript
jquery 无限极下拉菜单的简单实例(精简浓缩版)
May 31 #Javascript
通用无限极下拉菜单的实现代码
May 31 #Javascript
JavaScript+Java实现HTML页面转为PDF文件保存的方法
May 30 #Javascript
详解JavaScript中双等号引起的隐性类型转换
May 30 #Javascript
You might like
成为好程序员必须避免的5个坏习惯
2014/07/04 PHP
Javascript与PHP验证用户输入URL地址是否正确
2014/10/09 PHP
Codeigniter控制器controller继承问题实例分析
2016/01/19 PHP
thinkPHP订单数字提醒功能的实现方法
2016/12/01 PHP
cument.execCommand()用法深入理解
2012/12/04 Javascript
jQuery中attr()方法用法实例
2015/01/05 Javascript
jQuery trigger()方法用法介绍
2015/01/13 Javascript
JavaScript模拟实现继承的方法
2015/03/30 Javascript
CSS3实现动态背景登录框的代码
2015/07/28 Javascript
js带点自动图片轮播幻灯片特效代码分享
2015/09/07 Javascript
js密码强度校验
2015/11/10 Javascript
js放大镜放大购物图片效果
2017/01/18 Javascript
jQuery插件jqGrid动态获取列和列字段的方法
2017/03/03 Javascript
JS如何判断浏览器类型和详细区分IE各版本浏览器
2017/03/04 Javascript
前端开发之CSS原理详解
2017/03/11 Javascript
JS实现的模仿QQ头像资料卡显示与隐藏效果
2017/04/07 Javascript
分析JavaScript数组操作难点
2017/12/18 Javascript
vue组件开发之用户无限添加自定义填写表单的方法
2018/08/28 Javascript
nodejs更新package.json中的dependencies依赖到最新版本的方法
2018/10/10 NodeJs
Windows下支持自动更新的Electron应用脚手架的方法
2018/12/24 Javascript
JavaScript模块管理的简单实现方式详解
2019/06/15 Javascript
VSCode写vue项目一键生成.vue模版,修改定义其他模板的方法
2020/04/17 Javascript
[01:02:54]完美世界DOTA2联赛PWL S2 FTD vs GXR 第一场 11.22
2020/11/26 DOTA
python微信跳一跳系列之自动计算跳一跳距离
2018/02/26 Python
numpy和pandas中数组的合并、拉直和重塑实例
2019/06/28 Python
基于tensorflow __init__、build 和call的使用小结
2021/02/26 Python
特罗佩亚包官方网站:Tropea
2017/01/03 全球购物
俄罗斯电子产品在线商店:UltraTrade
2020/01/30 全球购物
腾讯公司的一个sql题
2013/01/22 面试题
会计大学生职业生涯规划书范文
2014/01/13 职场文书
大学生学习计划书
2014/09/15 职场文书
婚庆司仪开场白
2015/05/29 职场文书
幼儿园大班教学反思
2016/03/02 职场文书
Java面试题冲刺第十八天--Spring框架3
2021/08/07 面试题
MySQL利用UNION连接2个查询排序失效详解
2021/11/20 MySQL
SpringCloud Feign请求头删除修改的操作代码
2022/03/20 Java/Android