# 2018q3 Homework5 (bits) contributed by < [`ofAlpaca`](https://github.com/ofAlpaca) > ###### tags: `CSIE5006` `HW` ## 實驗環境 ``` $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian CPU(s): 8 On-line CPU(s) list: 0-7 Thread(s) per core: 2 Core(s) per socket: 4 Socket(s): 1 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 60 Model name: Intel(R) Xeon(R) CPU E3-1231 v3 @ 3.40GHz Stepping: 3 CPU MHz: 3548.766 CPU max MHz: 3800.0000 CPU min MHz: 800.0000 BogoMIPS: 6799.62 Virtualization: VT-x L1d cache: 32K L1i cache: 32K L2 cache: 256K L3 cache: 8192K NUMA node0 CPU(s): 0-7 ``` --- ## 無法 make 使用 make 時發現會產生錯誤 : ``` $ make Git commit hooks are installed successfully. gcc -O0 -g -Wall -Werror -m32 -lm -o btest bits.c btest.c decl.c tests.c In file included from btest.c:17:0: /usr/include/stdio.h:27:10: fatal error: bits/libc-header-start.h: 沒有此一檔案或目錄 #include <bits/libc-header-start.h> ^~~~~~~~~~~~~~~~~~~~~~~~~~ compilation terminated. In file included from decl.c:1:0: /usr/include/stdio.h:27:10: fatal error: bits/libc-header-start.h: 沒有此一檔案或目錄 #include <bits/libc-header-start.h> ^~~~~~~~~~~~~~~~~~~~~~~~~~ compilation terminated. In file included from /usr/lib/gcc/x86_64-linux-gnu/7/include-fixed/limits.h:194:0, from /usr/lib/gcc/x86_64-linux-gnu/7/include-fixed/syslimits.h:7, from /usr/lib/gcc/x86_64-linux-gnu/7/include-fixed/limits.h:34, from tests.c:3: /usr/include/limits.h:26:10: fatal error: bits/libc-header-start.h: 沒有此一檔案或目錄 #include <bits/libc-header-start.h> ^~~~~~~~~~~~~~~~~~~~~~~~~~ compilation terminated. Makefile:13: recipe for target 'btest' failed make: *** [btest] Error 1 ``` 之後找到 [解答](https://stackoverflow.com/questions/4643197/missing-include-bits-cconfig-h-when-cross-compiling-64-bit-program-on-32-bit) ,下指令 `$ sudo apt install gcc-multilib` 安裝完 gcc-multilib 後就可以 make 。 --- ## bits.c - integer ### absVal * (5+1)/10 operation * 想法 : * 改良自小考,因為無法使用 `-` 所以改成作兩次 `!` 來得到 `1` ,若 `y` 為 0 則不影響。 * 但是使用 shift left 31 會在 commit 時讓 cppcheck 跳出 Undefined behavior 的 error ,這裡有兩個解決方法,一個是改成 `x >> 30 >> 1` 或是轉型為 unsigned ,但是根據 [DataLab](https://github.com/sysprog21/datalab/blob/master/bits.c#L47) 的要求,是不能使用任何 casting 的,故改採前者解決。在此假設 implicit conversion 也不允許。 * git commit 時會產生 `(error) Shifting signed 32-bit value by 31 bits is undefined behaviour` 的錯誤訊息 : ```clike= int absVal(int x) { int y = x >> 31; return (x^y) + !!y; } ``` * 修改後 : (多增加一個 operation:cry:) ```clike= int absVal(int x) { int y = x >> 30 >> 1; return (x^y) + !!y; } ``` ### addOK * (6+1)/20 operation * 想法: * Overflow 只會發生於**負加負**或是**正加正**,正加負不可能溢位,所以主要檢查負加負之後是否為負,正加正之後是否為正,若不是則溢位。 * `x^z` 與 `y^z` 用於判斷 `x` 和 `y` 是否與相加結果 `z` 的 sign bit 一樣,一樣則為 0 ,否則為 1 。 * 若兩者與 `z` 的 sign bit 都不一樣,那 `&` 的結果會是 1 ,表示溢位。 * 若兩者與 `z` 的 sign bit 都一樣或只有一個不一樣,那 `&` 的結果會是 0 ,表示正常。 * 最後 shift right 31 取 sign bit 並使用 logical NOT ( **!** ) 得到 0 或 1 。 以下只看 sign bit : | & | y^z : 0 | y^z : 1 | | -------- | -------- | -------- | | x^z : 0 | 0 | 0 | | x^z : 1 | 0 | 1 | * git commit 時會產生 `(error) Shifting signed 32-bit value by 31 bits is undefined behaviour` 的錯誤訊息 : ```clike= int addOK(int x, int y) { int z = x + y; return !(((x ^ z) & (y ^ z)) >> 31) ; } ``` * 修改後 : (多增加一個 operation:cry:) ```clike= int addOK(int x, int y) { int z = x + y; return !(((x ^ z) & (y ^ z)) >> 30 >> 1) ; } ``` ### allEvenBits * 9/12 operation * 想法 : * 使用 `&` 可以確保所有偶位元都為 1 。 * 只要不作 `x &= x >> 1` ,那奇偶位元的結果就不會合在一起。 * 最後再取 LSB 即可。 ```clike= int allEvenBits(int x) { x &= x >> 16; x &= x >> 8; x &= x >> 4; x &= x >> 2; return x & 0x1; } ``` ### allOddBits * 10/12 operation * 想法 : * 道理與 `allEvenBits` 相同,只是多了 shift right 1 。 ```clike= int allOddBits(int x) { x &= x >> 16; x &= x >> 8; x &= x >> 4; x &= x >> 2; return (x >> 1) & 0x1; } ``` ### anyEvenBits * 9/12 operation ```clike= int anyEvenBit(int x) { x |= x >> 16; x |= x >> 8; x |= x >> 4; x |= x >> 2; return x & 0x1; } ``` ### anyOddBits * 10/12 operation ```clike= int anyOddBit(int x) { x |= x >> 16; x |= x >> 8; x |= x >> 4; x |= x >> 2; return (x >> 1) & 0x1; } ``` ### bang * (5+1)/12 operation * 想法 : * 除了 0 以外,其他數值的二補數都是正負號相反的,因此當某數值與其二補數作 `OR` 後的 sign bit 必定會是 1 。 * 若 sign bit 為 1 , shift right 31 後會是 -1 ,故再加 1 使其結果為 0 ; 若 sign bit 為 0 , shift right 31 後會是 0 ,再加 1 使其結果為 1 。 * git commit 時會產生 `(error) Shifting signed 32-bit value by 31 bits is undefined behaviour` 的錯誤訊息 : ```clike= int bang(int x) { return ((x | (~x + 1)) >> 31) + 1; } ``` * 修改後 : (多增加一個 operation:cry:) ```clike= int bang(int x) { return ((x | (~x + 1)) >> 30 >> 1) + 1; } ``` ### bitAnd * 4/8 operation * 想法 : * 使用 [De Morgan's laws](https://en.wikipedia.org/wiki/De_Morgan%27s_laws) : `x & y = ~~(x & y) = ~(~x | ~y)` 。 ```clike= int bitAnd(int x, int y) { return ~(~x | ~y); } ``` ### bitCount * 33/40 operation * 想法 : * 將所有 bit 相加後就可計算總共有幾個 1-bit ,但是如果一次一次位移來相加,絕對會超過 40 operation ,所以改用類似 SIMD 的方式去相加。 * 把相鄰的 bit 相加後,將原本所在的那兩個 bit 的位置當成是暫存,儲存相加後的結果。 2-bits 足夠表示 bit 與 bit 相加的結果,不用擔心溢位。 * 接下來都如法炮製,相鄰 2-bits 相加,並把結果儲存到這 4-bits 的位置。 * 一直做到剩下兩個 16-bits 為止,再把兩者相加就是總 bit 數了。 ``` x -> 00 01 00 11 00 00 01 10 00 01 11 10 11 10 10 01 ----------------------------------------------- 2-bits tmp -> 00|01|00|10|00|00|01|01|00|01|10|01|10|01|01|01 ----------------------------------------------- 4-bits tmp -> 00 01|00 10|00 00|00 10|00 01|00 11|00 11|00 10 ----------------------------------------------- 8-bits tmp -> 00 00 00 11|00 00 00 10|00 00 01 00|00 00 01 01 ----------------------------------------------- 16-bits tmp -> 00 00 00 00 00 00 01 01|00 00 00 00 00 00 10 01 ``` ```clike= int bitCount(int x) { int mask_01 = 0x55; int mask_0011 = 0x33; int mask_00001111 = 0x0F; int mask_FF = 0xFF; // mask 0x55555555 mask_01 = mask_01|mask_01 << 8; mask_01 = mask_01|mask_01 << 16; // mask 0x33333333 mask_0011 = mask_0011|mask_0011 << 8; mask_0011 = mask_0011|mask_0011 << 16; // mask 0x0F0F0F0F mask_00001111 = mask_00001111|mask_00001111 << 8; mask_00001111 = mask_00001111|mask_00001111 << 16; // mask 0x00FF00FF mask_FF = mask_FF|mask_FF << 16; x = (x & mask_01) + ((x >> 1) & mask_01); x = (x & mask_0011) + ((x >> 2) & mask_0011); x = (x & mask_00001111) + ((x >> 4) & mask_00001111); x = (x & mask_FF) + ((x >> 8) & mask_FF); return (x + (x >> 16)) & 0xFF; } ``` ### bitMask * 7/16 operation * 想法 : * highbit 是從 LSB 開始數到第 highbit 的位置。 * lowbit 是從 LSB 開始數第 lowbit 的位置。 * 圖解如下 : ``` bitMask(highbit = 5, lowbit = 3) : mask = 0x38 = 0 0 1 1 1 0 0 0 [7][6][5][4][3][2][1][0] ``` * 將 0xFFFFFFFF 作 shift left ( highbit + 1) 後取一補數,可得到 highbit 的 mask 。 * 將 0xFFFFFFFF 作 shift left lowbit 即可得到 lowbit 的 mask 。 * 最後將兩者作 `&` 即可找到對應的 mask 。 * 圖解如下 : (以 8-bits 為例,highbit = 5, lowbit = 3) ``` 0xFF -> shiftleft (5+1) -> 1100 0000 -> ones' complement -> 0011 1111 0xFF -> shiftleft 3 -> 1111 1000 --------------------------------------------------------------------- 0011 1111 & 1111 1000 ------------- 0011 1000 = 0x38 ``` * 一開始的版本 : ```clike= int bitMask(int highbit, int lowbit) { int full_one = ~0x00; // get 0xFFFFFFFF int high_mask = full_one << (highbit + 1); int low_mask = full_one << lowbit; return ~high_mask & low_mask; } ``` 然而,**以上的程式碼是有問題的**,當 highbit = 31 , lowbit = 0 時,照理來說答案應該要是 0xFFFFFFFF ,但結果卻是 0x00 。原因是 ==Undefined behavior== ,根據 C99 的描述,如果 shift left 後的值是無法表示的,則為 UB ,以上程式碼會讓 `full_one` 作 shift left 32 時產生 UB 因而造成非預期結果。 > [C99 6.5.7] The result of E1 << E2 is E1 left-shifted E2 bit positions; acated bits are filled with zeros. If E1 has an unsigned type, the alue of the result is E1 × 2^E2^ , reduced modulo one more than the aximum value representable in the result type. If E1 has a signed type and nonnegative value, and E1 × 2^E2^ is representable in the result type, then that is the resulting value; otherwise, **the behavior is undefined**. * 換個寫法就不會造成 UB 了 : ```clike= int bitMask(int highbit, int lowbit) { int full_one = ~0x00; // get 0xFFFFFFFF int high_mask = (full_one << highbit) << 1 ; int low_mask = full_one << lowbit; return ~high_mask & low_mask; } ``` ### bitMatch * 7/14 operation * 想法 : * 受限於題目只能用 `~` 和 `&` ,所以要想辦法取代 `^` 。 * 可以使用下圖寫出等價於 `XOR` 的電路 : ![](https://i.imgur.com/MfJrBoM.png) * `~(~(~a & b) & ~(a & ~b))` 由於此函式要的結果與 `XOR` 相反,所以去掉一個 `~` 。 ```clike= int bitMatch(int x, int y) { return ~(~x & y) & ~(x & ~y); } ``` ### bitNor 、 bitOr * bitNot : 3/8 operation * bitOr : 4/8 operation * 想法 : * 一樣使用 De Morgan’s laws 就好 ```clike= int bitNor(int x, int y) { return ~x & ~y; } int bitOr(int x, int y) { return ~(~x & ~y); } ``` ### bitParity * 11/20 operation * 想法 : * 透過一連串的 shift right 與 `XOR` 計算 1 有偶數還是奇數個。 * 下圖以 8-bits 來舉例 : ``` 0111 0011 XOR 0000 0111 (>> 4) --------------------- 0111 0100 --> 0111 0100 XOR 0001 0001 (>> 2) ---------------------- 0110 0101 --> 0110 0101 XOR 0011 0010 (>> 1) ---------------------- 0101 0111 --> LSB 為 1 ``` ```clike= int bitParity(int x) { x ^= x >> 16; x ^= x >> 8; x ^= x >> 4; x ^= x >> 2; x ^= x >> 1; return x & 0x1; } ``` ### bitReverse * 40/45 operation * 想法 : * 要將數值的 bit 位元排列逆轉過來,其實有個簡單的方法,那就是兩兩互相交換。 * 說的準確點是先相鄰 bit 互換,再相鄰 2-bits 互換,再相鄰 4-bits 互換,持續換到 16-bits ,這樣換完排列順序就會逆轉。 ```clike= int bitReverse(int x) { int mask_01 = 0x55; int mask_0011 = 0x33; int mask_00001111 = 0x0F; int mask_FF = 0xFF; int mask_FFFF = mask_FF | mask_FF << 8; // mask 0x55555555 mask_01 = mask_01 | mask_01 << 8; mask_01 = mask_01 | mask_01 << 16; // mask 0x33333333 mask_0011 = mask_0011 | mask_0011 << 8; mask_0011 = mask_0011 | mask_0011 << 16; // mask 0x0F0F0F0F mask_00001111 = mask_00001111 | mask_00001111 << 8; mask_00001111 = mask_00001111 | mask_00001111 << 16; // mask 0x00FF00FF mask_FF = mask_FF | mask_FF << 16; x = ((x & mask_01) << 1) | ((x >> 1) & mask_01); x = ((x & mask_0011) << 2) | ((x >> 2) & mask_0011); x = ((x & mask_00001111) << 4) | ((x >> 4) & mask_00001111); x = ((x & mask_FF) << 8) | ((x >> 8) & mask_FF); x = ((x & mask_FFFF) << 16) | ((x >> 16) & mask_FFFF); return x; } ``` ### bitXor * 8/14 operation * 想法 : * 根據邏輯閘的電路來寫就可以了 ![](https://i.imgur.com/nfY1p0n.png) ```clike= int bitXor(int x, int y) { return ~(~(~x & y) & ~(x & ~y)); } ``` ### bitSwap * 14/25 operation * 想法 : * 先靠 shift left 3 來取得指定的 byte 是從哪個 bit 開始 * 使用 shift right 後再取出該 byte 的數值 * 使用 `XOR` 將原本該 byte 位置的數值覆蓋為 0 * 再用 `OR` 將新的 byte 貼上 ```clike= int byteSwap(int x, int n, int m) { int n_shift = n << 3; int m_shift = m << 3; int n_byte = (x >> n_shift) & 0xff; int m_byte = (x >> m_shift) & 0xff; x ^= m_byte << m_shift; x |= n_byte << m_shift; x ^= n_byte << n_shift; x |= m_byte << n_shift; return x; } ``` ### conditional * 8/16 operation ```clike= int conditional(int x, int y, int z) { x = !!x; x = ~x + 1; // get all 0 or 1 return (x & y) | (~x & z); } ``` ### countLeadingZero * 44/50 operation * 想法 : * 將所有的 1-bit 向右填滿,再取其一補數,便可以得到從左邊數來最多的 0 的 mask 。 * 以下示範 : (以 8-bits 為例) ``` 0001 1010 OR 0000 0001 (>> 4) ------------------- 0001 1011 -> 0001 1011 OR 0000 1101 (>> 2) ------------------- 0001 1111 -> 0001 1111 OR 0000 1111 (>> 1) ------------------- 0001 1111 ->(~)-> 1110 0000 ``` * 最後再做 `bitCount` 即可求出答案。 ```clike= int countLeadingZero(int x) { x |= x >> 16; x |= x >> 8; x |= x >> 4; x |= x >> 2; x |= x >> 1; x = ~x; // do bitCount int mask_01 = 0x55; int mask_0011 = 0x33; int mask_00001111 = 0x0F; int mask_FF = 0xFF; // mask 0x55555555 mask_01 = mask_01 | mask_01 << 8; mask_01 = mask_01 | mask_01 << 16; // mask 0x33333333 mask_0011 = mask_0011 | mask_0011 << 8; mask_0011 = mask_0011 | mask_0011 << 16; // mask 0x0F0F0F0F mask_00001111 = mask_00001111 | mask_00001111 << 8; mask_00001111 = mask_00001111 | mask_00001111 << 16; // mask 0x00FF00FF mask_FF = mask_FF | mask_FF << 16; x = (x & mask_01) + ((x >> 1) & mask_01); x = (x & mask_0011) + ((x >> 2) & mask_0011); x = (x & mask_00001111) + ((x >> 4) & mask_00001111); x = (x & mask_FF) + ((x >> 8) & mask_FF); return (x + (x >> 16)) & 0xFF; } ``` ### copyLSB * 3/5 operation * 想法 : * 先 mask 出 LSB 後取其二補數。 * 若 LSB 為 1 ,二補數為 0xFFFFFFFF (-1) ; 若 LSB 為 0 ,二補數仍為 0 。 ```clike= int copyLSB(int x) { x = x & 0x1; return ~x + 1; } ``` ### distinctNegation * 5/5 operation * 想法 : * 比較某數值與其二補數是否相同,若兩者 `XOR` 後結果為 0 則表示該數值的正數與負數完全一樣,若為非 0 則表示兩者不同。 ```clike= int distinctNegation(int x) { return !!(x ^ (~x + 1)); } ``` ### dividePower2 * (9+1)/15 operation * 想法 : * 正數除 2^n^ 會向下取整數,等同於向零取整數;雖然負數也會向下取整數,但卻不是向零取整數,故結果須再加 1 。 * 首先判斷是否需要加 1 ,如果負數作 shift right 後有 bit 遺失,則表示需要 +1 。 * 下方可以看到 -6 除以 2^2^ 後應該要是 -1 ,但結果卻是 -2 ,其原因是在位移的過程中遺失了位元 : ``` 1010 -> (>> 2) -> 1110 -6 -2 ``` * 所以只要判斷**是否遺失位元**與**是否為負數**即可,若都是則加 1 來向零取整數。 ```clike= int dividePower2(int x, int n) { int missing_bit = !!(x ^ ((x >> n) << n)); int neg = x >> 31; return (x >> n) + (missing_bit & neg); } ``` * 因為 `neg = x >> 31` 會造成 cppcheck error ,故應修改為 `neg = x >> 30 >> 1` 。 ### evenBits * 4/8 operation * 想法 : * 規定最多只能使用 8-bits ,所以只要把 8-bits 的 `0101 0101` 複製成 32-bits 即可。 ```clike= int evenBits(void) { int x = 0x55; x |= x << 8; x |= x << 16; return x; } ``` ### ezThreeFourths * (11+1)/12 operation * 想法 : * 先將數值乘上 3 ,再除以 4 即可。 * 由於也需要向零取整數,所以延續 `dividePower2` 的部份程式碼。 ```clike= int ezThreeFourths(int x) { x = (x << 1) + x; // 2x + x = 3x // do dividePower2 int missing_bit = !!(x ^ ((x >> 2) << 2)); int neg = x >> 31; return (x >> 2) + (missing_bit & neg); } ``` * 因為 `neg = x >> 31` 會造成 cppcheck error ,故應修改為 `neg = x >> 30 >> 1` 。 ### fitsBits * 7/15 operation * 想法 : * 如果說 n-bits 就足夠表示該數值,那麼左位移掉多餘的 bits 照理來說應該也不影響數值才對。 * 所以先左位移掉多餘的 bits 後再右位移回來,比較和原本的有沒有差異。 ```clike= int fitsBits(int x, int n) { int shift = 32 + (~n + 1); // same as 32 - n int y = x << shift >> shift; return !(x ^ y); } ``` ### fitShort * 4/8 operation ```clike= int fitsShort(int x) { int y = x << 16 >> 16; return !(x ^ y); } ``` --- ## bits.c - floating point ### floatAbsVal * 9/10 operation * 想法 : * 只需要修改 floating point 上的 sign bit 就好,最快的方法就是先 shift left 1 再 shift right 1 回來,由於是型別是 unsigend 所以右位移回來並不會有 sign extension 。 * 需要注意的是當值為 NaN 時,就改回傳參數。 * 判斷方法就是將 exp 與 frac 的部份取出並檢查是否為 NaN ,如果 exp 為 0xff 且 frac 不為 0 ,則表示是 NaN ,回傳參數。 ```clike= unsigned floatAbsVal(unsigned uf) { unsigned _exp = (uf >> 23) & 0xff; unsigned _frac = uf << 9 >> 9; if (!(_exp ^ 0xff) && _frac) return uf; return uf << 1 >> 1; } ``` ### floatFloat2Int * 17/30 operation * 想法 : * 因為是要從 floating point 轉成 integer ,所以第一步要做的就是拆解出 `sign` 、 `exp` 、 `frac` 這三個部分。 * 接下來計算 Exponent `E` ,若為負值,表示 `frac` 部分是小數,轉成正數一定會被捨去,故可以直接回傳 0 , denormalized 的 case 也包含在此。 * 下一個 `else if` 是要處理 special case 的,照理來說應該填 `else if (E == 128)` ,也就是當 `exp` 都為 1 時的情況,那為什麼改成 `E > 23` 呢? 由於 `frac` 只有 23 位,當 `E` 大於 23 時, right-shifted 會把所有位元都移除,也就是只剩下 1 。 * 最後再根據 `sign` 是否為 1 決定是否取其二補數。 ```clike= int floatFloat2Int(unsigned uf) { // s | exp | frac // 1-bit| 8-bits | 23-bits unsigned _sign = ~(uf >> 31) + 1; unsigned _exp = (uf >> 23) & 0xff; unsigned _frac = uf & 0x7fffff; unsigned bias = 127; int E = _exp - bias; if (E < 0) // exp too small, truncated return 0; else if ( E > 23 ) // exp is full set, only Nan and infinity // why not 128 ? because 23 is the most bit it can shift. // Further left-shift will become 1. return 0x80000000u; _frac |= 0x800000; _frac = _frac >> (23 - E); return ((_frac ^ _sign) + (_sign & 1)); } ``` ### floatInt2Float * 29/30 operation * 想法 : * 第一步是先判斷正負並取其 `sign` ,如果是負數還需要轉成正數再做處理,因為表示法不再是二補數了。 * 接下來一直做 left-shifted 直到 Most Significant 1-bit ,記錄其位置, 31 減去該位置後可以得到 Exponent ,並計算 `exp` ,位移後在那個 Most Significant 1-bit 之後的部分即是 `frac` 。 * 接著再將 `sign` 、 `exp` 、 `frac` 使用 `OR` 作融合即可得到結果。 * 但還需注意的是**捨入**,如果被捨棄的部分是超過 0.5 的話須進位,如果剛好 0.5 則需要檢查進位後是否為偶數,是則進位,否則捨棄。 ```clike= unsigned floatInt2Float(int x) { unsigned _sign = 0, _exp = 0, _frac = 0, result = 0 ; unsigned MSB = 1 << 31; unsigned bias = 127; int i = 0; if (x >> 31) { _sign = 1; x = ~x + 1; } if (x != 0) { while (!(x & MSB)) { x = x << 1; i = i + 1; } x = x << 1; i = 31 - i; }else return 0; _exp = bias + i; // E = exp - bias _frac = x; result = _sign << 31; result |= _exp << 23 ; result |= _frac >> 9; // check for rounding if ((_frac >> 8) & 1) { // truncated bits are more than 1/2 if (_frac & 0xff) // has 1/2 and the further bits are not zero return result + 1; else { // has 1/2 but the further bits are all zero // then, decided by the LSB if (result & 1) // LSB is 1, then add 1 return result + 1; else // LSB is 0, then don't add 1 return result; } } // truncated bits are less than 1/2 return result; } ``` ### floatIsEqual ![](https://i.imgur.com/Li7Ibyi.png) 為什麼會 timeout ### floatIsLess ![](https://i.imgur.com/qJR2MS2.png) 為什麼會 timeout --- ## bits.c - Arithmetic ### getByte * 3/6 operation * 想法 : * 將 `n` 乘上 8 可以得到要取得位元組的起始位置。 * 把 `x` 右位移後即可得到該位元組。 ```clike= int getByte(int x, int n) { int shift = n << 3; return (x >> shift) & 0xff; } ``` ### greatestBitPos * (15+1) / 70 operaiton * 想法 : * 使用 shift 和 `OR` 將 Most Significant 1-bit 右手邊的位元全填成 1 。 * 由於 MS 1-bit 的左手邊位元全會是 0 ,故可作 right-shift 1 ,並與原本的數值做 `XOR` 來得到 MS 1-bit 的 mask。作 `& ~(1 << 31)` 只是為了要避免 sign extension 產生額外的 1 。 ``` 0110 000 => use shift and OR => 0111 1111 => right-shift 1 => 0011 1111 0111 1111 (x) ^ 0011 1111 (x >> 1) ------------ 0100 0000 <- MS 1 bit mask ``` ```clike= int greatestBitPos(int x) { x |= x >> 16; x |= x >> 8; x |= x >> 4; x |= x >> 2; x |= x >> 1; int y = (x >> 1) & ~(1 << 30 << 1); return x ^ y; } ``` ### howManyBits * 46/90 operation * 想法 : * 對於正數,只要找到 MS 1-bit 就知道他需要幾位才夠表示,但是負數卻不行。 * `1111 1000` 的最少表示位元數是 4 ,前面的 4 個 1 都是可以忽略的,重點是在 `1000` ,對於負數,只要找到其從 MSB 開始的連續 1 之最右手邊的位置即是以二補數表示所需最少位元數。 * 要找到該位置,先取其一補數得到 `0000 0111` ,再找到其 MS 1-bit 的左 1 bit 即是。 ```clike= int howManyBits(int x) { int sign = x >> 31; // pos: 0, neg: 0xffffffff int num ; x = x ^ sign; // if it's neg, then get ones' complement. x |= x >> 16; x |= x >> 8; x |= x >> 4; x |= x >> 2; x |= x >> 1; // do bitCount int mask_01 = 0x55; int mask_0011 = 0x33; int mask_00001111 = 0x0F; int mask_FF = 0xFF; // mask 0x55555555 mask_01 = mask_01 | mask_01 << 8; mask_01 = mask_01 | mask_01 << 16; // mask 0x33333333 mask_0011 = mask_0011 | mask_0011 << 8; mask_0011 = mask_0011 | mask_0011 << 16; // mask 0x0F0F0F0F mask_00001111 = mask_00001111 | mask_00001111 << 8; mask_00001111 = mask_00001111 | mask_00001111 << 16; // mask 0x00FF00FF mask_FF = mask_FF | mask_FF << 16; x = (x & mask_01) + ((x >> 1) & mask_01); x = (x & mask_0011) + ((x >> 2) & mask_0011); x = (x & mask_00001111) + ((x >> 4) & mask_00001111); x = (x & mask_FF) + ((x >> 8) & mask_FF); num = (x + (x >> 16)) & 0xFF; return num + 1; } ``` ### implication * 2/5 operation * 想法 : * $p \to q \equiv \neg p \lor q$ * 真值表 : | $p$ | $q$ | $\neg p$ | $\neg p \lor q$ | | -------- | -------- | -------- | ---| | 1 | 1 | 0 | 1 | | 1 | 0 | 0 | 0 | | 0 | 1 | 1 | 1 | | 0 | 0 | 1 | 1 | ```clike= int implication(int x, int y) { return (!x) | y; } ``` ### isAsciiDigit * 9/15 operation * 想法 : * 只要 x 的數值是在 0x30 ~ 0x39 之間即是 Ascii code 的 0 ~ 9 。 * 也就是說 `x - 0x30` 與 `0x39 - x` 都必須要大於等於 0 。 ```clike= int isAsciiDigit(int x) { int upper = 0x39 + (~x + 1); int lower = x + (~0x30 + 1); return !((lower | upper) >> 31); } ``` ### isEqual * 2/5 operation ```clike= int isEqual(int x, int y) { return !(x ^ y); } ``` ### isGreater 、 isLess * 14/24 operation * 想法 : (兩題思路一樣,不再贅述,以 `isGreater` 作解釋) * 將 `x > y` 看成 `0 > y - x` 來判斷。 * 要區分為有 Overflow 的 case 和沒有 Overflow 的 case 。 * 如果 `x` 、 `y` 正負號相同,則表示沒有 Overflow ,就計算 `y - x` 是否大於 0 。 * 如果 `x` 、 `y` 正負號相異,則表示可能有 Overflow ,就根據 `y` 的正負號去判斷是否大於 0 。 * 使用 `condition` 的技巧去選擇該怎麼判斷。 ```clike= int isGreater(int x, int y) { int same_sign = ((x ^ y) >> 31) & 1; int y_sign = (y >> 31) & 1; int gt = ((y + (~x + 1)) >> 31) & 1; return (y_sign & same_sign) | (gt & !same_sign); } int isLess(int x, int y) { int same_sign = ((x ^ y) >> 31) & 1; int x_sign = (x >> 31) & 1; int lt = ((x + (~y + 1)) >> 31) & 1; return (x_sign & same_sign) | (lt & !same_sign); } ``` ### isLessOrEqual * (14+3)/24 operation * 想法 : * 因為可能會 Overflow ,所以要分成**有溢位**和**沒溢位**來處理。 * 將 `x <= y` 解讀為 `0 <= y - x` ,只有當 `x` 、 `y` 是異號時才有可能 Overflow 。 * 當如果 `x` 、 `y` 為異號,則 `y - x` 結果之正負號一定會等於 `y` 的正負號,除非 Overflow 。 * 這裡處理 Overflow 的方法為當 `x` 、 `y` 是異號時,也就是可能發生溢位的情況下,不去計算其結果,根據 `y` 的正負號來決定。 ```clike= int isLessOrEqual(int x, int y) { int x_sign = !(x >> 30 >> 1); int y_sign = !(y >> 30 >> 1); int same_sign = x_sign ^ y_sign; int z = !((y + (~x + 1)) >> 30 >> 1); // z >= 0 : 0, z < 0 : 1 return (z & (!same_sign)) | (y_sign & same_sign); } ``` ### isNonZero * 5/10 operation * 想法 : * 0 的特色是其二補數一模一樣,但不幸的是 Tmin 也有同樣的特性,不過幸好 0 沒有 Tmin 的 sign bit , 所以還是可以靠這個來分辨。 ```clike= int isNonZero(int x) { return ((x | (~x + 1)) >> 31) & 1; } ``` ### isPallindrome * 43/45 operation * 想法 : * 做前面提到的 `bitReverse` 後比較看看和原本的一不一樣即可。 ```clike= int isPallindrome(int x) { int mask_01 = 0x55; int mask_0011 = 0x33; int mask_00001111 = 0x0F; int mask_FF = 0xFF; int mask_FFFF = mask_FF | mask_FF << 8; int y = x; // original value // mask 0x55555555 mask_01 = mask_01 | mask_01 << 8; mask_01 = mask_01 | mask_01 << 16; // mask 0x33333333 mask_0011 = mask_0011 | mask_0011 << 8; mask_0011 = mask_0011 | mask_0011 << 16; // mask 0x0F0F0F0F mask_00001111 = mask_00001111 | mask_00001111 << 8; mask_00001111 = mask_00001111 | mask_00001111 << 16; // mask 0x00FF00FF mask_FF = mask_FF | mask_FF << 16; x = ((x & mask_01) << 1) | ((x >> 1) & mask_01); x = ((x & mask_0011) << 2) | ((x >> 2) & mask_0011); x = ((x & mask_00001111) << 4) | ((x >> 4) & mask_00001111); x = ((x & mask_FF) << 8) | ((x >> 8) & mask_FF); x = ((x & mask_FFFF) << 16) | ((x >> 16) & mask_FFFF); return !(x ^ y); } ```