C语言乘除原理

C语言整数乘除原理

最近在阅读CSAPP时,发现其中讲解乘除原理这一小块内容还蛮有意思的,故自己写一遍帮助理解。

乘以常数

无符号(Unsigned)乘法

由于整数的乘法在机器中的开销相对于加减和移位来说慢得多,因此考虑将乘法转化为加减和移位可以减小开销,提高速度
对于unsigned整数乘以2k2^k2k的计算来说,就相当于将改整数向左移k位,下面给出证明:
假设该整数的二进制表示为
[xw−1,xw−2,xw−3,xw−4,⋯ ,x2,x1,x0] [x_{w-1},x_{w-2},x_{w-3},x_{w-4}, \cdots , x_2, x_1, x_0] [xw1,xw2,xw3,xw4,,x2,x1,x0]
则将其转化为十进制的算式为:
B2U([xw−1,xw−2,xw−3,xw−4,⋯ ,x2,x1,x0])=∑i=0w−1xi⋅2i B2U([x_{w-1},x_{w-2},x_{w-3},x_{w-4}, \cdots , x_2, x_1, x_0])=\sum_{i=0}^{w-1}x_i \cdot 2^i B2U([xw1,xw2,xw3,xw4,,x2,x1,x0])=i=0w1xi2i
将其乘以2k2^k2k我们可以得到如下推导
∑i=0w−1xi⋅2i⋅2k=∑i=0w−1xi⋅2i+k \begin{align*} \sum_{i=0}^{w-1}x_i \cdot 2^i \cdot 2^k= \sum_{i=0}^{w-1}x_i \cdot 2^{i+k} \end{align*} i=0w1xi2i2k=i=0w1xi2i+k
这就相当于将原来的数字左移k位,余位用0补全,用二进制表示为:
U2B(∑i=0w−1xi⋅2i+k)=[xw−1,xw−2,xw−3,xw−4,⋯ ,x2,x1,x0,0k−1,0k−2,⋯ ,02,01,00] \begin{align*} & U2B(\sum_{i=0}^{w-1}x_i \cdot 2^{i+k}) \\ = & [x_{w-1},x_{w-2},x_{w-3},x_{w-4}, \cdots , x_2, x_1, x_0,0_{k-1},0_{k-2} , \cdots,0_2,0_1,0_0] \end{align*} =U2B(i=0w1xi2i+k)[xw1,xw2,xw3,xw4,,x2,x1,x0,0k1,0k2,,02,01,00]
由此确定unsigned整数乘以2k2^k2k等价于其相应二进制数向左移动k位,我们也可以举一个具体的例子来验证:

将乘以二的幂次的运算转化为位移运算可以在大部分情况下减小开销,那么如果乘以一个任一的整数呢?
由前面二进制转化为十进制的运算我们可以发现任意一个整数都可以转化为多个二的幂次运算之和,而乘法满足分配律。
因此,我们可以得到以下推导:
X⋅Y=[xw−1,xw−2,xw−3,xw−4,⋯ ,x2,x1,x0]⋅[yλ−1,yλ−2,⋯ ,y0]=[xw−1,xw−2,xw−3,xw−4,⋯ ,x2,x1,x0]⋅(yλ−1⋅2λ+yλ−2⋅2λ−1+⋯+y0⋅20)=yλ−1⋅[xw−1,xw−2,xw−3,xw−4,⋯ ,x2,x1,x0]⋅2λ+yλ−2⋅[xw−1,xw−2,xw−3,xw−4,⋯ ,x2,x1,x0]⋅2λ−1 +⋯+y0⋅[xw−1,xw−2,xw−3,xw−4,⋯ ,x2,x1,x0]⋅20 \begin{align*} & X \cdot Y \\ = & [x_{w-1},x_{w-2},x_{w-3},x_{w-4}, \cdots , x_2, x_1, x_0] \cdot [y_{\lambda - 1},y_{\lambda - 2}, \cdots , y_0] \\ = & [x_{w-1},x_{w-2},x_{w-3},x_{w-4}, \cdots , x_2, x_1, x_0] \cdot (y_{\lambda -1} \cdot 2^{\lambda} + y_{\lambda -2} \cdot 2^{\lambda - 1} + \cdots + y_0 \cdot 2^0) \\ = & y_{\lambda -1} \cdot[x_{w-1},x_{w-2},x_{w-3},x_{w-4}, \cdots , x_2, x_1, x_0] \cdot 2^{\lambda} \quad + \\ & y_{\lambda -2} \cdot[x_{w-1},x_{w-2},x_{w-3},x_{w-4}, \cdots , x_2, x_1, x_0] \cdot 2^{\lambda - 1} \, + \\ & \qquad \qquad \qquad \qquad \qquad \cdots \qquad \qquad \qquad \qquad \qquad \quad+ \\ & y_{0} \quad \cdot[x_{w-1},x_{w-2},x_{w-3},x_{w-4}, \cdots , x_2, x_1, x_0] \cdot 2^{0} \\ \end{align*} ===XY[xw1,xw2,xw3,xw4,,x2,x1,x0][yλ1,yλ2,,y0][xw1,xw2,xw3,xw4,,x2,x1,x0](yλ12λ+yλ22λ1++y020)yλ1[xw1,xw2,xw3,xw4,,x2,x1,x0]2λ+yλ2[xw1,xw2,xw3,xw4,,x2,x1,x0]2λ1++y0[xw1,xw2,xw3,xw4,,x2,x1,x0]20
由于y_i只能为01这样就可以把整数乘法分解成多个整数位移之和
当然,像14这样的数字,不仅可以转化为23+22+212^3 + 2^2 + 2^123+22+21还可以表示为24−212^4 - 2^12421进一步减小运算量
由于无符号乘法的溢出也是通过截断高位来实现的,因此,位移在溢出的情况下得到的结果也相同

