做网站需要什么花费网站建设营销型
文章目录
- 无符号加法
- 练习1
- 练习2
- 补码加法
- 练习1
- 练习2
- 练习3
- 练习4
- 补码的非
- 练习
- 无符号乘法
- 补码乘法
- 练习1
- 练习2
- 练习3
- 乘以常数
- 练习1
- 练习2
- 除以2的幂
- 练习1
- 练习2
- 关于整数运算的最后思考
- 练习
无符号加法
考虑两个非负整数x
和y
,满足0≤x,y<2w0 \le x, y < 2^w0≤x,y<2w,每个数都能表示为一个w
位的无符号数。如果要计算x+y
,其结果的可能取值范围是0≤x+y≤2w+1−20 \le x+y \le 2^{w+1}-20≤x+y≤2w+1−2,表示该值可能需要w+1
位。如果x+y
的结果又要和别的数做加法,那可能需要更多的位表示运算结果。这种字长膨胀意味着,要想完整地表示运算的结果,我们不能对整型长度做任何限制。
但实际情况是,在C
语言中,整型变量占固定大小的字节和位,当整数运算结果超出了整型变量的表示范围时,计算机运算的结果是截断后的值,与预期值有偏差。
我们定义操作+wu+^u_w+wu是w
位的无符号加法。
原理:无符号数加法。
对满足0≤x,y<2w0 \le x,y<2^w0≤x,y<2w的xxx和yyy,有:
x+wuy={x+y,0≤x+y≤2w−1x+y−2w,2w≤x+y≤2w+1−2\begin{align} x+^u_wy= \begin{cases} x+y,\quad &0 \le x+y \le 2^w -1 \\ x + y-2^w, \quad &2^w \le x+y \le 2^{w+1}-2 \end{cases} \end{align} x+wuy={x+y,x+y−2w,0≤x+y≤2w−12w≤x+y≤2w+1−2
说一个算术运算溢出,是指完整的整数结果不能被有限的整型长度所表示,产生了高有效位的丢失。
执行C
程序时,系统不会因运算发生溢出而自己报错,因此程序员必须关注该情况。
原理:检测无符号加法中的溢出。
对满足0≤x,y≤2w−10 \le x,y \le 2^w-10≤x,y≤2w−1的x
和y
,存在s=˙x+wuys\.=x+^u_wys=˙x+wuy,当且仅当s<xs < xs<x,s<ys<ys<y时,运算发生了溢出。
- 证明:当s<xs < xs<x,s<ys<ys<y时,运算发生了溢出。
因为x≥0x\ge 0x≥0,因此x+y≥yx+y \ge yx+y≥y,因此s≥ys \ge ys≥y,所以当s<ys<ys<y时,发生运算错误(即溢出)。
因为y≥0y \ge0y≥0,因此x+y≥xx+y \ge xx+y≥x,因此s≥xs \ge xs≥x,所以当s<xs<xs<x时,发生运算错误(即溢出)。 - 证明:当运算发生溢出时,s<xs < xs<x,s<ys<ys<y。
运算发生溢出时,s=x+y−2ws=x+y-2^ws=x+y−2w,因为y<2wy<2^wy<2w,因此y−2w<0y-2^w<0y−2w<0,因此s<xs<xs<x。
运算发生溢出时,s=y+x−2ws=y+x-2^ws=y+x−2w,因为x<2wx<2^wx<2w,因此x−2w<0x-2^w<0x−2w<0,因此s<ys<ys<y。
对任何一个数xxx而言,存在−x-x−x使得−x+x=0-x+x=0−x+x=0,则称−x-x−x是xxx的加法逆元,xxx也是−x-x−x的加法逆元。加法逆元即取反。
原理:无符号数取反。
对满足0≤x≤2w−10 \le x \le 2^w-10≤x≤2w−1的x
,其w
位的无符号加法逆元−wu-^u_w−wu可表示为:
−wux={x,x=02w−x,x>0\begin{align} -^u_wx= \begin{cases} x,\quad &x=0\\ 2^w-x,\quad &x>0 \end{cases} \end{align} −wux={x,2w−x,x=0x>0
练习1
实现一个函数,如果参数x
和y
相加不会产生溢出,这个函数就返回1
。
int uadd_ok(unsigned x, unsigned y)
{unsigned s = x + y;return s >= x;
}
练习2
给出下表中数的无符号加法逆元 −4u-^u_4−4u 的位表示。
x | -x | ||
十六进制 | 十进制 | 十六进制 | 十进制 |
0x0 | 0 | 0x0 | 0 |
0x5 | 5 | 0xB | 11 |
0x8 | 8 | 0x8 | 8 |
0xD | 13 | 0x3 | 3 |
0xF | 15 | 0x1 | 1 |
补码加法
对满足−2w−1≤x,y≤2w−1−1-2^{w-1} \le x,y \le 2^{w-1}-1−2w−1≤x,y≤2w−1−1的x
和y
,x + y
的取值范围是−2w≤x+y≤2w−2-2^w \le x+y \le 2^w-2−2w≤x+y≤2w−2。跟无符号加法一样,当运算结果不能用w
位表示时,会截断数据,产生运算溢出。
我们使用操作+wt+^t_w+wt表示w
位的补码加法。
原理:补码加法。
对满足−2w−1≤x,y≤2w−1−1-2^{w-1} \le x,y \le 2^{w-1}-1−2w−1≤x,y≤2w−1−1的xxx和yyy,有:
x+wty={x+y+2w,x+y<−2w−1负溢出x+y,−2w−1≤x+y≤2w−1−1正常x+y−2w,x+y≥2w−1正溢出\begin{align} x+^t_wy= \begin{cases} x+y+2^w, \quad &x+y < -2^{w-1} \quad &负溢出\\ x+y, \quad &-2^{w-1} \le x+y \le 2^{w-1}-1 \quad &正常 \\ x+y-2^w,\quad &x+y \ge 2^{w-1} &正溢出 \end{cases} \end{align} x+wty=⎩⎨⎧x+y+2w,x+y,x+y−2w,x+y<−2w−1−2w−1≤x+y≤2w−1−1x+y≥2w−1负溢出正常正溢出
在
bit
层面,无符号加法和补码加法的运算规则是一样的,都是逢二进一。
负溢出指运算结果太小了,小于−2w−1-2^{w-1}−2w−1;正溢出指运算结果太大了,大于2w−1−12^{w-1}-12w−1−1。溢出产生了符号翻转。
原理:检测补码加法中的溢出。
对满足−2w−1≤x,y≤2w−1−1-2^{w-1} \le x,y \le 2^{w-1}-1−2w−1≤x,y≤2w−1−1的x
和y
,存在s=˙x+ys\.=x+ys=˙x+y。当且仅当x>0x>0x>0,y>0y>0y>0,s≤0s\le0s≤0时,算术运算正溢出;当且仅当x<0x<0x<0,y<0y<0y<0,s≥0s\ge0s≥0时,算术运算负溢出。
- 证明:x>0x>0x>0,y>0y>0y>0,s≤0s\le0s≤0时,算术运算正溢出。
预期s=x+y>0s=x+y>0s=x+y>0而s≤0s\le0s≤0,显然s
过大而无法表示,运算产生了错误(正溢出)。 - 证明:算术运算正溢出,因此x>0x>0x>0,y>0y>0y>0时s≤0s\le0s≤0。
运算正溢出,那么s=x+y−2ws=x+y-2^ws=x+y−2w,且2w−1≤x+y≤2w−22^{w-1}\le x+y \le 2^{w}-22w−1≤x+y≤2w−2,因此x>0x>0x>0,y>0y>0y>0,且2w−1−2w≤s≤2w−2−2w2^{w-1}-2^w \le s \le 2^{w}-2-2^w2w−1−2w≤s≤2w−2−2w,即−2w−1≤s≤−2≤0-2^{w-1} \le s \le -2 \le 0−2w−1≤s≤−2≤0,即s≤0s\le0s≤0。 - 证明:x<0x<0x<0,y<0y<0y<0,s≥0s\ge0s≥0时,算术运算负溢出。
预期s=x+y<0s=x+y<0s=x+y<0而s≥0s\ge0s≥0,显然s
过小而无法表示,运算产生了错误(负溢出)。 - 算术运算负溢出,因此x<0x<0x<0,y<0y<0y<0时s≥0s\ge0s≥0。
运算负溢出,那么s=x+y+2ws=x+y+2^ws=x+y+2w,且−2w≤x+y<−2w−1-2^{w}\le x+y < -2^{w-1}−2w≤x+y<−2w−1,因此x<0x<0x<0,y<0y<0y<0,且−2w+2w≤s≤−2w−1+2w-2^{w}+2^w \le s \le -2^{w-1}+2^w−2w+2w≤s≤−2w−1+2w,即0≤s≤2w−10 \le s \le 2^{w-1}0≤s≤2w−1,即s≥0s\ge0s≥0。
练习1
填写下表
xxx | yyy | x+yx+yx+y | x+5tx+^t_5x+5t | 情况 |
---|---|---|---|---|
[10100]-12 | [10001]-15 | [100101]-27 | [00101]5 | 负溢出 |
[11000]-8 | [11000]-8 | [110000]-16 | [10000]-16 | 正常 |
[10111]-9 | [01000]8 | [11111]-1 | [11111]-1 | 正常 |
[00010]2 | [00101]5 | [00111]7 | [00111]7 | 正常 |
[01100]12 | [00100]4 | [10000]16 | [10000]-16 | 正溢出 |
练习2
实现一个函数tadd_ok
,参数x
和y
补码相加不产生溢出时,返回1
。
int tadd_ok(int x, int y)
{int s = x + y;return !((x > 0 && y > 0 && s <= 0) || (x < 0 && y < 0 && s >= 0));
}
练习3
如下实现存在什么问题?
int tadd_ok(int x, int y)
{int sum = x + y;return (sum - x == y) && (sum - y == x);
}
函数会永远返回1
。因为并没有检测到越界的进位。
练习4
下面的函数在计算x - y
不溢出时,返回1
。
int tsub_ok(int x, int y)
{return tadd_ok(x, -y);
}
x
和y
取什么值时,该函数会产生错误的结果?写一个该函数的正确版本。
当y
的取值为INT_MIN
时,-y
的取值也为INT_MIN
。因此在计算机看来,x-y
就是x+y
。
此时按现实的整数运算来看,如果x >= 0
,x-y
预期运算溢出,返回0
;如果如果x < 0
,x-y
预期运算不溢出,返回1
。
但在计算机看来,如果x >= 0
,tadd_ok(x, -y)
不溢出,返回1
;如果x < 0
,tadd_ok(x, -y)
溢出,返回0
。
我们以为它做的是减法,实际上它做的是加法。
在计算机运算中,
INT_MIN
的位级表示是[1, 0, …, 0],-INT_MIN
的位级表示也是[1, 0, …, 0],因为[1, 0, …, 0] + [1, 0, …, 0] = [0, 0, …, 0]。当然,这不符合现实的整数运算规则。
正确的写法如下:
int tsub_ok(int x, int y)
{if (y == INT_MIN) {return !tadd_ok(x, -y);}return tadd_ok(x, -y);
}
补码的非
取非就是求加法逆元。
对满足TMinw≤x≤TMaxwTMin_w \le x \le TMax_wTMinw≤x≤TMaxw的x
,其补码的非−wtx-^t_wx−wtx表示为:
−wtx={TMinw,x=TMinw−x,TMinw<x≤TMaxw\begin{align} -^t_wx= \begin{cases} TMin_w, \quad &x=TMin_w \\ -x, \quad &TMin_w < x \le TMax_w \end{cases} \end{align} −wtx={TMinw,−x,x=TMinwTMinw<x≤TMaxw
在
C
语言中,可以说,对于任意整数值x
,因为x + ~x + 1 = 0
,所以-x = ~x + 1
。
练习
填写下表。
xxx | −4tx-^t_4x−4tx |
---|---|
0x00 | 0x00 |
0x55 | 0xB-5 |
0x8-8 | 0x8-8 |
0xD-3 | 0x33 |
0xF-1 | 0x11 |
无符号乘法
两个w
位的无符号数相乘,其结果可能需要2w
个位才能完整表示。但在C
语言中,会根据整数位宽做截断处理。
对满足0≤x,y≤2w−10 \le x,y \le 2^w-10≤x,y≤2w−1的x
和y
,有:
x∗wuy=˙(x∗y)mod2w\begin{align} x *^u_w y\.=(x * y) mod 2^w \end{align} x∗wuy=˙(x∗y)mod2w
补码乘法
对满足−2w−1≤x,y≤2w−1−1-2^{w-1} \le x,y \le 2^{w-1}-1−2w−1≤x,y≤2w−1−1的x
和y
,有:
x∗wty=˙U2Tw((x∗y)mod2w)\begin{align} x *^t_w y\.=U2T_w((x * y) mod 2^w) \end{align} x∗wty=˙U2Tw((x∗y)mod2w)
w
位无符号乘法和w
位补码乘法的运算结果的低w
位是一样的,区别在于解释这些位的方式。
练习1
填写下表,位宽w=3
。
模式 | xxx | yyy | x∗yx*yx∗y | 截断的x*y |
---|---|---|---|---|
无符号 | [100]4 | [101]5 | [010100]20 | [100]4 |
补码 | [100]-4 | [101]-3 | [001100]12 | [100]-4 |
无符号 | [010]2 | [111]7 | [001110]14 | [110]6 |
补码 | [010]2 | [111]-1 | [111110]-2 | [110]-2 |
无符号 | [110]6 | [110]6 | [100100]36 | [100]4 |
补码 | [110]-2 | [110]-2 | [000100]4 | [100]4 |
计算二进制
w
位的无符号乘法时,先做零扩展,扩展到2w
位,再两数相乘,取低2w
位。
计算二进制w
位的补码乘法时,先做符号扩展,扩展到2w
位,再两数相乘,取低2w
位。
练习2
考虑下面的函数,它判断两个参数是否会产生溢出,不溢出返回1
。
int tmul_ok(int x, int y)
{int p = x * y;return !x || p/x == y;
}
当x = 0
时,乘法不溢出,函数返回1
。和预期相符。
当x
不等于0
时:
- x∗y=p+t2wx*y=p+t2^wx∗y=p+t2w,其中t≠0t\ne 0t=0当且仅当计算溢出。
当t≠0t\ne 0t=0时,x∗y>px*y>px∗y>p或者x∗y<px*y<px∗y<p,计算溢出。
如果计算溢出,p<x∗yp<x*yp<x∗y或者p>x∗yp>x*yp>x∗y,所以t≠0t\ne 0t=0。 - p=x∗q+rp=x*q+rp=x∗q+r,其中
q
是p/xp/xp/x的结果,|r| < |x|
。
r
是p
除以x
的余数,因此一定存在|r| < |x|
。 q = y
当且仅当r = t = 0
。
q = y
时,x∗q=p+t2wx*q=p+t2^wx∗q=p+t2w,因此x∗q+r=p+t2w+rx*q+r=p+t2^w+rx∗q+r=p+t2w+r,因此p=p+t2w+rp=p+t2^w+rp=p+t2w+r,因此0=0+t2w+r0=0+t2^w+r0=0+t2w+r,所以r = t = 0
。
r = t = 0
时,x∗y=x∗q+t2wx*y=x*q+t2^wx∗y=x∗q+t2w,因此y=q+t2wxy=q+\frac {t2^w}{x}y=q+xt2w,因此q = y
。
计算溢出时,r≠0r \ne 0r=0,t≠0t \ne 0t=0,因此q≠yq\ne yq=y,p/x≠yp/x \ne yp/x=y,函数返回0
。反之不溢出,函数返回1
。
练习3
对于数据类型int
为32
位的情况,设计一个tmul_ok
函数,使用64
位精度的数据类型int64_t
,不使用除法。不溢出返回1
。
int tmul_ok(int x, int y)
{int64_t p = (int64_t)x * y;return p == (int)p; // 截断前后的值是不是一样
}
乘以常数
在大多数机器上,整数乘法指令相当慢。因此,编译器使用了一项重要的优化,就是用移位和加减法运算的组合来代替与常数因子的乘法。当然,如果数个移位和加减法指令比一个乘法指令更耗时,那就用乘法指令。
一个整数乘以2k2^k2k等价于左移k位(k≥0k \ge 0k≥0)。
练习1
LEA
指令能够执行如(a << k) + b
这样的运算。考虑b = 0
或者b = a
、k
为任意可能的值,一条LEA
指令可以计算a
的哪些倍数?
当b = 0
时,一条LEA
指令可以计算a
的20,21,22,23,...2^0,2^1,2^2,2^3,...20,21,22,23,...倍。
当b = a
时,一条LEA
指令可以计算a
的20+1,21+1,22+1,23+1,...2^0 + 1,2^1 + 1,2^2 + 1,2^3 + 1,...20+1,21+1,22+1,23+1,...倍。
练习2
填写下表。
kkk | 移位 | 加法/减法 | 表达式 |
---|---|---|---|
6 | 2 | 1 | (x<<3)−(x<<1)(x<<3)-(x<<1)(x<<3)−(x<<1) |
31 | 1 | 1 | (x<<5)−x(x<<5)-x(x<<5)−x |
-6 | 2 | 1 | (x<<1)−(x<<3)(x<<1)-(x<<3)(x<<1)−(x<<3) |
55 | 2 | 2 | (x<<6)−x−(x<<3)(x << 6) - x - (x << 3)(x<<6)−x−(x<<3) |
除以2的幂
在大多数机器上,整数除法比整数乘法还慢。
除以2
的幂可以用右移实现,无符号数使用逻辑右移,有符号数使用算术右移。
对于任何实数α\alphaα,定义⌈α⌉\lceil \alpha \rceil⌈α⌉为唯一的整数α′\alpha'α′,使得α′−1<α≤α′\alpha'-1 < \alpha \le \alpha'α′−1<α≤α′。
对于任何实数α\alphaα,定义⌊α⌋\lfloor \alpha \rfloor⌊α⌋为唯一的整数α′\alpha'α′,使得α′≤α<α′+1\alpha' \le \alpha <\alpha'+1α′≤α<α′+1。
对于x≥0,y>0x\ge0,y>0x≥0,y>0,xxx除以yyy的结果是⌊x/y⌋\lfloor x/y \rfloor⌊x/y⌋;对于x<0,y>0x<0,y>0x<0,y>0或x>0,y<0x>0,y<0x>0,y<0,xxx除以yyy的结果是⌈x/y⌉\lceil x/y \rceil⌈x/y⌉。即向零取整。
计算机执行除法运算时,不论结果正负,都是向下取整。
如果要向上取整,需要给被除数加上偏置量(除数-1)。
⌈x/y⌉=⌊(x+y−1)/y⌋\begin{align} \lceil x/y \rceil=\lfloor (x+y-1)/y \rfloor \end{align} ⌈x/y⌉=⌊(x+y−1)/y⌋
C
表达式(x < 0 ? x + (1 << k) - 1 : x) >> k
将会计算x/2kx/2^kx/2k。
同乘法不同,我们不能用右移和加减运算的组合表示除以任意常数。(乘法满足分配律,但除法不)。
练习1
写一个函数div16
,返回x/16
的结果。
int div16(int x)
{int sign = x >> 31; // 符号位填满intint k = 4;int bias = (1 << k) - 1;return (x + sign & bias) >> k;
}
练习2
下面的代码中,省略了M
和N
的定义。
int arith(int x, int y)
{return x * M + y / N;
}
编译器优化后:
int arith(int x, int y)
{int t = x;x <<= 5;x -= t;if (y < 0) {y += 7;}y >>= 3;return x + y;
}
M
和N
的值是多少?
M = 31, N = 8
。
关于整数运算的最后思考
计算机执行的“整数”运算实际上是一种模运算,因为整型的有限字长限制了可能的取值范围,运算结果可能溢出。
无符号数与补码数在运算时,拥有相同的位级行为,区别在于编译器解释位的方式。
练习
int x = foo();
int y = bar();
unsigned ux = x;
unsigned uy = y;
对于下面的C
表达式,
1)证明对于所有的x
和y
,结果都为1
。
2)给出使他们为0
的x
和y
。
- (x>0)∣∣(x−1<0)(x > 0) || (x - 1 < 0)(x>0)∣∣(x−1<0)
当x = TMin
时,结果为0
。 - (x & 7) != 7 || (x << 29 < 0)
x
的低3
位不都是1
时,结果是1
。
x
的低3
位都是1
时,结果是1
。 - (x∗x)>=0(x * x) >= 0(x∗x)>=0
运算可能会溢出,当x = 123456
时,x * x
的低32
位是1000 1100 0111 0101 0001 0000 0000 0000
,符号位是1
,是负数。 - x<0∣∣−x<=0x < 0 || -x <= 0x<0∣∣−x<=0
如果x
是非负数,-x
一定是非正的。 - x>0∣∣−x>=0x > 0 || -x >= 0x>0∣∣−x>=0
如果x = TMin
,-x
还是TMin
,都是负数。 - x+y==uy+yxx + y == uy + yxx+y==uy+yx
结果是1
。无符号数与补码数有相同的位级行为,且可交换。 - x * ~y + uy * ux == -x
结果是1
。
补码表示中,-y = ~ y + 1,因此~ y = -y - 1。
x * ~y + uy * ux = x * (-y - 1) + uy * ux = -xy - x + uy * ux = -x。