【转】图解:JavaScript中Number的一些表示上/下限 V4.2

来源:互联网 发布:数据库数据录入sql语句 编辑:程序博客网 时间:2024/05/05 20:08

本文来自对http://javascript-puzzlers.herokuapp.com/相关的精度问题的探索,并尝试着以一个数轴链接一些关于Number,关于数字表示的知识点。欢迎帮忙捉虫、补充。

更新list

V1.1 细微改进。

V2.0 补充了内存模型的草图,但是出现了重大问题,没有解释前面出现的隐藏位数。

V3.0 因为觉得太丢脸,删掉了原帖,在新的一贴中,补充说明了隐藏位数,补充说明反规格化值。
在这里感谢@P酱为本次问题的探索做出的贡献。
V3.1 修正了图片上的错误数位,修正了没头没脑的几句话……博客发到一半消失掉这种事情,再也不想经历第二遍了TAT
V3.2 应P酱的提议,修改了内存模型中比较让人混淆的两句话。

V 4.0 当当当当:直接写出了输入表达式,返回对应Number值的内存模型的DEMO(仅仅支持webkit和firefox)
V 4.1 有github page了(练习git操作什么的)
直接访问DEMO:
http://alvarto.github.io/VisualNumeric64/#1

V 4.2 应@miser的提议,额外说明了本文中图的字符排列顺序和其他的资料中,字符顺序的差别。

从题目开始

What is the result of this expression? (or multiple ones)

var end = Math.pow(2, 53);var START = end - 100;var count = 0;for (var i = START; i <= end; i++) {    count++;}console.log(count);
A:0 B:100 C:101 D:other

答案:D:other
it goes into an infinite loop, 2^53 is the highest possible number in javascript, and 2^53+1 gives 2^53, so i can never become larger than that.

这里答案的解释比较让人混淆,让我们深入到内存模型,来看看Number的表示上下限的由来。

数轴

说明

  • 关于Number.MAX_VALUENumber.MIN_VALUE:这个结果为了好看被我四舍五入了……
  • 关于±0:紫云飞:JavaScript中的两个0
  • 关于数组的最大索引:紫云飞:JavaScript:数组能越界?
  • 关于JavaScript可以精确表示到个位的最大整数:阮一峰:JavaScript数值

关于Number表示的内存模型

参考国际标准IEEE 754,我画了一张图帮助理解:

注,这里的字符是从左到右排的,和wiki之类的资料顺序相反。wiki资料考虑的是比较的顺序(符号-指数位-有效数字),而我这里考虑到的是阅读顺序(从0到63位,从左到右)。

中间的指数位是如何同时表示正负指数值的呢,和“符号位+有效数字位”的常规表示方法不同,指数是使用偏移法来做的:

IEEE 754:指数偏移值
指数偏移值(exponent bias),是指浮点数表示法中的指数域的编码值为指数的实际值加上某个固定的值,IEEE 754标准规定该固定值为2^(e-1)-1,其中的e为存储指数的比特的长度。
以单精度浮点数为例,它的指数域是8个比特,固定偏移值是28-1 - 1 = 128−1 = 127.单精度浮点数的指数部分实际取值是从128到-127。例如指数实际值为1710,在单精度浮点数中的指数域编码值为14410,即14410 = 1710 + 12710.
采用指数的实际值加上固定的偏移值的办法表示浮点数的指数,好处是可以用长度为e个比特的无符号整数来表示所有的指数取值,这使得两个浮点数的指数大小的比较更为容易。

因此,在JavaScript里面的指数位,是从1-2^(11-1),也就是从-1023开始,表示了(-1023,1024)这个区间。

实际指数值存储的指数值-102210102310232046

Number保留了指数值0和2047用于表示一些特殊的值。总的表示表格如下:

XY表示的值=0=0±0≠0=2047NaN=0=2047±Infinity≠0=0反规格化值(Denormalized):f(0.x , 1 , z) ∈(0,2047)规格化值(Normalized):f(1.x , y , z)

f(i,j,k) = (-1)k · 2-1023+j · i

精确表示到个位的最大整数

前52位能表示的最大值是下面这个(下面是52位+1位默认的1):

parseInt("11111111111111111111111111111111111111111111111111111",2)-> 9007199254740991 //即2^53-1

而下一个值是:

parseInt("100000000000000000000000000000000000000000000000000000",2)-> 9007199254740992 //即2^53

根据内存模型,画一张图就可以知道:

从第2^53位开始,第一个进制被舍弃,这个时候,2^53+1==2^53,每两个值都会有一个值出现这种不精确的情形。再过N个值,会出现每4个值里面都有3个值不精确;再过M个值,会出现每2^K个值里有2^K-1个值不精确;以此类推……(小题目:这个N值是多少?)

最大可表示的正数

验证:

Number.MAX_VALUE.toString(2)-> "1111111111111111111111111111111111111111111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"var a = Number.MAX_VALUE.toString(2).split("") , b = [ a.filter(function(i){return i==0}).length , a.filter(function(i){return i==1}).length ] ; b-> [971, 53]Number.MAX_VALUE === (Math.pow(2,53)-1)*Math.pow(2,971)-> true

QED

最小可表示的正数

还记得前面的表格吗:

XY表示的值≠0=0反规格化值(Denormalized):f(0.x , 1 , z) ∈(0,2047)规格化值(Normalized):f(1.x , y , z)

f(i,j,k) = (-1)k · 2-1023+j · i

非规格化值是这样表示的:

最小正数的内存模型

验证:

Number.MIN_VALUE.toString(2)-> "0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"var a = Number.MIN_VALUE.toString(2).split(""); a.filter(function(i){return i==0}).length - 1-> 1073Number.MIN_VALUE === Math.pow(2,-1074)-> true

参考资料

除了IEEE 754的维基页面,还有这篇文章,解释的非常清晰:"How numbers are encoded in JavaScript"

最后再推一次:输入表达式,返回对应Number值的内存模型的DEMO
http://alvarto.github.io/VisualNumeric64/#1

  • 2014年02月13日发布
0 0
原创粉丝点击