本文已参与「新人创作礼」活动,一起开启掘金创作之路。
前言
javascript中的Number类型使用IEEE754格式表示整数和浮点数(在js中浮点数表示的是双精度值)。在我们平时的看书或者实践中,会发现当某些小数相加时,会出现结果和你想要的不一致,比如会出现0.1 + 0.2 !== 0.3的情况,这时大部分书上都会告诉我们永远不要测试某些特定的浮点数,但是为什么会出现这样的问题却没有细说,那么现在我们大家一起来探索IEEE754的世界吧!
IEEE754标准
根据IEEE754的标准,我们来看一下浮点数在计算机中的存储格式。IEEE标准中规定单精度浮点数在机器中表示用 1 位表示数字的符号,用 8 位表示指数,用 23 位表示尾数,即小数部分。对于双精度浮点数,用 1 位表示符号,用 11 位表示指数,52 位表示尾数,其中指数域称为阶码。如图所示:
浮点数的机器码运算
此时我们需要考虑一下如何将一个浮点数转化成机器码。
十进制转换成机器码 :
符号位(1)+ 阶码(8/11)+ 尾数域(23/52)
阶码
通常我们把指数域称为阶码。那我们如何计算阶码呢?IEE754 规定浮点数阶码 E 采用"指数e的移码-1"来表示。所以我们这里又抛出了一个问题,如何计算指数e的移码。
移码(又叫增码)是对真值补码的符号位取反,一般用作浮点数的阶码,引入的目的是便于浮点数运算时的对阶操作。
由原码变成反码和补码有如下规则:
- 原码符号位为1不变,整数的每一位二进制数位求反得反码;
- 反码符号位为1不变,反码数值位最低位加1得补码。
所以我们可以得知从原码到移码的过程如下:
浮点数规格化
因为指数和尾数的变化是多样的,比如11可以表示为 , 也可以表示为、等等。所以我们必须要对浮点数进行规格化,若不对浮点数的表示作出明确规定,同一个浮点数的表示就不是唯一的。当尾数不为 0 时,尾数域的最高有效位为1,这称为浮点数的规格化。通俗点讲就是1001.11(二进制)要表示成, 0.001(二进制)表示为。
计算过程举例
,符号位为,指数为,规格化后尾数为。
单精度浮点数尾数域共23位,右侧以0补全,尾数域:
阶码E:
对照单精度浮点数的存储格式,将符号位S,阶码E和尾数域M存放到指定位置,得0.5的机器码:
,符号位为,指数为,规格化后尾数为。
单精度浮点数尾数域共23位,右侧以0补全,尾数域:
阶码E:
对照单精度浮点数的存储格式,将符号位S,阶码E和尾数域M存放到指定位置,得0.5的机器码:
浮点数的精度
如果有认真的看到这里,自己对于浮点数的精度也应该有所思索了,尾数域就表示的是小数的精度。
单精度的尾数域二进制为23位,若加上浮点数规格化的一位,那么单精度能表示的最大值为2^24 = 16777216,也就是表示小数点位后7~8位,最差也能表示7位。而双精度的尾数域为52位,则2^53 = 9007199254740992, 所以在javascript中的,浮点数所能表示的最大精度为15~16位。
结论
最后再让我们来思考一下为什么
当我们将0.2转化为机器码时,我们会发现0.2转化为二进制时是无限循环的,而js中的浮点数(双精度)的尾数域只有53位,二进制并不能准确的表示0.2,所以在相加后会出现舍入问题。
其实,我们去思考这个问题的时候,这个结论完全可以解决我们的疑惑,但是我们作为初学者不能丢失了我们对于事物的好奇心、求知欲,或许这些品质就会成为我们最锋利的刃,当我们遇到困难、瓶颈时,总能迎刃而解。
我是梨木,一个前端初学者,希望能在学习前端的过程中,留下自己的思考,给予你们帮助,以上文章若有错误,感谢指出!