- 编写过程is_little_endian,当在小端法机器上编译和运行时返回1,在大端法机器上编译运行则返回0。
int is_little_endian()
{
int i = 0;
return *(char*)&i; //整型指针转换为字节指针,每次指向一个字节
}
- 编写一个C表达式,它生成一个字,由x的最低有效字节和y中剩下的字节组成,对于运算数x=0x89ABCDEF和y=0x76543210,就得到0x7654321EF
int main()
{
unsigned x = 0x89ABCDEF;
unsigned y = 0x76543210;
unsigned num = (x & 0xFF) | (y & ~0xFF);
printf("%X", num);
}
- 假设我们将一个w位的字中的字节从0(最低位)到w/8-1(最高位)编号。写出下面C函数的代码,它会返回一个无符号值,其中参数x的字节i被替换成字节b
如:replace_byte(0x12345678,2,0xAB)->0x12AB5678
unsigned replace_byte(unsigned x, int i, unsigned char b)
{
char* x_char = (char*)&x;
*(x_char + i) = b;
return x;
}
- 如图:
printf("%d", !~x || !x || !~(x | 0x00ffffff) || !(x & 0x000000ff));
- 编写一个函数int_shifts_are_arithmetic(),在对int类型的数使用算数右移的机器上运行时这个函数生成1,而其他情况生成0。
int int_shifts_are_arithmetic()
{
int i = -1;
int i1 = i >> 4;
char* i1_one = (char*)&i1;
return *i1_one & 1;
}
- 如图:
//算术右移实现逻辑右移
unsigned sr1(unsigned x, int k)
{
unsigned xsra = (int)x >> k;
unsigned cmp = ~(-1 << (32 - k));
return xsra & cmp;
}
//逻辑右移实现算术右移
int sra(int x, int k)
{
unsigned xsra = (unsigned)x >> k;
unsigned cmp = (-1 << (32 - k));
return xsra | cmp;
}
- 写出代码实现以下函数:
/*
Return 1 when any odd bit of x equals 1;0 otherwise.
Assume w=32
*/
int any_odd_one(unsigned x)
{
return !~(x | 0xAAAAAAAA);
}
- 写出代码实现如下函数:
思路:
异或运算^
现在将这堆数(偶数个)分成两部分,前一半和后一半的数一 一对应进行异或运算,得到的结果再分成更少的两部分,重复上一个步骤,直到只剩一个数,就是答案了。
例如:1101,分成11和01两部分,11异或01=10,10再分成1和0,1异或0=1。得到结果
/*
Return 1 when x contains an odd number of 1;0 oherwise.
判断二进制中1的个数是否为奇数
Assume w=32
*/
int odd_ones(unsigned x)
{
x ^= x>>16;
x ^= x>>8;
x ^= x>>4;
x ^= x>>2;
x ^= x>>1;
return x&1; //最终最低有效位即答案
}
- 如图:
A.一次左移位数应该小于计算机位数
B.一次左移分两次
C.一次左移分三次 - 写出具有如下原型函数的代码
/*
Mask with least signficant n bits set to 1
Examples: n=6-->0x3F
*/
int lower_one_mask(int n)
{
return ~(-1<<n);
}
- 写出具有如下原型函数的代码
/*
Do rotating left shift
Examples when x = 0x12345678
n=4 ->0x23456781 ,n =20->0x67812345
*/
unsigned rotate_left(unsigned x,int n)
{
return x<<n | x>>32-n;
}
- 如图:
A.前任的代码会扩展成无符号数,而不是有符号数
B
int xbyte(packed_t word,int bytenum)
{
return ((int)(word<<(24 - bytenum<<3)))>>24;
}
- 如图:
A.无符号数参与运算得到的结果是无符号数,永远比0大
B.强制转换成int - 写出具有如下原型的函数代码,同正常的补码加法溢出的方式不同,当正溢出时,饱和加法返回TMax,负溢出时,返回TMin。饱和运算常常用在执行数字信号的处理的程序中。
int saturating_add(int x, int y)
{
//判断是否溢出,溢出返回0xFFFFFFFF,不溢出返回0
int result = (x ^ (x + y) & y ^ (x + y)) >> 31;
//判断是否为正溢出
int neg_result = (result << 31 & (x + y)) >> 31;
//判断是否为负溢出
int pos_result = (result << 31 & ~(x + y)) >> 31;
//用位代替条件判断语句的方法就是用若干个|,同一时间只有一项不为0,一般都是&上一个值为0x0或0xFFFFFFFF的值作为判断条件
//用于若干个条件互斥,只有一个不为0
//&~result是保证溢出的话这一项为0
return (x + y) & ~result | 0xEFFFFFFF & neg_result | 0x80000000 & pos_result;
// 不溢出返回x+y 正溢出返回TMax 负溢出返回TMin
}
- 写出具有如下原型的函数的代码:
//如果计算x-y不溢出,这个函数就返回1
int tsub_ok(int x,int y)
{
//判断是否溢出
int result = (unsigned)(x^(x-y) & ~y^(x-y))>>31;
return result;
}
- 如图
/*
这个问题需要一步一步的进行推导
T2Uw(x)我们把这种写法称为补码转无符号数,那么很容易得出:
(2^w表示2的w次方,为什么当x<0时是这个结果呢,
其实,补码的负数就是把原来w-1之后的位的结果减去了最高一位的值,最高位的值就是2^w)
if x < 0 => x + 2^w
if x > 0 => x
上边的公式很简单,但在使用的时候还要做判断,显然很不科学,我们可以认为T2Uw(x)是一个函数
接下来就想办法推导出一个表达式来
这里省略了一系列的推导过程,得出了这样一个结果"
T2Uw(X)= X + X(w-1)2^w
大家看看这个式子跟上边的那个作用一样,x的w-1位就是他的最高位,如果该位的值是1,那么就相当于
x<0的情况,否则就是另一种情况
我们假设x`表示x的无符号值
X` = X + X(w-1)2^w
我们假设y`表示x的无符号值
Y` = Y + Y(w-1)2^w
那么X` * Y` = (X + X(w-1)2^w) * (Y + Y(w-1)2^w)
如果要把这个计算式展开会很麻烦,我们可以进一步抽象
设a = X(w-1)2^w, b= Y(w-1)2^w
则: X` * Y` = X*Y + X*b + Y*a + a*b
我们假定有这样一个函数,他的功能是取出无符号数的最高位uh(),因此上边的式子变形为:
uh(X` * Y`) = uh(X*Y + X*b + Y*a + a*b)
= uh(X*Y) + uh(X*b) + uh(Y*a) + uh(a*b)
那么X * b 也就是X*b= X*Y(w-1)2^w 他的最高位的值就是X*Y(w-1)2^w / 2^w => X*Y(w-1)
那么Y * a 也就是Y*a= Y*X(w-1)2^w 他的最高位的值就是Y*X(w-1)2^w / 2^w => Y*X(w-1)
那么a * b 也就是a*b= X(w-1)2^w * Y(w-1)2^w 他 / 2^w => 0
===> uh(X` * Y`) = uh(X*Y) + X*Y(w-1) + Y*X(w-1)
上边推理的核心思想就是 无符号X`的补码表示:X + X(w-1)2^w 求高位的/ 2^w 操作
*/
unsigned unsigned_high_prod(unsigned x,unsigned y)
{
int sx = (int)x;
int sy = (int)y;
return signed_high_prod(sx,sy)+x<<31*y+y<<31*x;
}
- 如图:
void *calloc(size_t nmemb,size_t size)
{
/*
乘法和无符号加减法用如下方式判断是否溢出
all_size/nmemb!=size
有符号加减法判断最高位是否符合条件
(x+y)& x | (x+y) & y
*/
if(nmemb==0||size==0)
return NULL;
size_t all_size = nmemb*size;
//处理溢出
if(all_size/nmemb!=size)
return NULL;
void* p = malloc(all_size);
memset(p,0,all_size);
return p;
}
- 如图:
A:x<<4+x
B:x-x<<3
C:x<<6-x<<2
D:x<<4-x<<7
- 写出具有如下原型的函数的代码:该函数要用正确的舍入方式计算x/2k
/*
c语言标准规定向0舍入
在本函数中用右移代替,这样做会导致向下舍入,即需要调整x为负的情况
*/
int divide_power2(int x,int k)
{
int sum = x>>k;
//条件为x<0 且 x的前k位不为0
sum += x>>31 && (x&~(-1<<k));
return sum;
}
- 写出函数mul3div4的代码,对于整数参数x,计算3x/4,但是要遵循位级整数编码规则。你的代码计算3x也会产生溢出
//不用处理溢出,要处理为x负的舍入
int mul3div4(int x)
{
//将负号保留
int negflag = x >> 31;
//先算乘法
int x1 = (x << 1) + x;
//再算除法
int x2 = x1 >> 2;
//处理舍入
x2 += negflag && (x1 & ~(-1 << 2));
return x2;
}
- 写出函数threefourths的代码,对于整数参数x,计算3/4x的值,向零舍入。它不会溢出。函数应该遵循位级编码规则。
/*
大体方向是先右移在左移
但是这样会导致舍入判断丢失
所以应该先将做出舍入的判断
*/
int threefourths(int x)
{
int negflag = x << 31;
int x1 = (x << 1) + x;
int isneg = negflag && (x1 & ~(-1 << 2));
int x2 = x >> 2;
int x3 = (x2 << 1) + x2;
x3 += isneg;
return x3;
}
- 如图:
A.1w-k0k
B.0w-k-j1k0j
//Assume w = 32
int A(unsigned k)
{
return -1<<k;
}
int B(unsigned k,unsigned j)
{
return (-1<<j) & ~(-1<<(k+j));
}
- 如图:
A. 不总为1:
我们要知道两个正负都一样的数
一个是0,是TMin
所以当x=0,y=TMin时
(x<y)==(-x>-y)不成立
B.总为1
C.总为1
首先要知道~x+x=-1总是成立的
~x+~y+1 = -x-1-y-1+1=-x-y-1
~(x+y) = -x-y-1 == ~x+~y+1
D.总为1
注意无符号数和有符号数加减乘除的位级等效性
E.总为1
向右移再左移可能导致位的丢失
所以小于等于1是正确的
-
如图:
A. 等比数列求和a1*(1-q^n)/(1-q) a1=Y/(2^k) q=1/(2^k) 假设n非常大,1-q^n可看作1 a1/(1-q) 所以通项公式是Y/(2^k-1) B. y=101 5/7 y=0110 2/5 y=010011 19/63
-
如图:
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 || (sx&sy)&&(ux>=uy) || (~sx&~sy)&&(ux<=uy)
// x为负y为正 x,y都为负 x,y都为正
}
- 如图:
知识点:
(1)浮点数的一般表示
(2)浮点数的位编码
A:数7.0->111.000
尾数M为1.11,小数f为0.11
阶码E为2,bias为2k-1-1,e为E+bias = 2k-1+1
位表示:0|100…001|11000…
B:
尾数M为1.111…1,小数f为0.11…1
阶码E为n(前提是k足够大),e = E+bias = 2k-1-1+n
位表示:0|e|11111…111
C:
最小的规格化数的E为2-2k-1,其倒数:
尾数M为1.000…000,小数f为0.000…00
阶码E为2k-1-2,e = E+bias = 2k-1-k = 2k-3
位表示:0|e|00…00000 - 与Inter兼容的处理器也支持“扩展精度”浮点形式,这种格式具有80位字长,被分成1个符号位、k=15个阶码位、1个单独的整数位和n=63个小数位。整数位是IEEE浮点表示中隐含位的显式副本。也就是说,对于规格化的值它等于1,对于非规格化的值它等于0。填写下表:
描述 | 二进制 | 值 |
---|---|---|
最小的正非规格化数 | 0-0…0-0-0…001 | 2-61-214 |
最小的正规格化数 | 0-0…01-1-0…00 | 22-214 |
最大的规格化数 | 0-11…10-1–11…1 | (2 - 2(-63)) * 2(214 - 1) |
- 如图:
描述 | Hex | M | E | V | D |
---|---|---|---|---|---|
-0 | 0x8000 | 0 | -14 | -0 | -0.1 |
最小的>2的值 | 0x4001 | 1025/1024 | 1 | 1025*2-9 | 1025.001953 |
512 | 0x6000 | 0 | 24 | 512 | 512 |
最大的非规格化数 | 0x03FF | 1023/1024 | -14 | 1023*2-10 | 0.999023 |
-∞ | 0xFC00 | —— | —— | -∞ | -∞ |
十六进制表示为3BB0 | 3BB0 | 123/64 | -1 | 123 * 2-7 | 0.960938 |
非规格化数的E为1-bias
D是用printf的规范%f打印,默认为6位
- 考虑下面两个基于IEEE浮点格式的9位浮点表示
(1)格式A
有1个符号位,5个阶码位,3个小数位,bias为15
(2)格式B
有1个符号位,4个阶码位,4个小数位,bias为7
将下面表格A->B,如果要舍入,向+∞舍入。
位 | 值 | 位 | 值 |
---|---|---|---|
0 10110 101 | 208 | 0 1110 1010 | 208 |
1 00111 110 | -7/210 | 1 0000 0111 | -7/2-10 |
0 00000 101 | 5/217 | 0 0000 0000 | 0 |
1 11011 000 | -4096 | 1 1111 0000 | -∞ |
0 11000 100 | 768 | 0 1111 0000 | +∞ |
第二行中,要住转换的B格式规格化最小能表示-2-6,所以要将A格式转成格式化的B
第三行中,要转换的B格式最小能表示1/210,故向零舍入
第四行中,B格式最大可表示416远远小于4096,向-∞舍入
第五行中,416也小于768,向+∞舍入
- 如图:
补充知识点:
float符号、阶数、尾数分别为1,8,23
double符号、阶数、尾数分别为1,11,52
A:int->float会引起舍入,double->int会引起溢出+∞,但是这个double是int转换的,所以表达式是成立的
B:左边是double加减法,右边是int加减法,假如右边溢出而左边没有溢出则等式不成立
如:x = INT_MAX,y = -1,int会溢出
C:虽然说浮点数的加减乘除不满足结合律
但此时的double是由int转换而来,数值较小,不会舍去,故等式成立
D:乘法会导致溢出,不成立
int x = 0xEFFFFFFF;
int y = 0xEFFFFFFF-1;
int z = 0xEFFFFFFF-2;
得到的结果不一样
E:不成立
dx = 1,dz = 0
dx/dx = 1,dz/dz=NaN
- 如图:
float fpwr2(int x)
{
unsigned exp,frac;
unsigned u;
if(x<-149) //Too small Return 0.0
{
exp = 0;
frac = 0;
}
else if(x<-126) //非规格化结果
{
exp = 0;
frac = 1<<(x+149);
}
else if(x<128) //规格化结果
{
exp = 127 + x;
frac = 0;
}
else
{
exp = 0xFF;
frac = 0;
}
u = exp<<23 | frac;
return u2f(u);
}
-
如图:
A:表示的二进制小数为:3.141593 B:是11.001001001...... C:第一个近似值是11.0010010000111...,第9位开始
-
如图:
float_bits float_negate(float_bits f){
unsigned sign = f>>31;
unsigned exp = f>>23 & 0xFF;
unsigned frac = f & 0x7FFFFF;
if(exp==0xFF&&frac!=0)
return (sign<<31) | (exp<<23) | frac;
return (~sign<<31) | (exp<<23) | frac;
}
- 如图:
float_bits float_negate(float_bits f){
unsigned sign = f>>31;
unsigned exp = f>>23 & 0xFF;
unsigned frac = f & 0x7FFFFF;
if(exp==0xFF&&frac!=0)
return (sign<<31) | (exp<<23) | frac;
return (0<<31) | (exp<<23) | frac;
}
- 如图:
float_bits float_twice(float_bits f){
unsigned sign = f>>31;
unsigned exp = f>>23 & 0xFF;
unsigned frac = f & 0x7FFFFF;
if(exp==0xFF&&frac!=0)
return (sign<<31) | (exp<<23) | frac;
//如果是非规格化数
if(exp == 0)
{
/*这里分为两种情况;
第一种:左移后没有超过1,此时没有任何问题
第二种:左移后超过1了,此时要将浮点数规格化,那么这里为什么直接左移就行了呢?
首先,左移之后会将frac的最高有效位进位到exp的最低有效位,这时发现规格化后和
非规格化的E都是等于1-bias,这就是为什么非规格化的E要设置成1-bias而不是-bias,
它实现了非规格化到规格化的平滑过渡
然后,frac部分代表的就不是0.小数位,而是1.小数位了,所以最终结果是正确的
*/
frac<<1;
}
//如果是非规格化数
if(exp!=0 && exp!=0xFF)
{
//当浮点数没有达到最大的时候
exp += 1;
//如果加了后达到了饱和
if(exp>=0xFF)
frac = 0; // 将浮点数置为+∞
}
return (sign<<31) | (exp<<23) | frac;
}
- 上题的2改为0.5
//整数补码除法要注意的是C语言规定向0舍入,而右移后是向下舍入,x为负数的时候需要注意是否要加1
//浮点数除法使用偶数舍入,
/*这里我们考虑舍入,比如 f = 0 000…001 XXX…XYZ 进行右移变为 0 000…000 1XX…XXY(Z),题目要求偶
数舍入,需要看 Z 位,如果 Z 是 1,则需要舍入。偶数舍入要使 Y 位(最低有效位)为 0,如果 Y 是 1,
那么就 f 就要加 1;如果 Y 是 0,直接舍掉 Z。
*/
float_bits float_half(float_bits f){
unsigned sign = f>>31;
unsigned exp = f>>23 & 0xFF;
unsigned frac = f & 0x7FFFFF;
if(exp==0xFF&&frac!=0)
return (sign<<31) | (exp<<23) | frac;
if(exp==0) //非规格化右移
{
int isNeedRounding = frac && 0x01;
//需要舍入的情况
if(isNeedRounding)
{
frac = frac>>1;
int isNeedPlus = -(frac & 0x01);
frac = (~isNeedPlus)&frac | isNeedPlus & (frac+1);
}
else
frac = frac>>1;
}
if(exp!=0&&exp!=0xFF)
{
exp -= 1;
if(!exp)
{
frac = frac>>1;
int isNeedPlus = -(frac & 0x01);
frac = (~isNeedPlus)&frac | isNeedPlus & (frac+1);
frac = frac & 0x100000;
}
}
return (sign<<31) | (exp<<23) | frac;
}
- 实现将float转换成int的函数
/*
一个整数的正常表示形式应为XXXX
如果小于0,就是0.XXXXX
这时直接将后面的一串值放入frac
如果大于0,我们先将其转换为二进制形式1.XXXX*2^E,XXXX的长度为E
首先将E+bias=e存入exp
然后将前面的1丢掉(因为用了规格化),将后面的XXX存入frac:
如果可以放入就放入后后面补0
如果放不下就只放23位
这题是浮点数转整数
所以首先算出E = exp -bias
然后取出frac对应E位的数,前面加上去掉的1
*/
int float_f2i(float_bits f) {
unsigned sign = f >> 31;
unsigned exp = f >> 23 & 0xFF;
unsigned frac = f & 0x7FFFFF;
if (exp == 0xFF && frac != 0)
//dosomething'
if (exp == 0)
return 0;
unsigned bias = 127;
unsigned E = exp - 127;
int i = 0;
//如果XXXX小于23
if(E<=23)
i = frac >> (23-E) | 1 << E;
//如果XXXX大于23
else
i = frac << (E-23) | 1 << E;
if (sign)
{
i = -i;
}
if ((exp == 0xFF && frac != 0) | i>0xEFFFFFFF | i<0x80000000)
return 0x80000000;
return i;
}
- 实现将int转换成float的函数
/*
一个整数的正常表示形式应为XXXX
如果小于0,就是0.XXXXX
这时直接将后面的一串值放入frac
如果大于0,我们先将其转换为二进制形式1.XXXX*2^E,XXXX的长度为E
首先将E+bias=e存入exp
然后将前面的1丢掉(因为用了规格化),将后面的XXX存入frac:
如果可以放入就放入后后面补0
如果放不下就只放23位
*/
float_bits float_i2f(int i) {
unsigned sign = 0;
unsigned exp = 0;
unsigned frac = 0;
unsigned E = 0;
unsigned bias = 127;
int i_copy = i;
if(i<0)
{
sign = 1;
i = -i;
}
while(true)
{
i_copy = i_copy>>1;
if(!i_copy)
break;
E += 1;
}
E-=1;
exp = E + bias;
//去1
frac = i & (~(1<<E));
if(E<=23)
frac = frac << 23 - E;
else
frac = frac >> E-23;
return (sign<<31) | (exp<<23) | frac;
}