CSAPP-1:家庭作业以及Data Lab要点
信息存储
-
将一个$w$位的字中的字节0(最低位)开始编号,并将参数$x$的字节$i$替换为字节$b$,要考虑大小端:
unsigned replace_byte(unsigned x, int i, unsigned char b) { unsigned int *p = &x; unsigned char *v = (unsigned char *) p; if (is_little_endian()) v[i] = b; else v[sizeof(unsigned) - i - 1] = b; return *p; } int is_little_endian(void) { int n = 1; int *p = &n; int *v = (void *) p; if (*v == 0x01) return 1; else return 0; }
- 判断表达式是否满足以下任一条件: 通过逻辑反的判断来确保任何结果都是对的
- $x$的任何位都为1
!(x ^ 0xffffffff)
- $x$的任何位都为0
!(x ^ 0x0)
- $x$的最低有效字节的位都为1
!((x & 0xff) ^ 0xff)
- $x$的最低有效字节的位都为0
!((x & 0xff) ^ 0x0)
- $x$的任何位都为1
-
判断机器是否为算术右移:
int int_shifts_are_arithmetic() { int n = -1; int shifts = (n>>2); return shifts == n; }
-
用算术右移来完成逻辑右移以及用逻辑右移完成算术右移,后面其他操作不能包含右移和除法。 在实现算术右移的时候注意 如何判断x的最高位是否为1
// 算术右移到逻辑右移 unsigned srl(unsigned x, int k) { /* perform shift arithmetically */ unsigned xsra = (int) x >> k; int mask = -1; mask = mask << ((sizeof(int) << 3) - k); return ~mask & xsra; } unsigned sra(int x, int k) { /* perform shift logically */ int xsrl = (unsigned) x >> k; unsigned mask = 1; int *p = &x; char *temp = (void *) p; // 判断x的最高位是否为1 int h = (((mask << 7) & temp[sizeof(int) - 1]) == 128); h = (-h) << ((sizeof(int) << 3) - k); return h | xsrl; }
-
判断是否$x$的任一奇数位为1:
!(x & 0x55555555)
. 这里要用&
是因为避免$x$偶数位的影响。 -
判断$x$的位表示中是否有奇数个1:
int odd_ones(unsigned x) { x ^= x >> 1; x ^= x >> 2; x ^= x >> 4; x ^= x >> 8; x ^= x >> 16; return x & 0x1; }
第一次异或的结果,第$i$个位置上是1代表原数中第$i$位和第$i+1$位有一个为1,也就是奇数个1。也就是说,某个位为1,代表从它开始向左连续两位中1的个数是奇数(异或的本质)。第二次异或,就是两位两位的比较了,比如,若结果第6位为1,那么代表上一次的结果的第6位和第8位有奇数个1,也就是原数中第6,7,8,9位中有奇数个1。 整个过程就是不断的压缩信息。
-
得到暗示x最左侧的1的掩码,例如
0xFF00 -> 0x8000
int leftmost_one(unsigned int x) { unsigned int base = 0x80000000; while(base) { if((x & base)) break; base >>= 1; } }
这样做可以返回
0x8000
,但是没有涉及到掩码:int leftmost_one(unsigned x) { x |= x >> 1; x |= x >> 2; x |= x >> 4; x |= x >> 8; x |= x >> 16; return (x & (~(x >> 1))); }
假设此时的字长$w$ = 8,假设
x = 00010110
,首先前五步将$x$最左侧的1的右侧全置为1,得到0x00011111
,在此基础上右移一位再取反就可以得到掩码了。 -
得到最低侧$n$位均为1的值:
-1 >> (w-n)
注意当$n=w$的情况,此时右移$n$位的实际操作是不变 -
将$x$的最高$n$位交换到末尾,例如:
n=4, x=0x12345678 -> 0x23456781
unsigned rotate_left(unsigned x, int n) { int w = sizeof(int) << 3; unsigned temp = x >> (w - n - 1) >> 1; return (x << n) + temp; }
-
判断一个数能否用$n$位补码来表示:
正数需要要求第$n$位不为1,例如2是无法用2位来表示的。 负数要求$n$到$w$位均为1才可以。
int fits_bits(int x, int n) { int neg = x >> (n - 1); //考虑算术右移 int pos = x >> (n - 1); return neg == -1 || pos == 0; }
- $a^k$表示$a$重复$k$次,假设一个$w$位的类型,如何不适用$w$得到以下数值
- $1^{w-k}0^{k}$:
(-1) << k
- $0^{w-k-j}1^k0^j$:
a = -1 << (k-j), b = -1 << j, ~(a|~b)
- $1^{w-k}0^{k}$:
整数运算
-
符号整数饱和加法(正溢出返回TMax,负溢出返回TMin)以及对应的判断减法是否溢出(注意y=TMin的情况)
int asturating_add(int x, int y) { int sum = x + y; int w = sizeof(int) << 3; // x, y, sum的最高位 int h_x = (unsigned) x >> (w - 1); int h_y = (unsigned) y >> (w - 1); int h_sum = (unsigned) sum >> (w - 1); // neg = 1 表示负溢出, pos = 1 表示正溢出 int neg = h_x && h_y && !h_sum; int pos = !h_x && !h_y && h_sum; return (-neg & INT_MIN) + (-pos & INT_MAX); } int tsub_ok(int x, int y) { if(y == INT_MIN){ return x < 0; } int temp = asturating_add(x, -y); return temp != INT_MIN && temp != INT_MAX; }
-
假设我们已有用于计算$x$和$y$采用补码形式下$x\cdot y$的高$w$位的函数
int signed_high_prod(int x, int y);
现在计算无符号变量$x\cdot y$的高$w$位:
这里用到$x’\cdot y’ = x\cdot y + (x_{w-1}y+y_{w-1}x)2^w + x_{w-1}y_{w-1}2^{2w}$.
int signed_high_prod(int x, int y) { int64_t mul = (int64_t) x * y; return mul >> 32; } unsigned unsigned_high_prod(unsigned x, unsigned y) { int w = sizeof(int) << 3; int x_highest_bit = x >> w - 1; int y_highest_bit = y >> w - 1; return signed_high_prod(x, y) + (-y_highest_bit & x) + (-x_highest_bit & y); }
-
实现
calloc
函数为一个数组分配内存,该数组有nmemb个元素,每个元素size字节:判断是否发生溢出!void *calloc(size_t nmemb, size_t size) { if (nmemb == 0 || size == 0) return NULL; size_t bytes = nmemb * size; if (nmemb == bytes / size) // 判断是否溢出 { void* ptr = malloc(bytes); memset(ptr, 0, bytes); return ptr; } return NULL; }
-
实现整数除以2的幂数: 如何不适用判断来做到合理偏置(-1\0 & mask)
int divide_power2(int x, int k) { int sign = (x & INT_MIN) == INT_MIN; int mask = (1 << k) - 1; int bias = -sign & mask; return (x + bias) >> k; }
-
对于整数参数$x$,分两种方式计算$3*x/4$的值:1.会出现溢出;2.向零舍入,不会溢出。 不溢出就需要用更大的类型来存储,计算完毕后再进行舍入。
int mul3div4(int x) { int result = (x << 1) + x; return divide_power2(result, 2); } int threefourths(int x) { int64_t result = ((int64_t) x << 1) + x; int sign = (x & INT_MIN) == INT_MIN; int mask = (1 << 2) - 1; int bias = -sign & mask; return (result + bias) >> 2; }
浮点数运算
- 实现浮点数小于号判断(假设两个参数都不是NaN):
- +0.0等于-0.0
- 考虑NaN(默认比任何数都大)
- 考虑正负号
- 正数升序,负数降序(正负无穷也可以按照此规律)
unsigned f2u(float x) { return *(unsigned*)&x; } int float_le(float x, float y) { unsigned ux = f2u(x); unsigned uy = f2u(y); unsigned sx = ux >> 31; unsigned sy = uy >> 31; return (ux << 1 == 0 && uy << 1 == 0) || (sx > sy) || (sx && sy && ux >= uy) || (!sx && !sy && ux <= uy); }
-
实现$2^x$的浮点表示:
float u2f(unsigned u) { return *(float *) &u; } float fpwr2(int x) { /* Result exponent and fraction */ unsigned exp, frac; unsigned u; if (x < -126 - 23) { /* Too small. Return 0.0 */ exp = 0; frac = 0; } else if (x < -126) { /* Denormalized result */ exp = 0; frac = 1 << (23 + x + 126); } else if (x < 128) { /* Normalized result. */ exp = x + 127; frac = 0; } else { /* Too big. Return +oo */ exp = 255; frac = 0; } /* Pack exp and frac into 32 bits */ u = exp << 23 | frac; return u2f(u); }
-
实现对一个浮点数求相反数: 关键是符号,阶码,尾码的获取与重组
typedef unsigned float_bits; /* Compute -f. If f is NaN, then return f. */ float_bits float_negate(float_bits f) { float_bits sign, exp, frac; sign = f >> 31; exp = f >> 23 & 0xff; frac = f & 0x7fffff; if (exp == 0xff && frac) return f; else return (~sign << 31) | (exp << 23) | frac; }
-
实现浮点数绝对值函数:
float_bits float_absval(float_bits f) { float_bits exp, frac; exp = f >> 23 & 0xff; frac = f & 0x7fffff; if (exp == 0xff && frac) return f; else return 0 << 31 | (exp << 23) | frac; }
- 实现$2.0*f$:
- 考虑NaN
- 考虑非规格化: frac左移一位就相当于exp最低位有1
- 考虑2f溢出的情况,即exp=0xff或0xff-1
- 考虑规格化
float_bits float_twice(float_bits f) { float_bits sign, exp, frac; sign = f >> 31; exp = f >> 23 & 0xff; frac = f & 0x7fffff; if (exp == 0xff && frac) return f; if (exp == 0) // 非规格化的值 frac <<= 1; else if (exp == 0xff - 1) //无穷大时 { frac = 0; exp = 0xff; } else exp += 1; return (sign << 31) | (exp << 23) | frac; }
- 实现$0.5*f$:
- 考虑NaN
- 考虑非规格化时的舍入 学习如何向偶数舍入(只有11的情况才需要+1)
- 考虑exp=1 抛开符号位,整体右移一位再舍入
- 考虑其他规格化情况
float_bits float_half(float_bits f) { float_bits sign, exp, frac, rest; sign = f >> 31; exp = f >> 23 & 0xff; frac = f & 0x7fffff; rest = f & 0x7FFFFFFF; if (exp == 0xff) return f; /* 向偶数舍入 * round to even, we care about last 2 bits of frac * * 00 => 0 just >>1 * 01 => 0 (round to even) just >>1 * 10 => 1 just >>1 * 11 => 1 + 1 (round to even) just >>1 and plus 1 */ int addition = (frac & 0x3) == 0x3; if (exp == 0) { /* Denormalized */ frac >>= 1; frac += addition; } else if (exp == 1) { /* Normalized to denormalized */ rest >>= 1; rest += addition; exp = rest >> 23 & 0xFF; frac = rest & 0x7FFFFF; } else { /* Normalized */ exp -= 1; } return (sign << 31) | (exp << 23) | frac; }
- 实现float到int的转换,向零舍入,如果超出表示范围则返回0x80000000:
- 考虑溢出或者NaN(int 最大只能表示$2^{31}$)
- 当exp < bias时,浮点数f的绝对值小于1,直接返回0
- 此时bias <= exp < 31+bias, 即 0 <= E < 31 时,由于浮点数小数部分只有23位,此时分为两种情况:
- E > 23 时,rest 左移 E-23位
- E <= 23 时,rest 右移 23-E位(将精确数字舍去一部分就是向零舍入)
int float_f2i(float_bits f) { float_bits sign, exp, frac; float_bits M, E, bias; sign = f >> 31; exp = f >> 23 & 0xff; frac = f & 0x7fffff; bias = 0x7f; if (exp>= 31 + bias || exp == 0xff) // overflow or f is NaN return 0x80000000; else if (exp < bias) // 结果小于1 return 0; else { M = frac | 0x800000; E = exp - bias; if (E > 23) return pow(-1, sign) * (M << (E - 23)); else return pow(-1, sign) * (M >> (23 - E)); } }
- 实现int到float的转换:
- 0直接返回
- 如果是负数,先确定sign,然后取反+1获得其绝对值
- 算出最左侧的1在第k位,则exp = k+bias
- 如果k > 23,需要考虑如何舍入:
- 截断的部分大于截断的一半,向上舍入
- 截断的部分等于截断的一半,向偶数舍入 i&hide == hide是判断某一位是否为1,与判断符号数是否是负数一样
- 截断的部分小于截断的一半,向下舍入
float_bits float_i2f(int i) { unsigned sign, exp, frac; unsigned leftmost, rightmost, t; if (i == 0) return i; sign = (i & INT_MIN) == INT_MIN; if (sign) i = ~i + 1; for (rightmost = 1, t = INT_MIN; (t & i) != t; t >>= 1) rightmost++; leftmost = (sizeof(int) << 3) - rightmost; //最左侧的1的位置 exp = leftmost + 127; int shift; if (leftmost > 23) { // 考虑舍入 shift = leftmost - 23; int mask = ((1 << shift) - 1) & i; int half = 1 << (shift - 1); int hide = 1 << shift; //mask == half时只有舍入位为1时才加1 int round = mask>half || (mask==half && (i&hide)==hide); frac = (i >> shift) & 0x7FFFFF; //考虑算术右移的影响 if (frac == 0x7FFFFF && round == 1) //+1有可能导致进位 frac = 0, exp++; else frac += round; } else { // 补满23位 shift = 23 - leftmost; frac = (i << shift) & 0x7FFFFF; } return (sign << 31) | (exp << 23) | frac; }