1.bitXor
例:
x=4 = 0100(二进制)
y=5 = 0101(二进制)
x^y= 1=0001(二进制)
对于两个二进制数字i和j,若i和j在某一位上相同(都为0或都为1),则它们的按位异或结果该位为0; 如若不同,即i和j在该位上一个为0,另一个为1,则它们的按位异或结果该位为1
我们可以利用上述特性来实现按位异或的操作
总的来讲我们要关心哪些位不相同,将都不相同的位置操作为1,相同的位置改为不相同,这样&之后得到的结果就是a和b的按位异或。
int xor(int a, int b) {
return ~(~a & ~b) & ~(a & b);
}
2.Tmin
返回int类型的最小值,即-2^31。
- << 运算符是按位左移运算符,将表达式左边的数值向左移动右边的指定数量的二进制位。
- 1<<31 表示将整数1的二进制位向左移动31位(移出的部分被丢弃),相当于将右侧的0填充到左侧的空缺位置,这样得到的数值就是int类型最小值的绝对值的二进制补码形式。
- 在二进制补码的表示方式中,最高位表示符号位,为1表示负数,为0表示正数。因此,1<<31 表示的值就是 int 类型最小值的绝对值(也就是 2^31)的二进制补码,最高位为1,表示其为负值。
- 返回结果时,该函数直接返回1<<31,也就是-2^31,即int类型的最小值。
int tmin(void) { return 1<<31;
因此,该函数的作用是返回int类型的最小值。
3. isTmax
1.~x: 取x的按位取反值,即将x的所有二进制位都取反(0变成1,1变成0),包括符号位。
2.x = ~x: 将按位取反后的值赋给x,原本的x值就被覆盖了。
3.(~x + 1): 将~x的值加1,相当于对~x进行取反加1的操作,由于int类型是二进制补码形式存储,所以最高位是符号位1,~x + 1的值就是int类型的最大值。
4.x ^ (~x + 1): 对x和(~x + 1)进行按位异或操作,得到的结果是如果x为int类型的最大值,则结果为0,否则结果不为0。
5.!(x ^ (~x + 1)): 对上面得到的结果取反,对于x为int类型的最大值来说,结果为1,否则为0。
6.!!x: 对x进行两次逻辑非(!)操作,得到的结果是x是否为非0值的bool值,即如果x为0,则结果为0,否则为1。
7.!(x ^ (~x + 1)) & (!!x): 将第5步和第6步的结果进行按位与(&)操作,得到的结果是如果x为int类型的最大值并且不为0,则结果为1,否则为0。
因此,整个函数的功能就是判断给定的整数x是否为int类型的最大值。
int isTmax(int x) {
x = ~x;
return ! (x ^ (~x + 1)) & (!! x);
}
4. alloldbits
这段代码的作用是判断给定的整数x二进制表示中,所有偶数位(从右往左数,从0开始计数)是否都为0。如果偶数位全部为0,则返回1,否则返回0。
解析如下:
1.int mask=0xAAAAAAAA; 定义了一个int类型的常量mask,其二进制表示中所有的奇数位(从右往左数,从0开始计数)都被设置为1,偶数位都被设置为0,如下图:
1010 1010 1010 1010 1010 1010 1010 1010
2.将mask与x执行按位与(&)运算。
3.执行按位与(&)运算后,结果的偶数位部分将全部为0,奇数位部分同样也全部为0。
如果x的所有偶数位都为0,那么按位与后得到的结果也应该全部为0,此时函数会返回1;否则返回0。
因此,该函数的作用是判断给定的整数x二进制表示中,所有偶数位(从右往左数,从0开始计数)是否都为0,并将结果返回。
int allOddBits(int x) {
int mask=0xAAAAAAAA;
return !(x&mask);
}
5. negate
在二进制中表示负数时,会把位权的最高位上的数字作为符号来使用,因此,位权的最高位也称为符号位。符号位为 0,表示正数;符号位为 1,表示负数。
这里我们用到的方法叫做“二进制的补数”,补数就是用正数来表示负数。求补数的方法叫做“取反加一”。
代码如下:
int negate(int x) {
return (~x+1);
}
6.isAsciiDigit
1.将x加上(~0x2f),得到的结果是(x-0x30)-1的16进制补码表示,其高八位都是1。然后判断这个结果是否小于等于0xFF,即判断(x-0x30)-1是否小于等于0xFF,这等价于判断x-0x30是否小于等于0x9,也就是判断x是否小于等于0x39。如果是,upper的值为1,否则为0。
2.将0x3a加上(~x),得到的结果是0x3a-x-1的16进制补码表示,其高八位都是1。然后判断这个结果是否小于等于0xFF,即判断0x3a-x-1是否小于等于0xFF,也就是判断x是否大于等于0x30。如果是,lower的值为1,否则为0。
3.将upper和lower进行按位与运算,如果上述两个条件都满足,则返回1,否则返回0。
需要注意的是,该函数假设char类型为有符号类型,在有符号类型的算术右移时会进行符号扩展,因此能正确地处理负数的情况。如果char类型为无符号类型,则需要将右移运算转换为逻辑右移运算,例如将(x + ~0x2f) >>8替换为((unsigned)x + ~0x2f) >>8即可。
int isAsciiDigit(int x) {
int upper = !((x + ~0x2f) >> 8);
int lower = !((0x3a + ~x) >> 8);
return upper & lower;
}
7.conditional
1.将x与逻辑非(!)运算符结合,得到x的逻辑非值。如果x为0,其逻辑非值为1;否则,其逻辑非值为0。
2.将逻辑非值用取反(~)运算符进行取反,得到一个全1或全0的掩码。其中,如果x不为0,掩码为0xFFFFFFFF;否则,掩码为0x00000000。这里使用按位取反后加1(~z+1)的方法,将全1掩码变成了0xFFFFFFFF,将全0掩码变成了0x00000000。
3.将掩码与y进行按位与(&)运算,得到的结果为y的值(如果x不为0),或者0(如果x为0)。将掩码取反后再与z进行按位与(&)运算,得到的结果为z的值(如果x为0),或者0(如果x不为0)。
4.将上述两个结果进行按位或运算(|)得到最终结果,即如果x不为0,则返回y,否则返回z。
int conditional(int x, int y, int z) {
int mask = ~(!x) + 1;
return (mask & y) | (~mask & z);
}
8.isLessOrEqual
1.计算x的符号值signx,如果x是非负数,则signx等于0;否则,signx等于-1。同理,计算y的符号值signy。
2.计算y-x的符号值diffsign,如果y-xsign等于1;否则,diffsign等于0。这里使用的是(y+~x+1)的方法来计算差值,相当于计算y-x的16进制补码表示。如果y-x超出了int类型的范围,则diffsign的值就不再是1或0,无法判断大小关系。因此,需要单独讨论符号相反时的情况。
3.判断x和y的符号是否相同。如果相同,则可以根据差值的符号来判断大小。如果不同,则必有y大于x(如果y为非负数),或者y小于x(如果y为负数)。
4.如果x和y的符号不同,且x为负数,则opposign等于-1,否则opposign等于0。
5.根据符号位和差值的符号来判断x是否小于等于y,即如果x和y符号不同,则必有y大于x(如果y为非负数),或者y小于x(如果y为负数);如果x和y符号相同,则可以根据diff_sign的值来判断大小。
需要注意的是,由于加减有可能会溢出,因此需要将符号相反的情况单独处理。
int islessOrEqual(int x, int y) {
int sign_x = x >> 31; // 得到x的符号位代表的数值,0代表正数,-1代表负数
int sign_y = y >> 31; // 得到y的符号位代表的数值,0代表正数,-1代表负数
int diff_sign = ((y + ~x + 1) >> 31) & 1; // 如果y-x<0,则diff_sign为1,否则为0
int same_sign = !(sign_x^sign_y); // 如果x和y符号相同,则same_sign为1,否则为0
int oppo_sign = sign_x & !same_sign; // 如果x和y符号不同且x为负数,则oppo_sign为-1,否则为0
return ((!same_sign) & (sign_y & !oppo_sign)) | (same_sign & !diff_sign); // 如果y>=x,则返回1;否则,返回0
}
9.logicalNeg
1.计算x的符号位sign,通过右移操作(x>>31)来提取最高位。如果x为正数,则sign为0;如果x为负数,则sign为1。
2.当x等于-1时,返回的结果应该是0,因为在2's complement中,-1的二进制表示中所有位均为1,即0xFFFFFFFF。因此,在x等于-1时,需要设置一个特殊掩码mask,该掩码的值为0xFFFFFFFF。否则,mask的值为0x00000000。
3.将sign与mask进行按位与运算,得到如果x等于-1,则将符号位取反(由0变为1);否则,保持符号不变。
4.将取反后的符号位与掩码(~mask)进行按位与运算,得到如果x不等于0的逻辑非值(即取反后的符号位的值),或者0。这一步将处理掉除了-1以外的所有情况。
5.将上述结果用按位与运算符(&)运算,得到0或1的结果。
int logicalNeg(int x) {
int sign = ((x >> 31) & 1); // sign取值为0或1,表示x的符号位
int mask = ~0 + !(x ^ -1); // 如果x等于-1,则mask等于0xFFFFFFFF;否则,mask等于0x00000000
return ((mask & sign) | (~mask & ~sign)) & 1; // 返回x是否为0的逻辑非值
}
10.howManyBits
该函数的功能是计算用二进制补码表示整数x所需的最少位数。需要注意的是,在使用二进制补码表示时,正数和负数所需的位数可能不同。
1.计算x的符号位mask。如果x为正数或0,则mask值等于0;如果x为负数,则mask值等于-1。
对于负数x,为了能正确地计算它所需的位数,需要将x取反后再进行计算。因此,需要把x取反,即使用(mask & (~x)) | ((~mask) & x)的方法获取x的相反数。
2.接下来,需要统计需要对x进行多少位二进制补码表示。为了最小化所需的位数,采用了分治的方式。该算法采用了类似于二分的思想,每次砍掉一半不需要的位,从而减少所需比特位数的计算。具体来说,首先判断x的高16位是否有值。如果有值,则至少需要16位才能表示x;否则,需要用低位存储x。即通过(!(!(x >> 16)) << 4)的方法得到这一计算所需的位数,注意只有0或1两种情况。
3.接下来,使用x = x >> bit_num将剩余需要表示的比特位数保存在x中。
4.接着,在x中使用类似的方法计算剩余比特位数,直到判断完成8位、4位、2位和1位。计算剩余位数所需的比特位数时,要使用(!(!(x >> k)) << m)的方式。其中k为本次位数比较的起始位,m为需要加上的比特位数。在每次计算的结束处,将剩余需要表示的位数保存在x中,以便下一次比较时使用。
6.在计算完所有的位数后,将所有的位数相加,得到需要表示
int howManyBits(int x) {
int mask = x >> 31; // 如果x为正数或0,则mask为0;如果x为负数,则mask为-1
int bit_num = 0; // 记录需要表示x所需的比特位数
x = (mask & (~x)) | ((~mask) & x); // 如果x为正数或者0,则不变;如果x为负数,则取反
bit_num = !(!(x >> 16)) << 4; // 如果x的高16位有值,则需要至少16位存储;否则,还需低位存储
x = x >> bit_num; // 取得剩余的需要表示的比特位数
bit_num = bit_num + (!(!(x >> 8)) << 3); // 通过判断剩余位数是否需要高8位存储来更新bit_num
x = x >> (bit_num & 7); // 取得剩余的需要表示的比特位数
bit_num = bit_num + (!(!(x >> 4)) << 2); // 通过判断剩余位数是否需要高4位存储来更新bit_num
x = x >> (bit_num & 3); // 取得剩余的需要表示的比特位数
bit_num = bit_num + (!(!(x >> 2)) << 1); // 通过判断剩余位数是否需要高2位存储来
11.floatScale2
该函数的实现可以分为以下几个步骤:
从uf中获取符号位、指数位和尾数位。
判断指数位是否为0xFF,如果是,则表示uf为NaN或Infinity,此时返回uf。
如果指数位为0,即uf为denorm,则将尾数位左移一位表示2f。
如果指数位为正数,则是normal数,将指数位加1表示2f。
将符号位、指数位和尾数位按位或运算,获得2f的比特位表示。
返回得到的结果。
需要注意的是,在计算2f时,需要检查uf是否为NaN或Infinity等特殊情况,并选择适当的运算。同时,也需要考虑denorm数的情况。本实现中,使用的是位运算符,以便在不使用显式转换的情况下实现将浮点数的比特位表示按照指定方式进行调整的操作。
unsigned float_twice(unsigned uf) {
int sign = uf >> 31; // 获取符号位
int exp = (uf >> 23) & 0xFF; // 获取指数位
int frac = uf & 0x7FFFFF; // 获取尾数位
if(exp == 0xFF) { // 如果是 NaN or Infinity
return uf;
}
if(exp == 0) { // 如果是 denorm, 将尾数位左移一位
frac <<= 1;
} else { // 如果是 normal, 将指数位加1
exp++;
}
return (sign << 31) | (exp << 23) | frac; // 将三个位按位或运算得到结果
}
12.floatFloat2Int
1.从uf中获取符号位、指数位和尾数位。
2.计算指数exp,即减去偏置常数127,得到实际的指数位值。
3.如果exp小于0,表示f的值小于1,不能转换为整型,此时返回0。
4.如果exp大于等于31,表示f太大了,转换为整型会越界,此时返回0x80000000u。
5.如果exp在0到30之间,则可以将uf转换为整型。考虑符号位,如果sign为1,表示f是负数,将在下一步进行处理。
6.根据exp对尾数frac进行左移或右移操作。如果exp大于23,则将frac左移(exp-23)位,如果exp小于等于23,则将frac右移(23-exp)位。
7.如果sign为1,即f为负数,需要将frac取反,然后加上1,得到其补码形式。
8.返回转换后的整型值。
需要注意的是,在将uf转换为整型时,需要检查f的值是否在转换的范围内。由于uf可能表示NaN或Infinity等特殊值,在转换时需要做出特殊处理。
int floatFloat2Int(unsigned uf) {
int sign = uf >> 31; // 获取符号位
int exp = ((uf >> 23) & 0xFF) - 127; // 获取指数位,并减去偏置常数
int frac = (uf & 0x7FFFFF) | 0x800000; // 获取尾数位,并将整数位添加为1
if(exp < 0) { // 如果f的小于1,不可能转换为整型
return 0;
} else if(exp >= 31) { // 如果f太大,超出整型范围,则返回0x80000000u
return 0x80000000u;
} else {
if(exp > 23) { // 转换的值大于1,通过左移尾数位实现
frac <<= (exp - 23);
} else { // 转换的值小于1,通过右移尾数位实现
frac >>= (23 - exp);
}
if(sign == 1) { // 如果f是负数,转换后也为负数
frac = ~frac + 1; // 取反并加上1,得到补码
}
return frac; // 返回转换后的整型值
}
}
13. floatPower2
1.计算指数exp,即加上偏置常数127,得到单精度浮点数f的指数位值。
2.如果exp小于等于0,则表示f的值太小了,不能表示为单精度浮点数。此时,函数将返回0。
3.如果exp大于等于255,则表示f的值太大了,不能表示为单精度浮点数。此时,函数将返回+INF。
4.如果exp在1到254之间,则f可以表示为单精度浮点数,函数将返回其bit-level等价值。
5.返回转换后的单精度浮点数的比特位表示,其中指数部分通过左移23位得到,其他比特位的值为0。文章来源:https://www.toymoban.com/news/detail-847711.html
需要注意的是,为了正确地表示单精度浮点数f的值,需要检查指数的值是否位于区间[1, 254]之间,才能将f的比特位表示计算得到。计算指数部分的值时,需要将x加上一个偏置常数。计算结果要进行位运算和移位文章来源地址https://www.toymoban.com/news/detail-847711.html
unsigned floatPower2(int x) {
int exp = x + 127; // 获取指数位,加上偏置常数
if(exp <= 0) { // 如果指数太小,则f表示的值为denorm或零,返回0
return 0;
} else if(exp >= 255) { // 如果指数等于或大于255,则f表示的值为无穷大,返回+INF
return 0x7F800000u;
}
return exp << 23; // 得到正确的比特位表示,指数对应移位即可
}
到了这里,关于CSAPP实验----datalab(详解)(附每一步的讲解)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!