IEEE二进制浮点数算术标准(IEEE 754), 它规定了四种表示浮点数值的方式:单精度(32位),双精度(64位),延伸单精确度与延伸双精确度。在 JavaScript 中采用的是 64 位双精度存储的。这样存储结构优点可以归一化处理整数和小数。
64 位比特可以分为三部分来存储我们的数字。
符号位S:第1位是正负符号位,0表示是正值,1代表是负值。 63位为指数位。
指数位E:中间的11位存储指数,用来表示次方数。52-62位。
尾数位M:最后的52位尾数,超出的部分自动进一舍零。0-51位。
把多个比特的数据,从内存地址低端到高端,通常把最低端的放到最右边,称为最低有效位,代表最小的,改变时对整体的大小改动不大。
实际上用公式来计算的话:
以上的公式遵循科学记数法的规范,在十进制中 0<M<10,到二进制就是 0<M<2。所以的话,整数部分就只能是1,按照IEEE 754 的描述,在尾数位之前,默认会有一个隐藏位1,有效数字将会被表示位 1.M, 所以1可以被舍去,只保留后面的小数部分。
前文说小数位称为有效位,最高有效位是1(它是隐藏的),小数位的最高有是1(即整数字),这个浮点数被称为规约形式的浮点数。代表它是唯一确定的使用浮点形式来表示一个值。
我们在来讲讲指数位的偏移。
指数部分有 11 位,E是一个无符号整数,取值范围是 0~2047, 但是这样子很难表示一个值是大的还是小的,在我们的价值观上是认为有符号,是负的,才是小的,否则就算大的。基于这一点,指数在存储之前需要做偏差修正,将它的值调整到一个 无符号数 的范围内以便进行比较。
为了表示负数,会取中间值来进行偏移 0 <= E <=2**11 - 1 ----> 0 <= E <=2047, 我们取中间值的话, 取到bias = 1023(表示一个中间值)。然后偏移结果就是。-1023 <= E-bias <= 1024.因此会得到用 1023 表示 0,用2046 表示 1023.
有两个特殊的 E 值。 0 和 2047
双精度(64-比特) 在指数偏移值的值域为 00000000001 (11-bit) 到 11111111110, 在小数部分则是00000...00000(52-bit) 到 11111...11111(52-bit)。
parseInt('1111111110', 2); // 解析为 2046
parseInt('000000000001', 2); // 解析为 1
parseInt('0000000000000000000000000000000000000000000000000000', 2) // 0;
当 E = 2047 时,并且 M 值为 0 时,该值表示为无穷大, 但如果 f > 0,该值则为 NaN。
当 E = 0 时,f 也为 0, 该值可能为 +0 也可能为 -0,因为符号是单独的一位,63位存储的。
先举个例子,0.1 是怎么存放的。
0.1 转为二进制。小数转二进制的方法应该是乘2取整,直到小数位为0。然后从上到下收集,然后别忘了还有前面的0。
0.2 0
0.4 0
0.8 0
1.6 1
1.2 1
0.4 0
0.8 0
1.6 1
1.2 1
0.4 0
0.8 0
所以最后0.1 的二进制为。0.00011001100(1100是无限循环的)。科学记数法表示 1.1001*2^-4,这样子 -4时存储到指数位,1.1001的1被隐藏,只剩下1001(无限1001),我们拿出52位存储到小数位。100110011001100110011001100110011001100110011(53位,小数点后是1,我们遵循进1舍0的方法)变为 10011001100110011001100110011001100110011010, E= -4+1023 = 1019(存储位2进制位01111111011),是正数,所以符号位存储的是0。
我们将上面的存储的二进制转回十进制,得到了 0.100000000000000005551115123126, 我们可以看出,这与 0.1 已经不相等了,出现了精度误差。
那么为什么还会得到我们的 0.1, 而不是一个丢失精度的数字。
我们可以使用 测试IEEE 754工具 来计算一下,得到的十进制数还是 0.1. 这是为什么呢,你可能会想,这怎么可能。
这是因为 IEEE 754 双精度 用二进制转为十进制,结果会很长,但是我们只会取到17位精度,即 0.10000000000000000(1后面16个0),因为存储是浮点数,所以可以去掉0,即为 0.1。
为什么0.1 + 0.2 === 0.30000000000000004;
可以把 0.1 和 0.2 分别转为对应的二进制。
0.00011001100110011001100110011001100110011001100110011010 + 0.0011001100110011001100110011001100110011001100110011010 = 0.0100110011001100110011001100110011001100110011001100111 // 转成十进制正好是 0.30000000000000004
所以说,存储一个数字,实际上是先转为计算机能识别的0,1机器码,二进制只能精准表达2除尽的数字1/2,1/4,1/8,例如0.1(1/10)和0.2(1/5),在二进制中都无法精准表示时,所以在存储的时候,就会发生进 1 舍 0,从而造成精度丢失。然后在读取为10进制的时候,就会与预期的不符。