Data Lab
Link: csapp lab(该链接被限制访问)
也可自行在github中搜索csapplab,以找到实验原文件。
操作系统:linux
one
bitXor
- bitXor - x^y using only ~ and &
- Example: bitXor(4, 5) = 1
- Legal ops: ~ &
- Max ops: 14
- Rating: 1
bitXor:实现位级异或,限制操作: ~ &
1 | int bitXor(int x, int y) { |
思路:
XOR的与或非实现: (x & ~ y)|( ~ x & y)
; OR的与非实现: ~ (~ a & ~ b
)
time
- tmin - return minimum two’s complement integer
- Legal ops: ! ~ & ^ | + << >>
- Max ops: 4
- Rating: 1
tmin:位级实现输出Tmin
一开始没认真看INTEGER CODING RULES
的要求,后来才发现,仅允许使用0 ~ 255
之间的数值,-1 << 31
虽然是对的,但不符合要求
1 | int tmin(void) { |
two
isTmax
- isTmax - returns 1 if x is the maximum, two’s complement number,
- and 0 otherwise
- Legal ops: ! ~ & ^ | +
- Max ops: 10
- Rating: 1
isTmax:判断输入的数值是否为Tmax
即0x7FFFFFFF
,是,输出1
,否则,输出0
不知道为啥上面题目是tmin
,这里就是Tmax
,为啥大小写不统一呢?绝对是出题老师偷懒了。
1 | int isTmax(int x) { |
思路:
由于Tmax == ~(Tmax+1)
,|
左边利用异或^
充当判断==,
相等其值为0
, |
右边排除-1
即0xffffffff
(因为-1 == ~(-1+1)
);tmax
按位取反再按数值取反后,为0
, -1
按位取反再按数值取反后,为1
allOddBits
- allOddBits - return 1 if all odd-numbered bits in word set to 1
- where bits are numbered from 0 (least significant) to 31 (most significant)
- Examples allOddBits(0xFFFFFFFD) = 0, allOddBits(0xAAAAAAAA) = 1
- Legal ops: ! ~ & ^ | + << >>T
- Max ops: 12
- Rating: 2
allOddBits:判断一个数的奇数位(odd)
是否全为1
,是,输出1
,否则,输出0
1 | int allOddBits(int x) { |
思路:
先构造0xAAAAAAAA
,利用 <<、|
即可,再用x XOR x
的必为0
的性质,逻辑取反即可
negate
- negate - return -x
- Example: negate(1) = -1.
- Legal ops: ! ~ & ^ | + << >>
- Max ops: 5
- Rating: 2
negate:取相反数
不知道的时候是真的不知道= =
1 | int negate(int x) { |
three
isAsciiDigit
- isAsciiDigit - return 1 if 0x30 <= x <= 0x39 (ASCII codes for characters ‘0’ to ‘9’)
- Example:
- isAsciiDigit(0x35) = 1.
- isAsciiDigit(0x3a) = 0.
- isAsciiDigit(0x05) = 0.
- Legal ops: ! ~ & ^ | + << >>
- Max ops: 15
- Rating: 3
isAsciiDigit:判断一个数是否在0x30 <= x <= 0x39
之间,是,输出1
,否则,输出0
自己的思路把前半部的信息给清除了,不能用。还是大佬强,还具有扩展性,改变上下限数值就是改变范围了。
1 | int isAsciiDigit(int x) { |
思路:
上限(左边),目的是使输入的数大于0x39
时,真值为1
;下限(右边),目的是使输入的数小于0x30
时,真值为1
,
当两边的真值为0
时,才居于范围之间,输出1
先取一个符号位sign
即0x80000000
左边:0x39
按位或sign
后再取反,目的是得到一个低8
位为0xc6
,符号位为0
,其它位为1
的位级。
该位级+x
后,若x
大于0x39
则其符号位变为1
,反之为0
&sign
取符号位后,再右移31
。若其值大于0x39
则为-1
,反之为0
。
右边:0x30
按位取反+1
,目的是得到一个低8
位为0xd0
,符号位为1
,其它位为1
的位级。
该位级加x
后,若x
小于0x30
则其符号位仍为1
,反之为0
&sign
取符号位后,再右移31
。若其值小于0x30
为-1
,反之为0
。
conditional
- conditional - same as x ? y : z
- Example: conditional(2,4,5) = 4
- Legal ops: ! ~ & ^ | + << >>
- Max ops: 16
- Rating: 3
conditional:用位级运算实现三目运算符(x ? y : z)
思路往往可以更简洁
1 | //法一 |
思路:
用x构造出全1
或全0
,再使全1
、全0
分别与x
、y
对应。x
逻辑取反两次得真值,**再<<31>>31
**,使真值1
变为全1
即-1
,真值0
不变
此时,若x
为-1
,则&y
得到y
,且按位取反x
,并&z
清空;若x
为0
,则&y
清空,且按位取反x
,并&z
得到z
而后按位或输出
1 | //法二 |
思路:
同法一类似,只是构造全1
或者全0
的方法不同
用x
构造出全1
或全0
,再使全1
、全0
分别与x
、y
对应。x
逻辑取反两次得真值,**再按位取反后+1
**,使真值1
变为全1
即-1
,真值0不变
此时,若x
为-1
,则&y
得到y
,且按位取反x
,并&z
清空;若x
为0
,则&y
清空,且按位取反x
,并&z
得到z
而后按位或输出
1 | //法三 |
思路:x
不为0
时,输出y
;x
为0
时,输出z
。想办法使两者分别对应,利用非!
翻转(x
不为0
翻转1
次得全0
or x
为0
翻转2
次得全0
)使两者分别对应
再用-1(neg_1)
配出我们要的全1
,以便&x
或y()
。
运算过程:
左边:若x
不为0
,则翻转一次后-1
,为全1
,可得y
值;若x
为0
,则翻转再-1
,为全0
,可清空y
值;
右边:若x
不为0
,则翻转两次后-1
,为全0
,可清空z
值;若x为0
,则两次翻转再-1
,为全1
,可得到z
值;
两边取或,可输出数字(return竟然可以输出数值!!哭笑)
isLessOrEqual
- isLessOrEqual - if x <= y then return 1, else return 0
- Example: isLessOrEqual(4,5) = 1
- Legal ops: ! ~ & ^ | + << >>
- Max ops: 24
- Rating: 3
isLessOrEqual:判断是否x <= y
,是,输出1
,否则,输出0
一般自己写的都有点长
1 | int isLessOrEqual(int x, int y) { |
思路:
判断x<=y
,分成三段解决,任意情况成立即可,则用XOR
连接,1.两者相等;两者不相等时,2.sign
不同;3.sign
相同。
1.XOR
清零再逻辑非即可
2.先限定于sign
不同的情况,利用XOR
再&(x>>31)
,保留x的sign
位,若sign=1
,x
为负数,则全1
输出;若sign=0
,x
为正数,则全0
输出
3.先限定于sign
相同的情况(即排除sign
不同的情况),再利用作差,x-y
,小于0
时,符合题意,且sign
位为1
,最后>>31
,分割出全0
和全1
XOR
连接完后,为满足符合return 1
, 否则return 0
,要取两次逻辑非
logicalNeg
- logicalNeg - implement the ! operator, using all of
- the legal operators except !
- Examples: logicalNeg(3) = 0, logicalNeg(0) = 1
- Legal ops: ~ & ^ | + << >>
- Max ops: 12
- Rating: 4
logicalNeg:实现逻辑非,限制操作:~ & ^ | + << >>
1 | //法一 |
思路:
实现逻辑非!
,即实现0
输出1
,非0
输出0
。
首先要分割0
和非0
部分,办法是0将保持为0
,**非0
全部转换为负数使其符号位为1。,
用 ~ x
翻转,~ 0 = -1
,非0保持不变 ;(~ x) | sign)
,使-1
不变,非0数全部转换为负数且最大值为-2
,但注意到x=sign
经运算后也为-1
,用&(x^sign)>>31
剔除,同时,使x=0
时,第0
位为1
**;((~ x) | sign)+1)
,使-1
变0
,负数最大值此时为-1
;((~ x) | sign)+1)>>31
,0
不变,负数全为-1
;((((~ x) | sign)+1)>>31)+1
,0
变1
,-1
全变为0
;((((~ x) | sign)+1)>>31)+1 & (x^sign)>>31
,x=0
时,(左边1
&
右边-1
)输出1
;x
为非0
数时,(左边0
&
右边任何数)输出0
;
1 | 法二: |
思路:
利用补码(取反+1
)的性质,**0
和Tmin
的补码为本身,其它数值的补码为其相反数**;0
与其补码按位或之后,其值为全0
,Tmin
与其补码、其它数值与其补码,按位或之后,符号位为1
;
然后>>31
,0
不变,Tmin
、其它数值为全1
即-1
;
而后+1
,0
变为1
,Tmin
、其它数值为0
;
howManyBits
- howManyBits - return the minimum number of bits required to represent x in
- two’s complement
- Examples:
- howManyBits(12) = 5
- howManyBits(298) = 10
- howManyBits(-5) = 4
- howManyBits(0) = 1
- howManyBits(-1) = 1
- howManyBits(0x80000000) = 32
- Legal ops: ! ~ & ^ | + << >>
- Max ops: 90
- Rating: 4
howManyBits:求一个数最少要用多少位表示
这道题想了很久,因为允许90
个ops,就慢慢找规律,最终想出最高位和次最高位相异时,该数的位数可以确定,但实际只能从1
位到32
位一个个判断,总共操作接近300
ops,只能翻起答案来= =,知道会用重复判断的方法,没想到是二分法这么巧妙
1 | int howManyBits(int x) { |
思路:
如果是一个正数,则需要找到它最高为1
的是第几位(假设该位是第n位),再加上符号位0
(计数为1
),那么它最少需要n+1
位来表示;
如果是一个负数,则需要找到它**最高为0
**的是第几位(假设该位是第m
位),那么它最少需要m
位来表示
float
一开始是真的懵,想来是浮点数位级表示学得比较模糊
floatScale2
- floatScale2 - Return bit-level equivalent of expression 2*f for
- floating point argument f.
- Both the argument and result are passed as unsigned int’s, but
- they are to be interpreted as the bit-level representation of
- single-precision floating point values.
- When argument is NaN, return argument
- Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
- Max ops: 30
- Rating: 4
floatScale2:用unsigned的位级来表示一个浮点数uf,同时,对浮点数uf乘2
1 | unsigned floatScale2(unsigned uf) { |
思路:
区分规化数
、非规化数
、NaN
、INF
,注意区分的时候,都要乘2
第5行,不用exp<<1
的原因是:虽然能达到*2
的目的,但可能会使exp
越出255
,突破exp
限定的8
位
floatFloat2Int
- floatFloat2Int - Return bit-level equivalent of expression (int) f
- for floating point argument f.
- Argument is passed as unsigned int, but
- it is to be interpreted as the bit-level representation of a
- single-precision floating point value.
- Anything out of range (including NaN and infinity) should return
- 0x80000000u.
- Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
- Max ops: 30
- Rating: 4
floatFloat2Int:用unsigned
位级表示浮点数uf
强制转换为int
整型数
1 | int floatFloat2Int(unsigned uf) |
思路:
区分float
型的规化数
、非规化数
、INF
、NaN
,INF
、NaN
:其E >= 31
计算阶码值2^E
,超出int
型Tmax == 0x7fffffff
,直接输出0x80000000
非规化数:其E < 0
计算阶码值2^E
,为小数,直接输出0
规化数: 其31> E >=0
计算阶码值2^E
,居于1 ~ 2^30
之间,需要左右移
左右移原理见 CSAPP P82
其结论是:阶码E
大于尾数位数(float
型取frac
字段位数,共23
位)时,则M左移(23 - E)
(最多移8位);阶码E
小于尾数位数,则M
右移(23 - E)
(最多移23
位)
原解法对于E=23
时,直接输出tar=0
;对于E=31
时,将其看作仍可以做M
左移运算,事实上2 ^ E = 2 ^ 31
,已经超过int
型Tmax = (2 ^ 31) - 1
但检测可以通过 纠正1:虽然超过了Tmax
,但没有超过Tmin = - 2 ^ 31
,故,对于E=31
时,将其看作仍可以做M
左移运算。纠正2:只要E
取31
时,左移时,必然移动8
位到符号位且置1
,因为:规化数的阶码E
最后1
位为1
。那么,符号位为0
,frac
字段全0
时,其值为2 ^ 31
,左移8
位后为0x80000000
;符号位为0
,frac
字段非0
时,其值超过2 ^ 31,要置为INF(0x80000000)
;符号位为1
,frac
字段全0
时,其值为-2 ^ 31
,左移8
位后为0x80000000
;符号位为1
,frac
字段非0
时,其值为超过-2 ^ 31
,要置为INF
。综合下来,E = 31
时,可以直接置为INF
。
笔者做出修正
对于E=23
时,输出tar = sign*M
;对于E=31
时,直接输出0x80000000
(本题Anything out of range return 0x80000000
)
floatPower2
- floatPower2 - Return bit-level equivalent of the expression 2.0^x
- (2.0 raised to the power x) for any 32-bit integer x.
- The unsigned value that is returned should have the identical bit
- representation as the single-precision floating-point number 2.0^x.
- If the result is too small to be represented as a denorm, return 0.
- If too large, return +INF.
- Legal ops: Any integer/unsigned operations incl. ||, &&. Also if, while
- Max ops: 30
- Rating: 4
floatPower2:输入一个int x,用unsigned位级表示2.0 ^ x
1 | unsigned floatPower2(int x) { |
思路:
首先需要定义INF
,为exp
全1
即0xff << 23
;其次定义小于2 ^(-126-23)
为0
x
的取值即为E
,exp = E + bias
,可计算出指数exp
,而后
考虑临界值:bin(x)
= 0 00000000
00000000000000000000001
,此数是2 ^ (-126-23)
,(最小值)
即exp = 0
时情况;exp
小于等于0
,我们取 0x00400000>>( ~exp+1)
bin(x)
= 0 00000001
00000000000000000000000
,此数是2 ^ (-126)
,
即exp = 1
时情况;exp
居于0 ~ 254
时,我们取exp << 23
bin(x)
= 0 11111111
00000000000000000000000
,此数是2 ^ (128)
,(大于最大值)
即exp = 255
时情况。exp
大于等于exp
全1
,我们取INF
以及,exp
小于-23
时,超出最小值,我们取0
。
附图:
感想:
- 雄关漫道真如铁,而今迈步从头越
- 从9.4号开始做这个lab,一直到9.12号,一共9天。难度真是比较大,终于感受到很名校同学的差距了(特别是那道ASCII)。平时很少做这样的题。他们是把CSAPP当作ICS(计算机导论)来上的,我得向这些他们靠近。转专业并降级的我已经落后了不少,是时候好好学了。
- 现在看难度1、2觉得很容易,但一开始做的时候还是一头雾水的。位级运算的不但功能十分强大,而且还极大地减少运算时间。之后还得花点心思巩固。英文阅读能力非常需要提高,浮点题那一块,题目都看不太懂。以后做题前先翻翻书,想想知识点,不要直接凭空硬莽。
- 如有谬误,敬请指正。