zl程序教程

您现在的位置是:首页 >  硬件

当前栏目

计算机底层知识之处理小数

计算机 处理 知识 底层 小数
2023-06-13 09:16:00 时间

❝生活中最大的障碍之一就是对羞辱的恐惧 ❞

大家好,我是「柒八九」

今天,我们继续「计算机底层知识」的探索。我们来谈谈关于「小数运算」的相关知识点。

如果,想了解该系列的文章,可以参考我们已经发布的文章。如下是往期文章。

文章list

  1. 计算机底层知识之CPU
  2. 计算机底层知识之二进制

你能所学到的知识点

  1. 计算机精度缺失 「推荐阅读指数」 ⭐️⭐️⭐️
  2. 如何用二进制表示小数 「推荐阅读指数」 ⭐️⭐️⭐️⭐️⭐️
  3. 计算机精度缺失的原因
  4. 浮点数 「推荐阅读指数」 ⭐️⭐️⭐️⭐️⭐️
  5. 正则表达式和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这个有小数点的二进制数转换成十进制数。

小数点「前面」部分的转换方法在计算机底层知识之二进制中介绍过。只需将各「数位」数值和「位权」相乘,然后再将相乘的结果相加即可实现。其实,针对小数点后面的部分,也是「照猫画虎」,也是将各「数位」数值和「位权」相乘的结果相加即可。

二进制数小数转换成十进制数

二进制数小数点前面部分的「位权」

  • 第一位是20次幂
  • 第二位是21次幂
  • 第三位是22次幂
  • 以此类推

而小数点后面部分的「位权」

  • 第一位是2-1次幂
  • 第二位是2-2次幂
  • 第三位是2-3次幂
  • 以此类推

❝0次幂前面的位的位权按照1次幂、2次幂····的方式「递增」 0次幂后面的位的位权按照-1次幂、-2次幂····的方式「递减」


计算机运算出错的原因

❝计算机运算出错的原因:「有一些十进制数的小数无法转换成二进制」

小数点后4位用二进制数表示时的数值范围为0.0000~0.1111。这里只能表示0.50.240.1250.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=2551/2,即01111111=127(小数部分舍弃)表示的是0

我们再来一个例子说明。假设有这样一个游戏,用1~13(A~K)的扑克牌来表示负数。此时,我们把「中间」7当做0。那么10表示+33表示-4

单精度浮点数指数部分的EXCESS系统表现

实际运用

我们来一起看看如何用单精度浮点数来表示十进制数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填补二进制的低位」


后记

「分享是一种态度」

参考资料:《程序是怎样跑起来的》

「全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。」