- 編寫過程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;
}