位运算实验(下)
Data Lab: Manipulating Bits #2 - 对于实验一(Data Lab)的一些想法(下)。
这个实验要求学生实现简单的逻辑和算术运算函数,但是只能使用一个非常有限的C语言子集。比如,只能用位级操作来计算一个数字的绝对值。这个实验可帮助学生了解 C语言数据类型的位级表示,以及数据操作的位级行为。
Puzzle 1 - conditional
conditional - same as x ? y : z
Example: conditional(2,4,5) = 4
Legal ops: ! ~ & ^ | + << >>
Max ops: 16
Rating: 3
利用位操作来实现三目运算符表达式x ? y : z
的功能,只需判断x
是否为0
:若不为0
则返回y
;反之则返回z
。
同样可以利用算术右移的特点制作一个掩码(mask),类似#1中提到的“模版”,以便于操作。
1 | int conditional(int x, int y, int z) |
Puzzle 2 - isNonNegative
isNonNegative - return 1 if x >= 0, return 0 otherwise
Example: isNonNegative(-1) = 0. isNonNegative(0) = 1.
Legal ops: ! ~ & ^ | + << >>
Max ops: 6
Rating: 3
判断符号位即可。
1 | int isNonNegative(int x) |
Puzzle 3 - isGreater
isGreater - if x > y then return 1, else return 0
Example: isGreater(4,5) = 0, isGreater(5,4) = 1
Legal ops: ! ~ & ^ | + << >>
Max ops: 24
Rating: 3
比较大小,显然当x
为正且y
为负时,一定有x > y
成立;当x
为负且y
为正时,上式一定不成立。
当x
与y
同号时,判断二者之差是否为正即可。注意题目要求,当二者相等时返回0
,故此处作差时减1
。
判断正负依然利用符号位。
1 | int isGreater(int x, int y) |
Puzzle 4 - absVal
absVal - absolute value of x
Example: absVal(-1) = 1.
You may assume -TMax <= x <= TMax
Legal ops: ! ~ & ^ | + << >>
Max ops: 10
Rating: 4
正数的绝对值为其本身,负数的绝对值为其相反数。
位全0取反为-1(~0 + 1 = 0
),全1取反为0。
依然可以利用符号位右移作为掩码,当x
为正时掩码全0,异或后仍为原值;当x
为负时掩码全1,异或后相当于取反的效果,再加1即得到其相反数。
1 | int absVal(int x) |
Puzzle 5 - isPower2
isPower2 - returns 1 if x is a power of 2, and 0 otherwise
Examples: isPower2(5) = 0, isPower2(8) = 1, isPower2(0) = 0
Note that no negative number is a power of 2.
Legal ops: ! ~ & ^ | + << >>
Max ops: 20
Rating: 4
一个数为2的幂次,即其二进制位有且仅有1位为1。
不妨设其中1所在最低位为第n
位,则减1后其0~n-1
位均变为1,而第n
位变为0——0~n
为与原值相反,n
位以上与原值相同。此时将减1后的值与原值取与,可知取与结果的低n+1
位必为0。若原值在n
位以上仍存在1,则取与结果必不为0,由上段推论知,此时原值必不为2的幂次。若原值在n
位以上全0,则取与结果必为0,此时原值必为2的幂次。
显然非正值必不为2的幂次。
1 | int isPower2(int x) |
Puzzle 6 - float_neg
float_neg - Return bit-level equivalent of expression -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 representations of single-precision floating point values.
When argument is NaN, return argument.
Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
Max ops: 10
Rating: 2
本题需要对浮点数的构成有基本的了解,鉴于下题需要对浮点数有极为全面的认识,此处建议提前阅读IEEE754与单精度浮点数,注意文章中对NaN
的定义并思考相反数的求法。
0x7fffffff = 0111 1111 1111 1111 1111 1111 1111 1111
0x7f800000 = 0111 1111 1000 0000 0000 0000 0000 0000
0x80000000 = 1000 0000 0000 0000 0000 0000 0000 0000
本题只需判断参数uf
是否为NaN
,是则返回其本身,否则返回其相反数。
制作低31
位全1的掩码,同原值取与以保留参数uf
除去符号位的值,取与结果同0x7f800000相比可判断原值是否为NaN
。
求相反数只需变换符号位,同掩码0x80000000异或即可。
1 | unsigned float_neg(unsigned uf) |
Puzzle 7 - float_i2f
float_i2f - Return bit-level equivalent of expression (float) x
Result is returned as unsigned int, but it is to be interpreted as the bit-level representation of a single-precision floating point values.
Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
Max ops: 30
Rating: 4
本题需将int
型参数x
转化为单精度浮点数float
型数值,即实现类型转换(float)x
的功能。如果你对浮点数没有基本的了解,可能会认为int
型x
是整数,转化为float
型就不存在小数了。为避免类似一些误解,需要对浮点数有极为全面的认识,此处建议提前阅读IEEE754与单精度浮点数。
整数转化为浮点数,首先将整数的二进制编码转化为科学计数法$m\times 2^{e}$的形式,其中$m$的小数部分便对应浮点数的fraction
位,$e$对应浮点数的exponent
位,符号位互相对应。例如$78=1001110_{2}=1.00111\times 2^{6}_{2}$,故sign
位为0,exponent
位为$6+127=133=100 00101_{2}$,fraction
位为$001 1100 0000 0000 0000 0000$。
由于浮点数由sign
、exponent
、fraction
三部分组成,可以分三部分来进行转换。
Declare Variables
sig
、exp
、frc
分别代表浮点数的三部分;frcMsk
为fraction
位全1的掩码,用于存放fraction
位;eBit
指向x
中1的最高位。
1 | int sig = (x >> 31) & 1; |
Judge Special Value
特殊值只需返回特定值。
当x
的值为0x80000000时,即$-2^{31}$,是有符号int
型最小值。为避免操作可能导致的溢出错误,此处直接返回其float
值,即exponent
部分为$31+127=158$,其他位均为0。
1 | if (x == 0) |
Judge Sign
负数转化为其相反数进行运算。
1 | if (sig) |
Get Exponent Bits
当右移eBit
位后为0,而右移eBit-1
位不为0时,说明eBit-1
位1所在最高位。例如$78=1001110_{2}$,右移6
位不为0,而右移7
位为0,故1所在最高位为第6
位。
1 | while (x >> eBit) |
Get Fraction Bits
左移31-eBit
位,将1所在最高位移动到第31
位。当x
为$78=1001110_{2}$时,移动后即$$78=1001 1100 0000 0000 0000 0000 0000 0000_{2}.$$再右移到fraction的位最高位后$$0000 0000 1001 1100 0000 0000 0000 0000$$同掩码$$0000 0000 0111 1111 1111 1111 1111 1111$$取与,即可得到fraction
位。
至于为什么将最高位的1舍弃,请返回并继续阅读。
1 | x = x << (31 - eBit); |
Round to Even
上面的fraction
是将x
右移8
位后取得的,应该考虑被移出去的8
位是否满足进位的条件,若符合应该进行进位。
先取x
的低8
位,若大于一半或者等于一半,应该进位。注意此处进位为向偶数进位,即若等于一半且被进位的位置为0时不进位。
进位之后fraction
位会出现变化,若超出23
位长,应向exponent
进位,且进位后fraction
与掩码取与重置。
1 | x &= 0xff; |
Return Result
将各部分左移到相应位置取或即可。
1 | return (sig << 31) | (exp << 23) | frc; |
Source Code
1 | unsigned float_i2f(int x) |
Puzzle …
同时也要注意代码的规范问题,利用辅助工具可以协助提高代码的质量。
例如,运行./btest
以检查函数功能的正确性:
运行./dlc -e bits.c
以检查每个函数的运算符使用情况:
运行./driver.pl
以同时输出dlc
和BDD checker
的结果: