JS基础-数字-0.1+0.2!=0.3(二)

如果有人问你,计算机和计算器的区别是什么,要怎么回答?
最简单的回答就是:
计算机相当于巴贝奇发明的分析机。
计算器相当于巴贝奇发明的差分机。

不过今天可不是要说这个问题,抛出这个问题这是为了引出今天的主题,计算机大致是怎么运算的,对分析机和差分机有兴趣的话可以自己查阅,这个值得你花些时间,因为在计算机领域,不能只知道图灵,而不知道巴贝奇。

那么,就根据上一篇的理论部分《JS基础-数字-0.1+0.2!=0.3(一)》
,结合实物,来做一个最简单的加法器。

这是一个最简单的电路,我相信没有人看不懂,那我就不多说了。

继电器

这个装置是一个简单的继电器,原理非常简单:
符号V电压在此代表通电的线路。
当输入通电后,电磁铁就会带磁性,把带弹性的金属条吸下来连接B,使V与B连通,输出就带电了。
当输入断电后,电磁铁失去磁性,带弹性的金属条回到了A,输出就没有电流了。

现在把两者结合起来,就出现了以下装置:


这玩意有什么用?可以用来制作一些逻辑原件。

1. 与门电路(AND)

与门

这个装置叫做“与门电路”(AND),就是左边两个输入都有电流时,右边才有输出,灯泡才能亮。

js中的逻辑操作符 && 就是按照“与门”原理来的,&& 操作就是两个操作值都为true,结果才为真,只要有一个为false,结果就为false。

2. 或门电路(OR)

或门

这个装置叫做“或门”(OR),就是左边两个输入只要一个有电流(或者都有电流),右边就有输出,灯泡就能亮。

js中的逻辑操作符 || 就是按照“或门”原理来的,|| 操作就是两个操作值只要有一个为ture(或者同时为true),结果就为真。

3. 与非门电路(NAND)

与非门

这个装置叫做“ 与非门”(NAND),NAND 就是 NOT AND 的意思(AND 的 取反),左边必须都输入电流时灯泡才不亮,其余情况灯泡都亮。

这个装置相当于js中,使用 && 操作完之后再取反, !( A && B)

4. 或非门电路(NOR)

或非门

这个装置叫做“ 或非门”(NOR),NOR 就是 NOT OR 的意思(OR 的 取反),左边当两个输入只有当都没有电流时,灯泡才亮,其余情况灯泡不亮。

这个装置相当于js中,使用 || 操作完之后再取反, !( A || B)

注:本章并不涉及或非门电路(NOR)的使用,但是为了体现完整性,需要介绍,特此说明。

了解之后,需要简化一下这些电路,因为在图上作业时,画起来很不方便。

逻辑门简化图集合

尝试用逻辑电路做2进制加法

现在要计算 15 + 15 = 30,这个很容易,那么2进制加法该怎么做?
首先肯定要把 15 转化成2进制 为 1111(2),然后套用10进制的竖式加法方式来计算,如下


这种计算的特点是:同位相加,并且从低位开始,求出当前位的结果(和),和进位的值。
进位的情况只有一种:
同位都为1时,进位1。其他情况都进位0;
结果(和)的情况稍微复杂一点:
同位都为1时, 结果(和) 0;
同位都为0时, 结果(和) 0;
同位只有一个1时,结果(和)1;

把有电流代表1,没有电流代表0,对于进位操作,一个与门电路就可以搞定。

但是,同位相加的结果(和)并没有哪一个单一逻辑电路能够实现,需要重新组装一个逻辑电路才可以。

5. 异或门电路(XOR)

异或门

异或门电路由三个基础逻辑电路组成(或门,与非门,与门),这个需要好好捋顺下思路,从头到尾把 00, 01,10,11四种电流输入的情况走一遍,一点也不难。


异或门电路所有情况的输出流程

这样的输出结果就和想要的2进制加法同位相加结果一致了。

6. 半加器

由此,可以组装出2进制加法中,一个位的结果(和)和进位的装置,称之为“半加器”。

半加器

为什么叫“半加器”?因为他还只是一个半成品,先求个和来验证一下,还是拿 15 + 15 举例吧

半加器无法完成计算

现在需要一个输入端有三个入口的装置,两个同位的值入口 + 进位值入口,不用担心,有办法解决。

7. 全加器

全加器.png

这一块稍微有点绕,还是从头一步一步来,
1、第一个半加器没啥好解释的,他会输出同位的结果值(和)和进位值。
2、假设上一位有进位输入1,那么这个1,一定是与当前位的结果值(和)在第二个半加器处相加,不能和当前为的进位值相加,对吧,这是必须的,因为手写竖式的逻辑就是这样的。
3、第二个半加器的结果值(和)就是最终的结果值(和)了。
4、这一步最重要,两个半加器的进位输出居然用一个或门连接,为什么?你可以认真的思考一下,第一个半加器和第二个半加器的进位输出,会同时为1吗?这是不可能发生的事!!

8. 加法器

如果要计算1111(2) + 1111(2),就可以使用4个全加器(FA)连接来完成运算,设,上排4个加数从左到右依次为 A3、A2、A1、A0,下排4个加数从左到右依次为 B3、B2、B1、B0,那么如下图依次输入全加器,就可以得到结果值。

4位加法器

那么这就组成了一个4位的加法器,计算机都是8位为一个基本单元的,所以也可以组装成标准的8位加法器。

8位加法器

上图中A输入为一个8位2进制加数,B输入也是一个8位2进制加数,只不过排布不一样,原理和最开始的4位加法器一致,它可以用作两个8位2进制加数的求和。

如果需要位数更大的加法器,以便一次性计算更大的加数,比如16位加法器,可以向如下这样把两个8位加法器相连。

16位加法器

到这,是不是可以联想到32位,64位,甚至更多位的加法器如何拼装了呢?
当然,现代计算机已经不使用继电器了,从继电器,到电子管,再到晶体管,不过无论是什么材料器件,他们的计算原理都是这样的。

9. 定点机

当然,在此展示的只是整数的加法运算,但小数的加法运算也依然如此,如果计算:
0.101(2) + 0.001(2),只需要计算101 + 001 即可,把计算小数的加法器叫做小数定点机,之所以叫定点机,是因为0.是不考虑的,直接默认在定点机的最前面

例如使用8位小数定点机计算0.101(2) + 0.001(2),那么两个数要分别转换成
1010 0000 和 0010 0000,然后再参与运算,如果进位了,可以将进位的1输出到其他寄存器中,或者直接舍弃,根据具体需要而定。

而前文展示的其实就是整数定点机的运算,整数定点机默认小数点在最右边,和小数定点机刚好相反。

注意:整数定点机和小数定点机只是叫法上方便区分,实际上逻辑电路配置是一样的。这一点你应该想到。

10. 浮点数运算

而对于既有整数又有小数的原始数据(称之为浮点数),如果使用定点机进行运算,需要设定一个比例因子,数据按比例因子缩小成定点小数或扩大成定点整数再参加运算,结果输出时再按比例折算成实际值。

比例因子必须选择恰当。选择不当,将会影响运算精度,或者会使运算结果超出机器所能表示的数据范围,即出现溢出。

例如:
在定点小数机器中计算 11.01+10.01 选择比例因子=0.01,可将两操作数变换为 0.1101+0.1001 但 0.1101+0.1001=1.0110,运算结果不是纯小数,出现了机器数不能表示的数,即出现了正溢出。 如果选择比例因子 =0.001,可将两操作数变换为 0.01101+0.01001 则运算结果 0.01101+0.01001=0.10110 为正常结果。将0.10110除以比例因子,可得到正确结果 101.10。

