# 2018q3 Homework5 (bits) ###### tags: `C語言` contributed by < `asd757817` > **環境:** |作業系統|kernel|gcc version| |-|-|-| |Ubuntu 16.04 x64|4.15.0-38-generic|gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.10)| **作業要求:** 1. 只能使用: ! ˜ & ˆ | + << >> 等運算子實作函式 2. 盡可能精簡使用的運算子 3. 不能呼叫其他函式 --- ## absVal: 回傳絕對值 以 32-bit 為例,input 為 N: * 判斷 N 的正負號,做 `>>` - if N > 0, (N >> 31) = 0 (0x0) - if N < 0, (N >> 31) = -1 (0xffffffff) * `N ^ (N >> 31)`: - if N > 0, = N - if N < 0, = ~N * `abs(N) =` - if N > 0, = N - if N < 0, = ~N + 1 ```C int absVal(int x) { int y = x >> 31; return ( x ^ y ) - y ; } ``` ## addOK: 檢查兩數相加是否會發生 overflow * 32-bit 有號整數最大值為 0x7fffffff;最小值為 0x80000000。 * overflow 只出現在兩個情況: 1. 正 + 正 = 負 2. 負 + 負 = 正 Z = X + Y; 0 為正; 1 為負,truth table: ``` Z X Y | 0 | 1 ---------------- 0 0 | 1 | 0 1 1 | 0 | 1 1 0 | 1 | 1 0 1 | 1 | 1 ``` * 當 X、Y 同時與 Z 異號時表示 overflow,回傳 0,反之回傳 1。 => [(sign_X ^ sign_Z) & (sign_Y ^ sign_Z)] 上述表示式為 1 時表示 overflow,0 則反之,因此需要再做一次 `!` 運算。 ```C int addOK(int x, int y) { int z = x+y; return !(((z^x)&(z^y))>>31); } ``` ### allEvenBits: 檢查偶數位的位置數值是不是全是 1,是的話回傳 1 32 bits: 31, 30, ..... , 1, 0 ,若 0、2、4、......、30 都是 1 時回傳 1。 判斷方式: 將所有偶數位的數值做 & 運算,結果為 1 表示所有偶數位數值都是 1。 透過右移對不同的位置做運算 原 x 內各 bit 為: | 31 | 30 |... |1 |0 | | -------- | -------- | -------- | -------- | -------- | | b31 | b30 | ... |b1 | b0 | bn: 表示第 n 位的數值 執行 ```x &= x>>16``` 後,x 內第 0 ~ 15 bit 的情況: | 15 | 14 |... |1 |0 | | -------- | -------- | -------- | -------- | -------- | | b15 & b31 | b14 & b30 | ... | b1 & b17 | b0 & b16 | 再執行 ```x &= x>>8``` 後,x 內第 0 ~ 7 bit 的情況: | 7 | ... |1 |0 | | -------- | -------- | -------- | -------- | | b7 & b23 & b15 & b31 | ... | b1 & b17 & b9 & b25 | b0 & b16 & b8 & b24 | 以此類推直到第 0 位置出現 b0 & b2 & b4 & ... & b30,此時就可以把第 0 位置的數值做為回傳值回傳。 ```C int allEvenBits(int x) { x &= x>>16; x &= x>>8; x &= x>>4; x &= x>>2; return x&1; } ``` ### allOddBits: 檢查奇數位的位置數值是不是全是 1,是的話回傳 1 作法同 allEvenBits,allEvenBits 在 return 前 x 第 0、1 bit 的內容為: | 1 | 0 | | -------- | -------- | | b1 & b3 &.....& b31 | b0 & b2 &......& b30| 因此 allEvenBits 是 ```return x & 1 ``` ,奇數位的檢查則是回傳第 1 bit 的數值即可, ```return (x>>1) & 1``` 將原本 x 右移一位在 & 1 取得數值。 ```C int allOddBits(int x) { x &= x>>16; x &= x>>8; x &= x>>4; x &= x>>2; return (x >> 1) & 1; } ``` ### anyEvenBit: 檢查偶數位的位置是否有 1,有一個是 1 就回傳 1 判斷方法類似於 allEvenBit,但因為只要有一個是 1 就回傳 1,因此把原本函式的 & 改成 | ```C int anyEvenBit(int x) { x |= x >> 16; x |= x >> 8; x |= x >> 4; x |= x >> 2; return x & 1; } ``` ### anyOddBit: 檢查奇數位的位置是否有 1,有一個是 1 就回傳 1 判斷方法類似於 allOddBit,但因為只要有一個是 1 就回傳 1,因此把原本函式的 & 改成 | ```C int anyOddBit(int x) { x |= x >> 16; x |= x >> 8; x |= x >> 4; x |= x >> 2; return (x >> 1) & 1; } ``` ### bang: x 為 0 回傳 1;反之回傳 0 1. 利用 anyOddBit 檢查 32 個 bit 是否有 1 ```C x |= x >> 16; x |= x >> 8; x |= x >> 4; x |= x >> 2; x |= x >> 1; return ~(x&1); /* 若 32 個 bits 中含有 1,執行 x |= x >> 1 後,x = -1 ; 若不含 1 則 x = 0 ; 以 ! 運算使 -1→0、0→1 */ ``` 2. 利用二補數:任何非 0 的數與其二補數做 OR 得到的數值都是 -1;0 與其二補數做 OR 還是 0 ```C int bang(int x) { return (((~x+1)|x)>>31) + 1; } ``` ### bitAnd: 以 |、~ 實做 & X & Y 的 Truth table: ``` Y | 0 1 ---------------- X 0 | 0 0 1 | 0 1 ``` X OR Y 的 Truth table: ``` Y | 0 1 ---------------- X 0 | 0 1 1 | 1 1 ``` AND 運算中,只有 X=1 且 Y=1 時,數值為 1 ; OR 運算中,只有 X=0 且 Y=0 時,數值為 0 將 OR 運算的輸出做 ~ 運算得到:只有 X、Y 都是 0 時輸出為 1;輸入的 X、Y 都必須先經過一次 ~ 運算,使得只有 X、Y 都等於 1 時輸出才會得到 1。 ```C int bitAnd(int x, int y) { return ~(~x|~y); } ``` ### bitCount: 計算 32 個 bit 中有幾個是 1 ```C int bitCount(int x) { int mask_1 = 0x55555555; x = (x & mask_1) + ((x>>1) & mask_1); int mask_2 = 0x33333333; x = (x & mask_2) + ((x>>2)&mask_2); int mask_3 = 0x0f0f0f0f; x = (x & mask_3) + ((x>>4)&mask_3); int mask_4 = 0x00ff00ff; x = (x & mask_4) + ((x>>8)&mask_4); int mask_5 = 0xffff; x = (x & mask_5) + ((x>>16) & mask_5); return x; } ``` ### bitMask: 傳入 highbit、lowbit,將兩者範圍內的 bit 設為 1 回傳數值,當 lowbit > highbit 時回傳 0 想法: ``` # 以 (highbit, lowbit) = (16 , 8) 為例,目標是產生下列數值: 00000000 00000000 11111111 00000000 # 可以透過 AND 下列兩數產生 11111111 11111111 11111111 00000000 00000000 00000000 11111111 11111111 # 將 32 bit 都設為 1 11111111 11111111 11111111 11111111 # 左移 8 bit 11111111 11111111 11111111 00000000 # 希望透過右移 16 bit 產生 00000000 00000000 11111111 11111111 ``` 希望產生上的結果必須將使用 unsigned 型態來實作,使用 gcc 編譯,右移時是補 sign bit,但我們希望左移、右移都是補 0,因此使用 unsigned 。 ```C int bitMask(int highbit, int lowbit) { unsigned x = ~0U; return x & (x >> (31 +(~highbit + 1))) & (x << lowbit); } ``` ### bitMatch: 找出傳入的兩個數 bit 相同的位置 ```C int bitMatch(int x, int y) { return ~(x & ~y) & ~( ~x & y ); } ``` ### bitNor: 找出傳入的兩個數 bit 都是 0 的位置 ```C int bitNor(int x, int y) { return ~x & ~y; } ``` ### bitOr: 找出傳入的兩個數 bit 至少有一個為 1 的位置 ```C int bitOr(int x, int y) { return ~(~x & ~y); } ``` ### bitParity: 計算 parity,32 個 bit 中含有奇數個 1 回傳 1,否則回傳 0 ```C int bitParity(int x) { x ^= x >> 16; x ^= x >> 8; x ^= x >> 4; x ^= x >> 2; return (x^(x>>1)) & 1; } ``` ### bitReverse: 將 bit 反轉 (e.g.,0x80000000 → 0x00000001) ```C int bitReverse(int x) { unsigned y = x; y = ((y & 0xffff0000) >> 16) | ((y & 0x0000ffff) << 16); y = ((y & 0xff00ff00) >> 8 ) | ((y & 0x00ff00ff) << 8 ); y = ((y & 0xf0f0f0f0) >> 4 ) | ((y & 0x0f0f0f0f) << 4 ); y = ((y & 0xcccccccc) >> 2 ) | ((y & 0x33333333) << 2 ); y = ((y & 0xaaaaaaaa) >> 1 ) | ((y & 0x55555555) << 1 ); return y; } ``` ### bitXor: 將傳入的兩數做 XOR ```C int bitXor(int x, int y) { return ~(~(x & ~y) & ~(~x & y)); } ``` ### byteSwap: 將指定的 byte 位置數值交換 ```C int byteSwap(int x, int n, int m) { unsigned y = x; unsigned a = (y & (0xff << ( n << 3 ))) >> ( n << 3 ) << ( m << 3 ); unsigned b = (y & (0xff << ( m << 3 ))) >> ( m << 3 ) << ( n << 3 ); unsigned mask = ~((0xff << (n << 3)) | (0xff << (m << 3))); return (y & mask)|a|b; } ``` ### getByte: 回傳指定 byte 的數值 ```C int getByte(int x, int n) { unsigned y = x; return (y & (0xff << (n << 3))) >> (n << 3); } ``` ### conditional: 當 x != 0 時回傳 y,否則回傳 z ```C int conditional(int x, int y, int z) { return ((0xffffffff + !x)&y) | ((~!x + 1)&z); } ``` ### copyLSB: 將所有 bit 設為傳入數值的 least significant bit ```C int copyLSB(int x) { return !(x & 1) + 0xffffffff; } ``` ### distinctNegation: 若 x != -x 回傳 1 否則回傳 0 實際上只有 0x0 跟 0x80000000 會回傳 0,其他都會回傳 1。 ```C int distinctNegation(int x) { return !!(x ^ (~x + 1)); } ``` ### dividePower2: 計算 $x / 2^n$ ```C int dividePower2(int x, int n) { unsigned mask = ~(0xffffffff << n); return (x >> n) + ((x >> 31)&(!!(x & mask))); } ``` ### evenBits: 將偶數位的 bit 都設為 1 ```C int evenBits(void) { return 0x55555555; } ``` ### ezThreeFourths:將傳入的數乘以四分之三(無條件捨去),計算結果應與 x\*3/4 一致 ```C int ezThreeFourths(int x) { x = x + x + x; return (x >> 2) + ((x >> 31)&(!!(x & 0x3))); } ``` ### fitsBits: 傳入 x,n 判斷 x 是否能以 n bit 的有號數系統表示,可以回傳 1 反之回傳 0 想法: - 以 數字 5 為例,最少要 4 bit 才能表示,4 bit 可表示的範圍: -8 ~ 7 ``` # 32 bit 中 5 的表示如下 xxxxxxxx xxxxxxxx xxxxxxxx xxxx0101 # xxxx..... 表示 5 可以安全左移(不發生 overflow)的空間 ``` - n >= 4,表示可以安全左移的空間(32 - n)應小於 n 可安全左移的空間,藉由將 x 左移 32 - n 位,檢查是否有發生 overflow 即可判斷 n 是否 >= 4。 - overflow 判斷方式: 左移後在右移判斷數值是否相等 ``` # 以 x = 2 , n = 2 檢驗 00000000 00000000 00000000 00000010 # 左移 32 - n = 30 bits 10000000 00000000 00000000 00000000 # 再右移 30 bits 11111111 11111111 11111111 11111111 # 數值改變,表示有發生 overflow,因此可以 n bit 表示 x ``` ```C int fitsBits(int x, int n) { int shift_bit = 32 + ~n + 1; return !(x ^ ((x << shift_bit) >> shift_bit)); } ``` ### fitsShort: 檢查 x 是否可以用 16 bits 的二補數系統表示,若可以表示回傳 1,反之回傳 0 ``` int fitsShort(int x) { return !(x^((x << 16)>>16)); } ```