文章目录
2.77**
问:假设我们有一个任务:生成一段代码,将整数变量x
乘以不同的常数因子K
。为了提高效率,我们想只用+
、-
和<<
运算。对于下列K
的值,写出执行乘法运算的C
表达式,每个表达式最多使用3
个运算。
答:
- K = 17
(x << 4) + x;
- K = -7
-(x << 3) + x;
- K = 60
(x << 6) - (x << 2);
- K = -112
-(x << 7) + (x << 4);
测试:
#include <assert.h>
void csapp_2_77(int x)
{
assert(((x << 4) + x) == x * 17);
assert((-(x << 3) + x) == x * -7);
assert(((x << 6) - (x << 2)) == x * 60);
assert((-(x << 7) + (x << 4)) == x * -112);
}
int main()
{
csapp_2_77(0);
csapp_2_77(123);
csapp_2_77(-123);
printf("PASSED!\n");
return 0;
}
[liheng@localhost2 2]$ ./a.out
PASSED!
2.78**
问:写出具有如下原型的函数的代码:
int divide_power2(int x, int k);
该函数要用正确的舍入方式计算 x / 2 k x/2^k x/2k。
答:
整数除法:向零取整。
右移除法:向下取整。
因此使用右移计算 x / 2 k x/2^k x/2k时要注意负数。如果x
是负数就需要向上取整,要是它的低k
位是非零的,就需要对右移结果+1
。
int divide_power2(int x, int k)
{
int low_k = x & ~(~0 << k);
int w = sizeof(int) << 3;
int sign = (unsigned)x >> (w - 1);
int res = x >> k;
(sign && low_k && (res = res + 1));
return res;
}
测试:
int main()
{
assert(divide_power2(1024, 4) == 1024 / 16);
assert(divide_power2(-1024, 4) == -1024 / 16);
assert(divide_power2(1027, 4) == 1025 / 16);
assert(divide_power2(-1027, 4) == -1025 / 16);
printf("PASSED!\n");
return 0;
}
[liheng@localhost2 2]$ ./a.out
PASSED!
2.79**
问:写出函数mul3div4
的代码,对于整数参数x
,计算 3 ∗ x / 4 3*x/4 3∗x/4,注意 3 ∗ x 3*x 3∗x会产生溢出。
答:
把x
分为两部分:
- 可以被
4
整除的部分 - 小于
4
的部分。
对于第一部分,先除以4
再乘3
;对第二部分,先乘3
再除以4
,如果x
是负数,要注意向上取整。
#include <stdio.h>
#include <assert.h>
#include <stdint.h>
#include <limits.h>
int mul3div4(int x)
{
int h = x & ~0x3;
int hdiv4 = h >> 2;
int hdiv4mul3 = (hdiv4 << 1) + hdiv4;
int l = x & 0x3;
int lmul3 = (l << 1) + l;
int lmul3div4 = lmul3 >> 2;
int sign = x >> ((sizeof(int) << 3) - 1);
(sign && (lmul3 & 0x3) && (lmul3div4 = lmul3div4 + 1));
return hdiv4mul3 + lmul3div4;
}
测试:
int main()
{
assert(mul3div4(0xffff) == (int64_t)0xffff * 3 / 4);
assert(mul3div4(0xffffff) == (int64_t)0xffffff * 3 / 4);
assert(mul3div4(INT_MAX) == (int64_t)INT_MAX * 3 / 4);
assert(mul3div4(INT_MIN) == (int64_t)INT_MIN * 3 / 4);
assert(mul3div4(-1) == (int64_t)-1 * 3 / 4);
printf("PASSED!\n");
return 0;
}
liheng@~/coding/csapp/2$ ./a.out
PASSED!
2.80***
问:写出函数threefourths
的代码,对于整数参数x
,计算 3 / 4 x 3/4x 3/4x的值,向零舍入,它不会溢出。
答:同上。
2.81**
编写C
表达式产生如下位模式,其中 α k \alpha^k αk表示符号 α \alpha α重复 k k k次。假设一个w
位的数据类型,代码可以包含对参数j
和k
的引用,它们分别表示j
和k
的值,但是不能使用表示w
的参数。
- 1 w − k 0 k 1^{w-k}0^k 1w−k0k
~0 << k;
- 0 w − k − j 1 k 0 j 0^{w-k-j}1^{k}0^j 0w−k−j1k0j
(1 << (j + k)) - (1 << j);
2.82*
问:我们在一个int
类型值为32
位的机器上运行程序。这些值以补码形式表示,而且它们都是算术右移的,unsigned
类型的值也是32
位的。
我们产生随机数x
和y
,并且把他们转换成无符号数,显示如下:
int x = random();
int y = random();
unsigned ux = (unsigned)x;
unsigned uy = (unsigned)y;
对于下列每个C
表达式,你要指出表达式是否总是为1
,如果是,请描述其中的原理;如果不是,列举出使它为0
的例子。
答:
- (x < y) == (-x > -y)
否。当x = INT_MIN, y = -1
时,-x
还是INT_MIN
,-y = 1
,明显-x < -y
。 - ((x + y) << 4) + y - x == 17 * y + 15 * x
是。左移4
位相当于乘16
,乘法满足分配律。 - ~x + ~y + 1 == ~(x + y)
是。根据补码运算规则-x = ~x + 1, -y = ~y + 1
得到~x = -x - 1, ~y = -y - 1
,得到:
-x - 1 + (-y) - 1 + 1 = -x - y - 1 = -(x + y) - 1 = ~(x + y)
。 - (ux - uy) == -(unsigned)(y - x)
是。无符号数和补码数有同样的位级行为。 - ((x >> 2) << 2) <= x
是。x
的低2
位可能会变小,低2
位是正权,所以整体值可能会减小。
2.83**
问:一些数字的二进制表示是由形如0.yyy...
的无穷串组成的,其中y
是一个k
位的序列。例如, 1 3 \frac{1}{3} 31的二进制表示是0.01010101...(y=01)
,而 1 5 \frac{1}{5} 51的二进制表示是0.001100110011...(y=0011)
。
答:
- 设 Y = B 2 U k ( y ) Y=B2U_k(y) Y=B2Uk(y),也就是说,这个数具有二进制表示
y
。给出一个由Y
和k
组成的公式表示这个无穷串的值。
定义n = 0.yyy...
,则(n << k) = y.yyy...
,(n << k) - n = Y
,因此 n ∗ ( 2 k − 1 ) = Y n*(2^k-1)=Y n∗(2k−1)=Y,即 n = Y 2 k − 1 n=\frac{Y}{2^k-1} n=2k−1Y - 对于下列的
y
值,串的数值是多少?
- 101
n = 5 2 3 − 1 = 5 7 n=\frac{5}{2^3-1}=\frac{5}{7} n=2