这种计算浮点数的方式效率极其低下,现代计算机早已使用浮点运算器来处理浮点数运算,且JS所有数字都是使用浮点数运算器来进行运算的(当然也就包含0.1 + 0.2的运算)。浮点运算器的逻辑配置极其复杂,有自己独立的运算硬件,本人知识储备有限,就不展开说明了,但是肯定是建立在前面所说的基本逻辑之上的。未来可能会写浮点运算器硬件相关内容。

*11. 如何计算减法

这一部分说明补码由来相关,因为在第四章会涉及到补码概念,所以和0.1 + 0.2 的主题有一定关系。但这不是必须的,完全可以跳过。

前面配置的逻辑电路是针对于加法的,但是减法的逻辑电路如何实现呢。
补数
如果要确定两个数是不是互为补数,需要有一个参照,或者说需要一个标量,比如:
4 和 3 互为补数,是相对于标量7来说的,一般称作“4的7的补数为3” 或者 “3的7的补数为4”,因为 4 + 3 = 7。如果标量是 8, 4 和 3 就不是互为补数。
同理:

2的10的补数为8,
2的100的补数为98。
21的1000的补数为979。

7的9的补数为2。
7的99的补数为92。

注意,补数没有正负的概念。

补数有啥用?

口算:
997 + 973 = ?
(1000 - 3) + 973 = 1970
这个是在口算数学加法的时候用的方法,在计算机中利用补数能干啥呢,做减法。

145 - 98 = ?
在这个减法中,一定要用到借位,如果要使用逻辑电路计算的话,要先判断同位数值的大小,小的话就要加上一个值,上一位呢就要减去一个值,非常复杂,如果不需要借位做减法就好了对吧,那么就可以用到补数概念。