补码乘法

对于补码来说,由于无符号乘法和补码乘法的位级等价性,同时溢出也是通过截断高位实现,因此,补码位移也等价于补码的乘法

除以2的幂

在大多数机器上,整数的除法开销还要多余乘法开销,因此除以2的幂时使用移位运算来实现除法也可以减小开销。
但在作除法时,会遇到除不断的情况,此时c语言是如何处理的呢?我们可以先举个例子:

我们可以看到,不论是整数还是负数,c语言的除法都只将小数截断,即对于负数向上取整,对于正数向下取整

无符号除法

对于无符号的数来说,就相当于正数,因此右移就是逻辑右移,得到的结果一定是正确的,推导如下:
计算:x÷2kx \div 2^kx÷2k令,
x=[xw−1,xw−2,xw−3,xw−4,⋯ ,x2,x1,x0]x′=[xw−1,xw−2,xw−3,xw−4,⋯ ,xk]x′′=[xk−1,⋯ ,x2,x1,x0] x = [x_{w-1},x_{w-2},x_{w-3},x_{w-4}, \cdots , x_2, x_1, x_0]\\ x' = [x_{w-1},x_{w-2},x_{w-3},x_{w-4}, \cdots , x_k]\\ x'' = [x_{k-1}, \cdots , x_2, x_1, x_0] x=[xw1,xw2,xw3,xw4,,x2,x1,x0]x=[xw1,xw2,xw3,xw4,,xk]x′′=[xk1,,x2,x1,x0]
x=2k⋅x′+x′′\quad x = 2^k \cdot x' + x'' \quadx=2kx+x′′,由于0≤x′′≤2k\quad 0 \leq x'' \leq 2^k \quad0x′′2k因此,x′′÷2k<1\quad x'' \div 2^k < 1 \quadx′′÷2k<1即为小数部分,可以得出x′x'x就是除法的正确结果,逻辑右移与除法等价

补码除法

对于补码除法,如果是正数则与无符号除法无异,而对于负数,我们可以先举个例子看看:

欸?我们发现对于移位来说,负数除法似乎变成像下取整了,这是为什么?
我们可以从数轴上数的关系来看:
对于补码奇数除法与其在数轴上左边相邻偶数除法结果相同。可以举个例子来理解:
对于-7-8,对应的二进制补码为1111 10011111 1000(对于负数补码来说,右移高位补1)可以观察到就末位相差1,而当他们同时除以2时,右移动一位得到相同的结果,如果移动更多位也可以用相似的方式说明,这也就解释了为什么对于负数来说,右移是向下取整。
那么回到C语言中,要实现向零取整应该怎么做呢?
这时候就需要用到偏置技术,如果用⌊⌋\lfloor \quad \rfloor表示向下取整,⌈⌉\lceil \quad \rceil表示向上取整,则偏执技术的核心是这个公式:
⌈x÷y⌉=⌊(x+y−1)÷y⌋(x>y,x,y∈Z,y>0) \lceil x \div y \rceil = \lfloor (x + y - 1) \div y \rfloor (x>y,x,y\in Z,y>0) x÷y=⌊(x+y1)÷y(x>y,x,yZ,y>0)
当x为负数时,我们需要的是⌈x÷y⌉\lceil x \div y \rceilx÷y而仅仅依靠位移能得到的只是⌊x÷y⌋\lfloor x \div y \rfloorx÷y,因此我们需要在做除法之前加上一个偏置y−1y-1y1,令x=ay+b(0≤b<y)x = ay + b(0 \leq b < y)x=ay+b(0b<y) :
⌈(x+y−1)÷y⌉=⌊(ay+b+y−1)÷y⌋=a+⌊(b+y−1)÷y⌋={a+1(b≠0)a(b=0)=⌈x÷y⌉ \begin{align*} \lceil (x + y - 1) \div y \rceil & = \lfloor (ay + b + y - 1) \div y \rfloor \\ & = a + \lfloor (b + y - 1) \div y \rfloor \\ & = \left\{ \begin{align*} & a + 1 && (b \neq 0) \\ \\ & a && (b = 0) \end{align*} \right. \\ & = \lceil x \div y \rceil \end{align*} ⌈(x+y1)÷y=⌊(ay+b+y1)÷y=a+⌊(b+y1)÷y=a+1a(b=0)(b=0)=x÷y
至此,我们已经证明加上偏置后作除法就相当于原数向上取整,用在负数的位移上效果也相同。因此,C语言在计算负数二的幂次除法时需要加上偏置
由于除法不满足分配律,因此,除了正二次幂的除法之外,不能使用上述方法,这里也不作其他讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值