生活中最大的障碍之一就是对羞辱的恐惧
大家好,我是柒八九。
今天,我们继续计算机底层知识的探索。我们来谈谈关于小数运算的相关知识点。
如果,想了解该系列的文章,可以参考我们已经发布的文章。如下是往期文章。
文章list
你能所学到的知识点
- 计算机精度缺失 推荐阅读指数 ⭐️⭐️⭐️
- 如何用二进制表示小数 推荐阅读指数 ⭐️⭐️⭐️⭐️⭐️
- 计算机精度缺失的原因
- 浮点数 推荐阅读指数 ⭐️⭐️⭐️⭐️⭐️
- 正则表达式和EXCESS系统 推荐阅读指数 ⭐️⭐️⭐️⭐️⭐️
好了,天不早了,干点正事哇。
将0.1累加100次也得不到10
我们来一个计算机运算错误的例子。
function sum(){ let sum = 0; for(let i=1;i<=100;i++){ sum +=0.1; } console.log(sum) } 复制代码
我们在浏览器的控制台中,运行sum()
,得到的运行结果为9.99999999999998
。这显然和我们的九年义务教育所教导的背道而驰。
有句话说,雪崩的时候,没有一片雪花是无辜的。在这段代码中,程序没错,计算机也没有发生故障,当然和所使用的语言也没有关系(选用其他的高级语言可能运算结果不同)。如果硬要找一个背锅的,那就是计算机处理小数的机制。
用二进制数表示小数
在计算机底层知识之二进制中我们讲过,由于计算机内部所有的信息都是以二进制数的形式来处理,因此,整数和小数并无差别。
在说明计算机如何用二进制数表示小数的具体方法前,我们先尝试将1011.0011
这个有小数点的二进制数转换成十进制数。
小数点前面部分的转换方法在计算机底层知识之二进制中介绍过。只需将各数位数值和位权相乘,然后再将相乘的结果相加即可实现。其实,针对小数点后面的部分,也是照猫画虎,也是将各数位数值和位权相乘的结果相加即可。
二进制数小数点前面部分的位权
- 第一位是
2
的0
次幂 - 第二位是
2
的1
次幂 - 第三位是
2
的2
次幂 - 以此类推
而小数点后面部分的位权
- 第一位是
2
的-1
次幂 - 第二位是
2
的-2
次幂 - 第三位是
2
的-3
次幂 - 以此类推
0次幂前面的位的位权按照
1
次幂、2
次幂····的方式递增0次幂后面的位的位权按照
-1
次幂、-2
次幂····的方式递减
计算机运算出错的原因
计算机运算出错的原因:有一些十进制数的小数无法转换成二进制
小数点后4位用二进制数表示时的数值范围为0.0000~0.1111
。这里只能表示0.5
、0.24
、0.125
、0.0625
这四个二进制数小数点后面的位权组合而成(相加总和)的小数。
可以看出:二进制数是连续的,十进制数是非连续的
在前面讲二进制
的时候,我们说,根据IC
引脚个数不同,我们可以表示位数不同的二进制数。我们可以通过增加引脚数,也就是增加二进制小数点后面的位数,与其相对应的十进制数的个数也会增加,但是不管增加多少位,2的-〇〇
次幂怎么相加都无法得到0.1这个结果。
实际上,十进制数0.1
转换成二进制后,会变成0.00011001100···
(1100
循环)这样的循环小数。这和用十进制数来表示1/3
是一样的道理。
计算机这个功能有限的机器设备,是无法处理无限循环的小数的
因此,在遇到循环小数时,计算机就会根据变量数据类型所对应的长度将数值从中间截断或者四舍五入。
然后,我们再结合我们上面的例子,一个循环小数在进行存储的时候,已经被掐头去尾,而偏偏针对这个值,又进行了N
多次处理。不怕你不努力,就怕你,持之以恒的向偏离既定轨道的方向上移动,那么结果可想而知,是永远不会达到最终想要的结果。
浮点数
像1011.0011
这样带小数点的表现形式,在计算机内部是无法使用的。
很多编程语言中都提供了两种表示小数的数据类型,分别是双精度浮点数和单精度浮点数。
- 双精度浮点数用
64
位表示小数 - 单精度浮点数用
32
位表示小数
浮点数是指用符号、尾数、基数和指数这四部分表示的小数。
计算机内部使用的是二进制数,所以基数是2,因此,实际的数据中往往不考虑基数。只用符号、尾数、指数这三部分就可以表示浮点数。
浮点数表现形式
浮点数的表现方式有很多中,我们采用IEEE标准
来解释。
双精度浮点数和单精度浮点数在表示同一个数值时使用的位数不同。
符号部分是指使用一个数据位来表示符号。数据位是1时表示负,为0时表示正或者0
数值的大小用尾数部分和指数部分来表示。即用尾数部分 × 2的指数部分次幂的形式来表示。
- 尾数部分用的是将小数点前面的值固定为1的正则表达式
- 指数部分用的是EXCESS系统表示
正则表达式和EXCESS系统
尾数部分
尾数部分使用正则表达式,可以将表现形式多样的浮点数统一为一种表现形式。
例如,十进制数0.75
就有很多表现形式。
虽然他们表示的都是同一个数值,但因为表现方法太多,计算机在处理时会比较麻烦。
因此,需要制定统一的规则:
十进制数的浮点数应该遵循:小数点前面是0,小数点后面第一位不能是0
也就是说,只能用尾数部分是0.75
、指数部分是0
的方法来表示。即0.75 × 100
在二进制数中,我们规定:将小数点前面的值固定为
1
的正则表达式
具体来讲,就是将二进制数表示的小数左移或右移(逻辑移位
)数次后,整数部分的第一位变成1,第二位之后都变成0。
而且,第一位的
1
在实际的数据中不保存,因此省略该部分后就可以节省一个数据位,从而可以表示更多的数据范围。
我们,看一下1011.0011
如何用单精度浮点数的正则表达式来表示尾数部分。
指数部分
指数部分中使用的是EXCESS
系统,使用这种方式主要是为了表示负数时不使用符号位。
在某些情况下,在指数部分,需要通过负〇〇次幂的形式来表示负数。
EXCESS系统表现是指,通过将指数部分表示范围的中间值设置
0
,使得负数不需要用符号来表示。
也就是说,当指数部分是8位单精度浮点数
时,最大值11111111=255
的1/2
,即01111111=127
(小数部分舍弃)表示的是0
。
我们再来一个例子说明。假设有这样一个游戏,用1~13
(A~K
)的扑克牌来表示负数。此时,我们把中间的7
当做0
。 那么10
表示+3
,3
表示-4
。
实际运用
我们来一起看看如何用单精度浮点数来表示十进制数0.75
。
- 符号位:因为
0.75
是正数,所以符号位是0
0.75
转换成二进制正则表示为1.1×2-1
,按照前面介绍的就很容易知道下面的各个数值。
- 指数部分:为
-1
,但是用EXCESS
表示的话,就变成了01111110
。换算为十进制为126
。而EXCESS
系统中,126
代表-1
- 尾数部分:根据正则表达式的规则,小数点前面的第1位是
1
,因此尾数部分1000···
实际上表示的是1.1000···
二进制数和十六进制数关系
在以位为单位表示数据时,使用二进制数很方便,但如果位数太多,看起来很麻烦。因此,在实际程序中,经常用十六进制数来替代二进制数。
在一些高级语言中,只需要在数值的开头加上0x
就可以表示十六进制数。
二进制数的4位,正好相当于十六进制数的1位。
由此可见,通过使用十六进制数,二进制数的位数能够缩短至原来的1/4
。
用十六进制数表示二进制小数时,小数点后的二进制数的4位也同样相当于十六进制数的1位。不够4位时用0填补二进制的低位
后记
分享是一种态度。
参考资料:《程序是怎样跑起来的》
全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。