= 145 - (999 - 901
= 145 + 901 - 999
= 1045 + 901 + ( 1 - 1000)
= 1047 - 1000
= 47

估计你已经绕晕了,磨磨唧唧的这么算到底有啥好处?目的只有一个,就是不用借位算减法,901就是 98 的 999 的补数。为啥选择999呢?因为10进制中,没有单个数字比9更大了,又因为 145 为 3位,所以选择 999。这样做减法,一定不会借位。

转化成2进制计算呢?

在这里,原来的补数标量 999 要换成 11111111,因为2进制中,单个数字没有比1更大的了,又因为 145 转化成 2进制为 8位,所以要用 11111111。
98转化成2进制为 01100010。
01100010 的 11111111 的补数,就是用 11111111 - 01100010,到这里先停一下,仔细看这个2进制减法。

这个减法实际上就是把 01100010 各位取反了,这就是2进制的神奇所在,也是2进制减法的精髓所在,因为做一个取反的电子原件实在太容易了。

反向器

这个玩意叫做“反向器”,它不算是逻辑电路,但是同样重要,可以用它来做取反的操作。

类似于js中的一元操作符 !

8位反向器

= 1001 0001 - ( 1111 1111 - 1001 1101)
= 1001 0001 + 1001 1101 - 1111 1111
= 1001 0001 + 1001 1101 + ( 1 - 1 0000 0000)

为什么把 - 1111 1111 分解成 1 - 1 0000 0000
按照上述方法计算减法时,如果 被减数减数 数值大,结果一定是1 xxxx xxxx(x表示1或者0的值)的9位2进制数,不信你可以验证一下o( ̄︶ ̄)o。
1 xxxx xxxx - 1 0000 0000 = xxxx xxxx
这样,计算结果的进位输出1,忽略不计就行了。

因此,现在这个计算方法,只能计算被减数减数 大的情况,这点要注意。

在这又得停一下,因为可以使用加法器来完成后续操作了。

你可以验证一下和输出的 0010 1111 是不是 47。CO输出的进位1可以和要减去的 1 0000 0000抵消掉。如果 减数被减数数值大,CO输出就是0,此时的计算结果错误,就需要特殊处理了。

如何优化

初始时的进位输入1其实大可不必,可以把他直接合并到补数10011101中:

1001 0001 + (1001 1101 + 1) - 1 0000 0000
1001 1110,这样进位输出就节省出来了,在输入数值时直接输入1001 1110即可。

原码 和 补码

计算机的真实计算情况与所展示流程还是有点区别,先来看下什么是原码。

原码:计算机中只能识别0和1,不过也巧了,数字也只有正负2种,没有第三种,刚好合适,那么正负数也就能用0和1来区分。科学家们规定,0代表正数,1代表负数,放在数值的最高位上,也就是最左边。

拿8位为例,拿出一个位来表示正负,就还剩7位来表示数值,7位能表示 27 = 128 种状态,从0开始就是0 ~ 127,所以8位能代表的数值为 -127 ~ ±0 ~ 127,如下图所示。

8位原码

原码不能直接参与运算,因为 1111 1110 + 0000 0001 = 1111 1111 ,-126 + 1居然等于-127,这明显不对啊,所以原码一般用于输入输出数据时使用,因为人眼可以很容易区分数值,但是计算机不能运算。那么运算使用什么编码?

补码:计算机运算使用补码,补码的规则是在原码的基础上变形而来:

①如果值为正数,那么补码就是原码本身。

127转化成补码

②如果值为负数,那么补码就是其值绝对值的原码各位取反,然后 + 1。

-127转化成补码

补码和原码对比一下:

补码和原码对比示意图

相同点:补码和原码都是使用 0代表正数,1代表负数

不同点:原码存在±0,而补码只有一种0,就是机器0(所有位数都为0称为机器0),原码中的-0在补码中表示一个绝对值最大的负数。

补码最大的优点就是:无论加法和减法,都统一按照加法来计算,但是注意,
补码与补码相加,结果依然是补码

找几个例子试一下:
+127 + (-128)
= 0111 1111 + 1000 0000
= 1111 1111
结果1111 1111就是一个补码,但是我们还是习惯于看原码。
补码转换成原码和原码转化成补码规则差不多:

①如果符号位是0,那么这个补码就是原码,
②如果符号位是1,就符号位不变,各位取反,然后 + 1

1111 1111(补) => 1000 0000 + 1 => 1000 0001(原),最终结果为 -1。

疑惑

①两个负数相加,进位输出一定为1,那么这个进位1怎么办?
答:直接忽略不计,这也正是保证补码计算准确的关键。

②如果两个数相加大于127或者小于128了怎么办?
答:这种情况就是溢出,溢出也很好判断,两个正数相加,如果符号位变化了,就说明溢出了,两个例子:

127 + 127
= 0111 1111 + 0111 1111
= 1111 1110
正数相加,符号位变化了,就是向上溢出。

-128 - 128
= 1000 0000 + 1000 0000
= 0000 0000(超出8位的直接舍弃)
负数相加,符号位变化了,也是向上溢出。

8位范围内的两个补码相加,如果一个为正数,一个为负数,是不可能发生溢出的。想想是不是这么回事。加法没有向下溢出的情况。

在 145 - 98 = ?这个例子中,实际上对于 -98 的处理就是补码,只不过没有加入符号位的概念,计算机中并不是配置不出通过“借位”实现的减法器,而是没有必要有,这一点希望你能体会到,那么关于补码和原码的说明就到这吧,如果还有疑惑可以查阅相关资料,网上相关的资料还是很多的。

12. 总结:

本章所写内容无疑是“管中窥豹”,根本是让大家对计算机硬件运算了解一二,下一章将进入0.1 + 0.2 的最主要部分,IEEE754浮点数运算标准的说明。

对于乘法、除法如何计算,因为不涉及0.1 + 0.2运算,时间允许的话,在本系列最后会简要说明。

你可能感兴趣的:(JS基础-数字-0.1+0.2!=0.3(二))