> 文档中心 > 读书笔记Pt.7——《深入理解计算机系统》

读书笔记Pt.7——《深入理解计算机系统》

目录

    • 传统艺能😎
    • 补码乘法🤔
    • 整数乘法🤔
    • 整数除法🤔
    • 关于整数运算的最后思考🤔

传统艺能😎

小编是双非本科大一菜鸟不赘述,欢迎大佬指点江山(QQ:1319365055)
此前博客点我!点我!请搜索博主 【知晓天空之蓝】

🎉🎉非科班转码社区诚邀您入驻🎉🎉
小伙伴们,打码路上一路向北,背后烟火,彼岸之前皆是疾苦
一个人的单打独斗不如一群人的砥砺前行
这是我和梦想合伙人组建的社区,诚邀各位有志之士的加入!!
社区用户好文均加精(“标兵”文章字数2000+加精,“达人”文章字数1500+加精)
直达: 社区链接点我

🎉🎉🎉倾力打造转码社区微信公众号🎉🎉🎉
在这里插入图片描述


在这里插入图片描述

补码乘法🤔

范围在 0= 2^w -1 内的整数可表示 w 位的补码数,但是他们乘积 x*y 的范围在 【-2(w-1)*2(w-1) -1),-2(w-1)*-2(w-1)】,可能需要 2w 位来表示结果。C语言中有符号乘法结果被定义为 w 位值,就是用 2w 的数截断为 w 位来表示,w 位补码乘法结果为:在这里插入图片描述
对于无符号乘法而言,运算的位级表示是一样的,,机器可以用一种乘法指令来进行有符号数和无符号数的乘法。

我们以不同的三位数字乘法,对每一对位级运算数,我们执行无符号和补码乘法会得到 6 位结果最后截断成 3 位,无符号截断后的乘积为 x*y mod8,虽然两种乘法乘积的 6 位表示不同,但截断后乘积的位级表示都相同。

在这里插入图片描述
模运算所以权重 2^w 都丢掉了,因此补码和无符号乘积低位是相同的,下面是补码运算过程:在这里插入图片描述

整数乘法🤔

大多数机器上,整数的乘法指令相当慢,相比之下加减,位级,移位等操作相当于他的 10 倍,因此编译器采用了一套非常重要的优化,尝试用移位和加法组合来替代直接的乘法。首先,我们会考虑乘以 2 的幂,再概括成乘以任何数。

这个模式下我们推导出 w 位的无符号整数 x 的属性,对于 k<w ,我们可以将移位后的位截断到 w,所以 x<< k 等价于 x*2^k:

在这里插入图片描述
注意,无论是无符号数还是补码运算,乘以 2 的幂都有可能溢出,结果表明,及时溢出的时候我们通过移位的结果也是一样的。由于整数乘法比移位和加法的代价大的多,许多C语言编译器试图一移位,加减法的组合来消除整数乘以常数的情况。

比如 x*14,利用等式 14 = 23+22+2^1,编译器会将乘法重写成 (x<<3)+(x<<2)+(x<<1),实现一个将乘法替换为 3 个移位和 2 个加法。无论 x 是无符号还是补码,甚至当乘法会导致溢出时,两个计算都会得到一样的结果,更好的方法是编译器还可以利用属性 14 = 24-21,将乘法重写成 (x<<4)-(x<<1),这时就更加精简了。

整数除法🤔

大多数机器上,整数除法会比整数乘法更慢大约 3 倍的周期,除以 2 的幂可以利用移位来实现,只不过是右移了,无符号数和补码分别使用逻辑移位和算术移位来达到目的。

整数除法总是输入到 0,对于 x >= 0,y > 0 结果是 x/y,这里对于任何实数 a,a’ 定义为唯一的整数,a’ <= a< a’+1,比如 3.14’ = 3,-3.14’ = -4,而 3’ = 3.

考虑一个无符号数执行路逻辑右移 k 位,这和除以 2^ k 有一样的效果,我们还给出了如果用真正的运算去做除法得到的结果,移位总是舍入到 0,和整数除法的规则是一样的。

为证明逻辑右移和除以 2 的幂之间的关系,设 x 为位模式【x(w-1),x(w-2),……,x0】表示无符号数整数,而 k 的取值范围 【0<= k < w】,设 x’ 为 w-k 的位表示 【x(w-1),x(w-2),……,xk】 的无符号数,我们可以推出关系 x’ = x/2^k。

总结为对于无符号变量 x,C表达式 x >> k 等价于 x/2^k
在这里插入图片描述
现在考虑一个补码数进行算术右移的结果,对于一个正整数最高有效位为 0,所以效果和逻辑右移是等价的,因此,对于非负数而言,算术右移 k 位与 除以 2^k 是一样的,如果是一个负数,比如 -12340 的 16 位,表示算术右移不同位数的结果,结果和除以 2 的幂是一样的,对于不需要舍入的情况,结果是正确的,但是一旦需要舍入,移位导致结果向下舍入,而不是像规则需要那样向 0 舍入,比如 -7/2 会得到 -3,

x>=0 时不需要舍入,结果就是我们期望的值,不过对于 x0 ,整数除法结果应该为 x/y ,这里对于任何实数 a,a’ 定义为唯一的整数,也就是说整数除法应该将为负的结果向上朝 0 舍入,舍入发生时,将一个负数右移 k 位不等价于把他除以 2^k ,这也证实了我们刚刚的结论。

我们可以在移位之前偏置一下,通过这种方法来修正这种不合适的舍入,这种计数利用的是对于整数 x 和任意 y>0 的 y,有 x/y = (x+y-1)/y。比如 x =-30,y = 4,有 x+y-1 = -27,而 -30/4 = -7 = -27/4,x = -32 且 y = 4时,有 x+y-1 = -29,而 -32/4 = -8 = -29/4。我们假设 x = ky+r,这里 r 介于 0 到 y 之间,得到 (x+y-1)= k+(r+y-1)/y。也就是说,通过给 x 增加一个偏量 y-1,然后再将除法向下舍入,y 整除 x 时,我们得到 k,否则得到 k+1.因此对于 x<0, 我们在右移之前,先加上一个 (2^k)-1,旧梦得到正确的结果了,C表达式:
在这里插入图片描述
在执行算术右移之前加上一个适当的偏量是如何导致结果正确舍入的。我们给出了—12340 加上偏量值之后的结果。低k位左边的位可能会加1,也可能不会加1。对于不需要舍入的情况(k=1),加上偏量只影响那些被移掉的位。对于需要舍人的情况,加上偏量导致较高的位加1,所以结果会向零舍入。

关于整数运算的最后思考🤔

计算机执行的“整数”运算实际上是一种模运算形式。表示数字的有限字长限制了可能的值的取值范围,结果运算可能溢出。

补码表示提供了一种既能表示负数也能表示正数的灵活方法,同时使用了与执行无符号算术相同的位级实现,这些运算包括加法、减法、乘法,甚至除法,无论运算数是以无符号形式还是以补码形式表示的,都有完全一样或者非常类似的位级行为。

我们看到了C语言中的某些规定可能会产生令人意想不到的结果,而这些可能是难以察觉和理解的缺陷的源头。特别看到了unsigned数据类型,虽然它概念上很简单,但可能导致即使是资深程序员都意想不到的行为。我们还看到这种数据类型会以出乎意料的方式出现,比如,当书写整数常数和当调用库函数时。
在这里插入图片描述

今天就先到这里吧,润了家人们。