Computer Systems : A Programmer's Perspective (CS:APP) 学习专栏
\(Computer \ Systems:A \ Programmer's \ Perspective \ (CSAPP)\) 学习专栏
\(Chapter\ 2 \ Representing \ and \ Manipulating \ Information\)
P47二进制转补码:\(B2T_w(x)=-x_{w-1}2^{w-1}+\displaystyle\sum_{i=0}^{w-2}x_i2^i\)
P48一个非常有意思的点:补码\(Two's\ complement\)和反码\(Ones'\ complement\)中 \('\) 号的位置不同,来源于这样的情况
- 对于一个非负数\(x\),我们用\(2^w - x\)(这里只有一个2)来计算\(-x\)的w位表示。而反码我们使用\([111...11]\)(w个1)来计算-x的w位反码表示
P49 Practice2.18 当我们确定位级表示后,只有最高位为\(-2^w\),例如对于32位机器来说,只有第31位为1才需要在结果里最高位表示数上加上负号,换句话说,0xff在32位机器里是正数,最高位不能表示成负数,只有十六进制表示成0xXXXXXXXX共八位时,最高位为8~f这个数才为负数。
P49 unsigned u = 4294967295u;,末尾的u表明这个数是unsigned,C的特性。
P50 \(T2U_w(x) = x+x_{w-1}2^w\)
P51 \(U2T_w(u)=-u_{w-1}2^w+u\)
P53 在C语言中,如果它的一个运算数是有符号的而另一个是无符号的,那么C语言会隐式地将有符号参数强制类型转换为无符号数。
P54 一个有意思的点,当扩展一个数的位表示时,无符号数采用零扩展(\(zero \ extention\)),即扩展位填0,补码数字采用符号扩展(\(sign \ extention\)),即高位填原本最高有效位的值,也就是说原本最高有效位为0,则扩充位均为0,原本最高有效位为1,则扩充位均为1.
- 举个例子,将3位[101]扩展到4位变为[1101],我们可以发现,当扩展到w=4,最高两位的组合权重为-4与w=3时最高位的权重-4相同,这样就可以保证补码数进行位扩展时数值不会发生改变。
P56 当位数转换和符号转换同时发生时,会发生很有意思的现象,位数和符号转换的先后顺序会大大影响最终的数值,C语言标准要求先改变位大小再完成符号转换。
- 例如将
short sx转换成unsigned,先进行(int) sx完成16位→32位转换,再进行(unsigned)(int)sx完成signed→unsigned。
P57 截断数字
- unsigned : $x' = x \mod{2^k} \(,截断为k位。\)\mod{2^k}$能将k+1位及以上全部取模丢弃
- two's complement : \(U2T_k(B2U_w(...)\mod{2^k})\)
P62 无符号数加法中的溢出,溢出后小于任何一个原数。
-
若溢出有\(sum = x+y-2^w\),由于x和y均为小于\(2^w\)的无符号数,则有
sum < x && sum < y -
无符号数求反(无符号数的加法逆元),当x!=0,我们通过让x+(-x)的值在第w+1位为1 其余位为0的情况,求出在w位下的加法逆元。
\[-^u_wx = \begin{cases} x,\ \ \ \ \ \ \ \ \ \ x=0\\ 2^w-x, \ x>0\\ \end{cases} \]
P65 补码加法时,有一个需要注意的点就是负溢出是发生在第w+1位进为1的情况,而正溢出则是只要大于\(T_{max}\)就会发生正溢出,例如对于w=4,4+4就会正溢出,\(>T{min}=7\)
P66 计算一个位级的补码非
- 我们之前学过了 取反+1来计算补码非
- 我们找到x二进制表示下的从低位到高位的第一个1,将这个1左边的所有位取反就能得到x的补码非表示。
- 举个例子,计算[1010]的补码非,第一个1出现在第2位,将其左边位取反,[0110]就是补码非。
P73 补码除法时,使用“偏置”来修饰不合适的舍入
- 当进行负数除法时,我们采取的策略应是向零舍入,而非向下舍入,所以我们需要偏置
- 我们利用属性\(\lceil x/y \rceil=\lfloor x+y-1/y\rfloor\)来把负数除法需要的向上取整转化为向下取整。
- 计算数值\(x/2^k\),我们可以利用表达式
(x < 0 ? x + (1 << k) - 1 : x) >> k
P74
补码除法的不同主要是正数和负数的不同,我们判断x是正或负,取决于其最高有效位,在32位字长下用x>>31就可以看需不需要用偏置来向上取整。&0xf后若为负数就可以完成+y-1的操作,在这里是\(2^4-1=15\)
int divi16
{
return (x + (x >> 31 & 0xf)) >> 4;
}
P79 非规格化值为什么要设置偏置值位1-Bias
- 为了与规格化值平滑连接(表示小于1的数)
P87 关于练习题2.54 x默认为int型,f默认为float型,d默认为double型
-
B.
x == (int)(float)x,当x为\(T_{max}\)时等式右边的结果为\(T_{min}\),等式不成立,下面我们来分析一下- 我们先将\(T_{max}\)转化为IEEE浮点数表示,\(T_{max}\)就是第32位为0其余位为1,转化为IEEE浮点数表示就是Sign位为0,阶码位为127+30,尾数表示就是\(1.111..1 \times 2^{30}\),但此时float型也就是单精度IEEE,只有23位用于表示尾数,剩下位只能向偶数舍入,也就是变成Sign 0 阶码 127+30+1 小数部分0000(23个0),再转化为int,我们发现就变成了\(1\times 2^{31}\)也就是只有最高位为1,转化后就是\(T{min}\)。所以等式不成立。通过这道题我们就可以发现虽然float能表示的数可以很大,但是表示不精确,因为他的小数位只有23位,只要当前数二进制表示下最高位的1和最低位的1之间大于23位都需要进行舍入。
-
H.
(f + d) - f == d等式不成立-
浮点数的加法很多时候都需要舍入,举个例子\((1.0 + 1.0e20)-1.0e20\)由于舍入计算后实际上为0,我们来模拟一下
double型二进制下小数位有64-1-11=52位,也即是说二进制表示下最高位的1和最低位的1之间大于52位都需要进行舍入。
1.0e20也就是\(1.0\times 10^{20}\)最高位肯定大于\(2^{52}(约4.5\times10^{15})\),那么他和1.0之间的位数肯定大于52位就需要舍入,1.0就被舍入掉了,只保留结果1e20,1e20-1e20=0所以等式不成立
-
Homework
记录一道非常有思考价值的题目
2.65
/*Return 1 when x contains an odd number of 1s; 0 otherwise. Assume w = 32*/
int odd_ones(unsigned x);
题目的意思是说当x的二进制表达式有奇数个1时,返回1,否则返回0。这里假设int有32位。
读题都没读懂,英语还不太过关。
那么怎么来判断x里有多少个1呢?
利用逻辑运算符异或^,我们设想一下,由于异或运算1 ^ 1 = 0 ^ 0 = 0,1 ^ 0 = 0 ^ 1 = 1,也就是说当两个数进行异或运算时,当结果为0,这两个数中不可能有奇数个1,而当异或结果为0,必定两个数中有奇数个1。
由于这么写代码太复杂,我们可以找些特性来化简,不妨将x切成两半,让x的前一部分与后一部分先进行异或运算,再将结果切成两半,再让前一部分与后一部分进行异或运算,持续切分,直到x只剩1位,这样就可以实现x的所有位之间都实现了异或运算。
遵循结合律和交换律,可以先交换将每两位之间结合,再将结果与其余部分结合。
我在代码中解释细节
/*
* odd-ones.c
*/
#include <stdio.h>
#include <assert.h>
int odd_ones(unsigned x) { //假设x为32位
x ^= x >> 16; //将x切分为两半 x>>16高位右移到与低位平齐,高位与低位进行异或运算
//此时不用管x>>16后导致的高位全为0与x高位异或的结果
//x31与x15 x30与x14 ... x15与x0之间都已异或完成,结果保留至x15~x0
x ^= x >> 8; //再切分 现在进行x15~x8与x7~x0之间的异或运算,结果保留至x7~x0
x ^= x >> 4; //再切分 现在进行x7~x4与x3~x0之间的异或运算,结果保留至x3~x0
x ^= x >> 2; //再切分 现在进行x3~x2与x1~x0之间的异或运算,结果保留至x1~x0
x ^= x >> 1; //再切分 现在进行x1与x0之间的异或运算,结果保留至x0
x &= 0x1; //x与0x1进行&运算,取出x0的值,若为1表明有奇数个1,若为0表明有偶数个1,将结果返回
return x;
}
int main(int argc, char* argv[]) {
assert(odd_ones(0x10101011));
assert(!odd_ones(0x01010101));
return 0;
}
2.66
/*
* Generate mask indicating leftmost 1 in x. Assume w=32
* For example, 0xFF00 -> 0x8000, and 0x6000 -> 0x4000.
* If x = 0, then return 0
*/
int leftmost_one(unsigned x)
leftmost_one函数是让我们在x的二进制表达式中只保留最左边一位的1,其余全部置零
这道题有和上一题类似的地方,也有截然不同的思路
模拟样例0xFF00(1111 1111 0000 0000) -> 0x8000(1000 0000 0000 0000)
那么我们怎么实现只保留最左边的1呢,不妨采用移位,让最左边的1每次右移与与右移处的位置取|,让右边所有位置都变为1,再将整个数右移一位+1,就能实现要求。
举个例子 假设最左边的1位于第b位
0110 0110 0100 0000 >>1 | x
0011 0011 0010 0000 --> 0111 0111 0110 0000 >>2此时第b位和b+1位都为1,而最左边1之前的0并没有影响
0001 1001 1001 0000 --> 0111 1111 1111 0000 >> 4 此时第b位,第b+1位,第b+2位,第b+3位均为1,所以下一步我们可以位移4位,让最左边的1之后的8位均为1
依次类推,当位移w/2位之后,最左边1之后所有位均为1,只要x存在1(用x&&1来判断),我们让x右移1位加1就可以得到答案
0111 1111 1111 1111 >> 1 + 1 = 0100 0000 0000 0000
int leftmost_one(unsigned x) {
/*
* first, generate a mask that all bits after leftmost one are one
* e.g. 0xFF00 -> 0xFFFF, and 0x6000 -> 0x7FFF
* If x = 0, get 0
*/
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
/*
* then, do (mask >> 1) + (mask && 1), in which mask && 1 deals with case x = 0, reserve leftmost bit one
* that's we want
*/
return (x >> 1) + (x && 1);
}
2.78、2.79、2.80是三道层层递进,紧密联系的题,放到一起看,在此不得不感叹本书编写的精妙
2.78要我们用正确的舍入方式计算\(\frac{x} {2^k}\),并且需要遵循位级整数编码规则。
C语言默认向下取整,所以当x为负数时,向下取整不满足舍入要求,我们要将其改为向上取整,需要用到偏置修正

/*
* divide-power2.c
*/
#include <stdio.h>
#include <assert.h>
#include <limits.h>
/*
* Divide by power of 2, -> x/2^k
* Assume 0 <= k < w-1
*/
int divide_power2(int x, int k) {
int is_neg = x & INT_MIN; //&INT_MIN INT_MIN是最小的负数,只有最高位为1其余位全为0,而x是负数的必要条件就是最高位为0
(is_neg && (x = x + (1 << k) - 1)); //若x为负数,进行偏置修正后再右移k位
return x >> k;
}
int main(int argc, char* argv[]) {
int x = 0x80000007;
assert(divide_power2(x, 1) == x / 2);
assert(divide_power2(x, 2) == x / 4);
return 0;
}
2.79要求我们计算3x/4,需要遵循位级整数编码规则,你的代码计算3 * x也会产生溢出,要求计算3x也会溢出就要求我们先乘后除以此来保证精度,如果先除法就是对x右移,会丢失精度,这也是题目的用意。这道题也要注意进行除法时的舍入要求,正数和负数不同
/*
* mul3div4.c
*/
#include <stdio.h>
#include <assert.h>
#include <limits.h>
/*
* code from 2.78
*
* Divide by power of 2, -> x/2^k
* Assume 0 <= k < w-1
*/
int divide_power2(int x, int k) { //和上题一样的对负数进行处理后再进行除法
int is_neg = x & INT_MIN;
(is_neg && (x = x + (1 << k) - 1));
return x >> k;
}
int mul3div4(int x) {
int mul3 = (x << 1) + x; //先进行乘法
return divide_power2(mul3, 2);
}
int main(int argc, char* argv[]) {
int x = 0x87654321;
assert(mul3div4(x) == x * 3 / 4);
return 0;
}
2.80就是将上两题积累的思路汇总,要求对于整数参数x,计算\(\frac {3*x}{4}\)的值,向零舍入。
他不会溢出。
本题又需要保证精度,又要保证不会溢出,怎么操作?
由于最终要除以4,我们不妨将>>2导致会丢失的低两位保存起来,单独计算,定为xx
这样x>>2就不怕低两位丢失,先除后乘也不会溢出
最后再将x和xx的计算结果相加即可
#include <stdio.h>
#include <limits.h>
int threefourths(int x)
{
int xx = x&0x3; //利用低两位的掩码保存低两位的值,记录为xx
int is_neg = x & INT_MIN; //判断是否是负数
(is_neg && (xx = ((-x)&0x3))); //若x为负数,那么xx的值也需要修改,使其也表示为负数 例如 1001 利用掩码记录的xx=01,由于x是负数,-x=0111, 修改后的xx=11,这样才能计算出正确的结果
(is_neg && (x = x + (1 << 2) - 1)); //对x进行舍入修正
x=x>>2; //先除,此时丢失低两位
xx=(((xx<<1)+xx)>>2);//xx只有两位先乘后除不会溢出.
(is_neg && (xx = -xx)); //回归原值,因为最终要加到低两位上
return (x<<1)+x+xx; //对x进行乘法,之前进行了除法此时不会溢出,我们就能计算出高两位的精确结果,加上低两位xx的计算结果就是答案
}
int main()
{
printf("3result=\t%d\n",threefourths(3));
printf("-3result=\t%d\n",threefourths(-3)); //很好的例子,为什么当x为负数时,我们要修改x为-x的值
printf("150result=\t%d\n",threefourths(150));
printf("-150result=\t%d\n",threefourths(-150));
printf("-10result=\t%d\n",threefourths(-10));
return 0;
}
2.84 第一道提出了自己创新点的Homework
这道题需要补充函数的return部分,当x<=y时返回1,否则返回0。
利用符号位分成符号相同和符号不同的部分即可。
这道题有个特别需要注意的点就是在IEEE标准浮点数表示下,0.0与-0.0是不同的,-0.0的sign位是1而0.0是0,这也导致转换为Unsigned时,ux与uy差距很大,这时我们需要用一个特殊的判断条件,对于只有符号位为1时其补码表示与无符号表示的二进制表达式相等,由于做==判断有unsigned存在,所有类型会强制转换成unsigned,所以对于-0.0的特殊性,我们用如下语句判断。(ux == -ux && uy == -uy)。
/*
* float-le.c
*/
#include <stdio.h>
#include <assert.h>
#include <bits/stdc++.h>
using namespace std;
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 ((sx == sy) && (ux <= uy)) || (sx == 1 && sy == 0) || (ux == -ux && uy == -uy); //the last brackt is uesd for check -0.0
}
int main(int argc, char* argv[]) {
cout << float_le(-0, +0) << endl;
cout << (float_le(+0, -0)) << endl;
cout << float_le(1, 1) << endl;
cout << float_le(4, -0) << endl;
cout << float_le(-4, 4) << endl;
cout << (float_le(-0.0, +0.0)) << endl;
cout << (float_le(+0.0, -0.0)) << endl;
return 0;
}
Datalab
logicalShift
任务描述
本关任务:补充函数logicalShift(),将x逻辑右移n位(0<=n<=31) ,将结果return返回。
操作符使用数量限制:20
不做不知道,原来到现在我对算术右移和逻辑右移的理解都是错的,我一直以为算术右移是将二进制表示中的最高的那个1右移,实际上并不是,而是将位于最高位的符号位右移,在补码表示下符号位就是最高位,例如对于32位机器,符号位就是最高位。
举个例子,将0110 0010 算术右移1位的结果是0011 0001,而将1000 0111 算术右移1位的结果是1100 0011,这下才终于搞清楚了,两种右移的区别点只在于对于符号位的处理。
那么理清概念后,本题的处理就简单多了,由于题目是对32位机器进行处理,符号位位于第32位,只需要处理出1个高n位为0 低32-n位为1的数 与 算术右移后的x>>n进行&操作后,就可以算出逻辑运算后的结果了。
int logicalShift(int x, int n)
{
/********* Begin *********/
int y = (1 << 31) >> n;
y <<= 1; //先将高n位处理成1 , 注意这里是算术右移n-1位,至于不直接写成y = (1 << 31) >> (n - 1)的原因是本题n可能为0,n-1会造成错误
x >>= n;
return (x & (~y));
/********* End *********/
}
bitCount
任务描述
本关任务:补充函数bitCount(),统计x的二进制表示中1的数量,将结果return返回。
操作符使用数量限制:40
我们需要统计出x二进制表示中1的数量,不妨设想,我们让相邻两位加起来,最后得出的数就是相邻两位二进制下1的个数,例如 11 我们将高位的1也挪到低位后再相加,就是10,也就是2,满足要求,所以本题关键就是构造合适的掩码取出我们需要的位。
我们将32位先分成16组,每组有2位,计算出每组的二进制1的个数。由于每组内都要放到低位计算,所以每组的掩码就是01,先取出低位后,将32位数>>1,再与01取&后,就可以取出每组内高位的数。由于每组的掩码是01,在32位十六进制表示下就是t1=0x55555555,我们进行c = (x & t1)+(x>>1 & t1);就可以将1的个数放到每一组内。
然后再将32位分为8组,相邻4位为一组,将之前高两位和低两位1的计算结果相加放在本组内,放到低位计算,所以掩码就是0011,32位下即为t2=0x33333333,高位右移两位到低位相加,注意我们利用的是之前相邻两位计算出1的个数之后的结果,也就是 c = (c & t2) + (x >> 2 & t2)。
以此类推,再分为4组,每组8位,掩码为0x0f0f0f0f,将高4位与低4位1的个数相加,再分为2组,掩码为0x00ff00ff,将高8位与低8位1的个数相加,最后分为1组,掩码为0x0000ffff,将高16位与低16位计算结果相加,就是最终1 的个数。
int bitCount(int x)
{
/********* Begin *********/
int c, t1, t2, t3, t4, t5;
t1 = 0x55 | ((0x55) << 8);
t1 = t1 | (t1 << 16); //t1 = 0x55555555
t2 = 0x33 | ((0x33) << 8);
t2 = t2 | (t2 << 16);
t3 = 0x0f | ((0x0f) << 8);
t3 = t3 | (t3 << 16);
t4 = 0xff | ((0xff) << 16);
t5 = 0xff | ((0xff) << 8); //t5 = 0x0000ffff
c = (x & t1) + ((x >> 1) & t1);
c = (c & t2) + ((c >> 2) & t2);
c = (c & t3) + ((c >> 4) & t3);
c = (c & t4) + ((c >> 8) & t4);
c = (c & t5) + ((c >> 16) & t5);
return c;
/********* End *********/
}
bang
任务描述
本关任务:补充函数bang(),不使用!实现!操作符,将结果return返回。
操作符限制:~ & ^ | + << >>
操作符使用数量限制:12
本题是实现操作符!,对于!来说,运算结果只在非0数和0之间有区别,不妨列个样例找找规律,非0数1010 、0000 ,取~后 0101 、1111,我们可以发现在位数只有4位时,所有数中只有0的反码+1会溢出,我们可以利用这个特质,也就是说当我们对数x计算x | (~x + 1)后的结果中,只有当x=0时,计算结果的最高位才为0,其余情况由于不会发生溢出,x | (~x + 1)的结果中最高位必然为1。
int bang(int x)
{
/********* Begin *********/
return ((x | (~x + 1)) >> 31) + 1;
/********* End *********/
}
fitsBits
任务描述
本关任务:补充函数fitsBits(),如果x可以只用n位补码表示则返回1,否则返回 0 (1<=n<=32)。
- 操作符使用数量限制:15
第一次独立做出来稍微有点难度的题了,判断x是否能在n位内表示出来,实际上就是判断是否满足Tmin <= x <= Tmax的条件,注意当n=32是肯定成立的(偷懒)
int fitsBits(int x, int n)
{
/********* Begin *********/
return n != 32 ? (x < (1 << (n - 1))) && (x >= -(1 << (n - 1))) : 1;
/********* End *********/
}
法二
一个很妙的方法
我们知道如果数x能在n位下表示,假设n为4
- 如果x>=0,则x在第32位到第n位都应为0,例如0000....0111(7)
- 如果x<0,则x在第32位到第n位都应为1,例如1111....1000(-8)
而当数x不能在n位下表示时,第32位到第n位就不会全为0或全为1
所以我们可以用将x >> (n - 1)的方法来判断,若x >> (n - 1)的值不等于0或-1就不能在n位下表示。
int fitsBits(int x, int n)
{
/********* Begin *********/
int k;
k = n + ~0; //~0就是-1
x >>= k;
return !x || !(x + 1);
/********* End *********/
}
isAsciiDigit
//3
/*
* 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
*/
int isAsciiDigit(int x) {
int flag1 = !(x >> 4 ^ 0x30 >> 4); //check high
int y = x & 0xf;
int flag2 = (y + ~0xa + 1) >> 31; //check low
return flag1 & flag2;
}
conditional
/*
* conditional - same as x ? y : z
* Example: conditional(2,4,5) = 4
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 16
* Rating: 3
*/
int conditional(int x, int y, int z) {
int flag = !(x); //判断x是否为0
int t = 0xff << 8 | 0xff;
t = t << 16 | t;
return (y & (flag + t)) + (z & (!flag + t)); //进行掩码处理,若x不为0 flag=0 输出y 就进行y & 0xffffffff操作
}
isLessOrEqual
/*
* isLessOrEqual - if x <= y then return 1, else return 0
* Example: isLessOrEqual(4,5) = 1.
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 24
* Rating: 3
*/
int isLessOrEqual(int x, int y) {
return ((x >> 31 ^ 0) | (y >> 31 ^ 1)) & (((x + ~y + 1) >> 31) || !(x ^ y) || ((x >> 31) & !(y >> 31))); //核心就是分类判断,要注意的点就是当异号的时候做减法可能会溢出,所以将异号的情况单独判断
}
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
*/
int logicalNeg(int x) { //实现!的操作
//return ~((((~x + 1) ^ x) >> 31) & 1) + 2; //本来我想用这种方法的,但是Tmin的补码Tmax+1==Tmin也满足(~x + 1) ^ x == 0 这个数与0的唯一区别是 0的补码和原码最高位都为0,而这个数为1 故采取|
return ~((((~x + 1) | x) >> 31) & 1) + 2; //记住,0和其他数的区别就是 x == ~x+1(也可以理解为0的补码和0的符号位相同)同时符号位为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
*/
//本题是要找出能表示x的最低位数 对于正数 是最左边一位1所在位 + 1 对于负数 是最左边一位0 + 1
int howManyBits(int x) {
int flag = x >> 31; //判断是正数还是负数
x = ((~flag) & x) | (flag & (~x)); //将负数利用反码切换为正数,这样可以统一只需找出最高的一位1
//先考虑高16位是否有1
int bit_16 = (!!(x >> 16)) << 4; //'!!'用于规范化处理成一位,如果有1,!!(x >> 16)结果为1,再将结果左移四位,表示此时最高位1至少位于第16位,如果!!(x >> 16)=0那么左移运算符没影响
//我们利用bit_16的结果对x进行移位,若高16位有1,我们就右移16位,忽略低16,继续在高16里细分,若高16没有,bit_16=0,继续找低16
//换句话说就是利用bit_16的结果分别在高16和低16里继续找1
x = x >> bit_16;
//继续细分 过程同理
int bit_8 = (!!(x >> 8)) << 3;
x = x >> bit_8;
int bit_4 = (!!(x >> 4)) << 2;
x = x >> bit_4;
int bit_2 = (!!(x >> 2)) << 1;
x = x >> bit_2;
int bit_1 = (!!(x >> 1));
x = x >> bit_1;
int bit_0 = x;
return bit_16 + bit_8 + bit_4 + bit_2 + bit_1 + bit_0 + 1; //记住最低表示位数是最高位1所在位+1
}
float
/*
* float_twice - 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
*/
unsigned float_twice(unsigned uf) {
//取出sign exp frac
unsigned s = (uf >> 31) & 0x1;
unsigned exp = (uf >> 23) & 0xff;
unsigned frac = uf & 0x7fffff;
//处理NaN
if (exp == 0xff)
return uf;
//当处理非规格化数 即exp==0
if (exp == 0)
{
frac <<= 1;
return (s << 31) | (exp << 23) | frac;
}
//处理规格化数,阶码加1就能实现乘2
exp ++ ;
if (exp == 0xff) frac = 0; //注意这里要加上一个特判条件,当处理后阶码全为1时,要将frac清零,即表示为正无穷,不将尾数清零就会变为NaN,因为最大机器码就是0x7f7fffff,这个数乘2结果是正无穷
return (s << 31) | (exp << 23) | frac;
}
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
*/
unsigned float_i2f(int x) {
unsigned sign, absx, flag, frac, exp;
absx = x;
sign = (x >> 31) & 0x1;
if (sign) absx = -x; // 对所有负数进行正数处理0x80000000 == -0x80000000
frac = absx;
if (!x) return 0; //对非规格化数0特殊处理
//用这个方法来算最高位1处于什么位置太复杂了,这题可以用if while
// //先考虑高16位是否有1
// int bit_16 = (!!(t >> 16)) << 4; //'!!'用于规范化处理成一位,如果有1,!!(x >> 16)结果为1,再将结果左移四位,表示此时最高位1至少位于第16位,如果!!(x >> 16)=0那么左移运算符没影响
// //我们利用bit_16的结果对x进行移位,若高16位有1,我们就右移16位,忽略低16,继续在高16里细分,若高16没有,bit_16=0,继续找低16
// //换句话说就是利用bit_16的结果分别在高16和低16里继续找1
// t = t >> bit_16;
// //继续细分 过程同理
// int bit_8 = (!!(t >> 8)) << 3;
// t = t >> bit_8;
// int bit_4 = (!!(t >> 4)) << 2;
// t = t >> bit_4;
// int bit_2 = (!!(t >> 2)) << 1;
// t = t >> bit_2;
// int bit_1 = (!!(t >> 1));
// int cnt = bit_16 + bit_8 + bit_4 + bit_2 + bit_1;
exp = 0;
while (1)
{
if (frac & 0x80000000) break; //将最左边的1移至最高位时退出循环
frac = frac << 1;
exp ++ ;
}
frac <<= 1; //frac要再多移一位,若x的符号位原本有1,此时要先隐去这个1,frac的高位往低位数共23位即float的尾码部分
exp ++ ;
//向偶数舍入 趋向于使最低有效位为0
if ((frac & 0x01ff) > 0x0100) flag = 1;
else if((frac & 0x03ff) == 0x0300) flag = 1; //特殊情况 向上舍入使得最低有效位为0 例子 10.11100向上舍入到11.00 而10.10100向下舍入到10.10 偶数舍入倾向于使得最低有效位为0
else flag = 0;
return ((sign << 31) | ((159 - exp) << 23) | (frac >> 9)) + flag;
//在做这道题之前我没有思考过负数在IEEE浮点数下的表示,实际上对于int里的符号位,在浮点数里符号是额外表示的,浮点数的表示形式并不是整型的缩略,也就是说在浮点数表示下0x80000000并不是换种方式实现位表示,而是要把符号位单独列出来,对所有负数先做正数处理
//更简洁明了地说,就是IEEE浮点数下frac尾数部分的表示,是以unsigned型为标准的,所以先要将负数的补码形式转化为对应正数
}
float_f2i
/*
* float_f2i - 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
*/
int float_f2i(unsigned uf) {
int s, exp, frac, E;
s = (uf >> 31) & 0x1;
exp = (uf >> 23) & 0xff;
frac = uf & 0x7fffff;
//进行阶码与尾数还原
E = exp - 127; //指数值
frac = frac | (1 << 23); //加上小数点前的1
//当E<0,uf为纯小数,整型下返回0, 显然这里也考虑了非规格化数Denormalized,这里非规格化数的E=-127,值都小于1,int下均返回0
if (E < 0) return 0;
//当E<23, 23位尾数无法完全保留需要左移,反之frac要右移
if (E < 23) frac = frac >> (23 - E);
else
{
frac = frac << (E - 23);
}
//当E>=31,int下无法表示,为infinity,返回0x80000000u;
if (E >= 31) return 0x80000000u;
//浮点数里的NaN和infinity
if (exp == 0xff) return 0x80000000u;
//负数的frac改造
if (s)
frac = ~frac + 1;
return frac;
}
纪念一下也不知道做了几十个小时的datalab

原来很多以为早已掌握的知识,全在一个一个题目里被击破,很多细节其实都没有理解到,确实是这样的,自学的关键是一定要配合够量的题目练习,否则很多知识的细节你其实根本没有理解到位。
收获良多,补足了很多缺漏的知识(一定要学习完对应章节后马上开始lab练习,否则像这次一样战线拉得太长了就会导致很多知识已经忘记了),自己也利用阿里云整出了虚拟机,成就感满满吧,继续努力!

浙公网安备 33010602011771号