contributed by < Xx-oX
>
1
考慮對兩個無號整數取平均值的程式碼
可以改寫成以下等價的實作
A
或者
B
填入 EXPx ,並
A
a >> 1
表示
而 在 均為奇數時,會比預期的結果少 1
e.g. a = 11, b = 13, 均為 unsigned (實行邏輯右移)
比預期的答案 (12) 少一
原因是右移會把 LSB 捨去,而兩個奇數 LSB 加總的進位也就被忽略了
因此,EXP1 要判斷 a, b 是否均為奇數,如果成立,則加上被捨去的 1
判斷奇數 => 判斷 LSB => 跟 1 做 &
(AND) => a & b & 1
B
考慮半加器
a 和 b 的平均 =
比照 A
右移 s 而省略的 LSB 以及進位由 c 補上
因此
EXP2 填入 a & b
EXP3 填入 a ^ b
以 a = 11, b = 13 驗證
環境: gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04)
命令: gcc -S -O3 t.c
之後會嘗試改用 compiler explorer,以圖方便
測試程式t.c
A
的對應組合語言
B
的對應組合語言
發現 B
版本相比 A
版本
mov
操作and
操作shr
操作xor
操作註: 後綴
l
表示是針對 32-bit 暫存器的指令
TODO: 以第一手資料解釋
2
考慮以下程式碼
填入 EXPx ,並
參考用真假值表
0 1 0 1 0 0 1 0 0 0 1 1 0 1 0 1
先不考慮兩者相等的情形
觀察程式碼,發現回傳 a ^ (…)
根據 XOR (, c:^
) 的特性進行判斷
恆等律
得知當 時, (EXP4 & -(EXP5))
的值為 0
自反律
由
歸零律
且結合律
得知
得知當 時, (EXP4 & -(EXP5))
的值為 a ^ b
a ^ a ^ b = b
再觀察 (EXP4 & -(EXP5))
根據 AND (, c:&
) 的特性
保真性
A & 0xFFFFFFFF = A
其中 0xFFFFFFFF
為 -1 (2's complement)保假性
得知應控制 EXP5 的值,使其在 時為 1, 時為 0
而 EXP4 則填入 a ^ b
使得 時 a ^ (...) = a ^ a ^ b = b
綜上所述
a ^ b
a < b
或 a <= b
(a, b 相等時回傳何者皆可)使得結果 a ^ ((a ^ b) & -(a < b))
等於 max(a, b)
無號數的情形即為題目的情形 (uint32_t
)
而有號數也可用相同的方法求最大值
驗證如下
取 a = -5, b = 3
取 a = -5, b = -3
因此使用相同的程式碼即可對有/無號數作找最大值的運算
3
考慮以下 64 位元 GCD 求值函式
改寫成以下等價實作
此處標示行數以便下面進行解說
補完上述程式碼,並
__builtin_ctz
改寫 GCD,分析對效能的提升註: GCD 演算法
以測驗題目提供的演算法為基底,並補上兩者皆為奇數的情形,參考自 Binary GCD algorithm
參考上述 GCD 演算法
並以 u 紀錄結果,搭配 do-while 迴圈將遞迴去除
第 4 行是判斷 u, v 中是否有一者(或兩者)為 0
第 6 到第 8 行的 for 迴圈是當 u, v 皆為偶數時,將兩者均除以 2 ,並用 shift
紀錄待會要乘以 2 的次數
第 9 到第 10 行的迴圈不斷將 u 除以 2 直到它變成奇數
第 12 到第 13 行的迴圈不斷將 v 除以 2 直到它變成奇數
第 14 到第 20 行則是兩者皆為奇數的情形,相減並對 u - v 以及 u, v 中較小者作 GCD 直到出現 0
因此 21 行的 COND 應填入 v
判斷被減數 v 是否不為 0
而 RET 應填入 u << shift
將前面沒乘上的 2 通通乘回來
__builtin_ctz
改寫Built-in Function: int __builtin_ctz (unsigned int x)
Returns the number of trailing 0-bits in x, starting at the least significant bit position. If x is 0, the result is undefined.
e.g. 6 = b'0110
__builtin_ctz(6) = 1 -> 最右邊的 1 右邊有 1 個 0
將連續除 2 的動作用 __builtin_ctz
替代
e.g.
本來還想把判斷 0 的動作也替換,後來發現當它為 0 時行為未定
改寫實作
因為是 uint64_t 故使用 __builtin_ctzll (unsigned long long 版本)
4
在影像處理中,bit array 被廣泛使用,如
使用 builtin_ctzll
改寫
其中第 9 行的作用是找出目前最低位元的 1,並紀錄到 t 變數。舉例來說,若目前 bitset 為 b'000101
,那 t 就會變成 b'000001
,接著就可以透過 XOR 把該位的 1 清掉,其他保留 (此為 XOR 的特性)。
填入 EXP6 ,並
3 bits 時二補數的編碼
1 | 2 | 3 | |
---|---|---|---|
+ | 001 | 010 | 011 |
- | 111 | 110 | 101 |
+&- | 001 | 010 | 001 |
可以發現將 (c: A & (-A)
) 會得到最低位元的 1
以 bitset = 20 =
b'00010100
為例
-bitset = ~bitset + 1 =b'11101011
+1
=11101100
bitset & -bitset =b'00000100
即為所求
其中 unsigned 與 signed 僅為判讀上的差異,實際上存的 bitmap 皆相同
在 bitwise 操作時使用 unsigned 以獲得更大的可攜性 你所不知道的 c 語言 - bitwise 操作
printf(…) 中 %d 會以 signed 的方式判讀,而 %u 會以 unsigned 的方式判讀
因此 EXP6 應填入 bitset & -bitset
5
考慮以下針對 LeetCode 166. Fraction to Recurring Decimal 的實作
補完程式碼,並
例如,判斷負號只要寫作 bool isNegative = numerator < 0 ^ denominator < 0;
搭配研讀 The simple math behind decimal-binary conversion algorithms
6
__alignof__
是 GNU extension,以下是其可能的實作方式
補完程式碼,並
__alignof__
的使用案例 2 則,並針對其場景進行解說如圖,利用 struct 自動 alignment 來算出 alignment 大小
char: 1 byte
t: ? byte, 1,mod 4 = 0
所以會以 t 的大小作對齊
將 p 的記憶體位置減去 0 即為所求
因此 M 應填入 _h
以取得第三個區塊的開頭位置
而由前面 (struct {char c; t _h} *)0
可知 X 應填入 0
,才會在同個起始位置
7
考慮針對 FizzBuzz
- 從 1 數到 n,如果是 3的倍數,印出 “Fizz”
- 如果是 5 的倍數,印出 “Buzz”
- 如果是 15 的倍數,印出 “FizzBuzz”
- 如果都不是,就印出數字本身
的實作
補完,並