2256 views
# 課前測驗參考解答: Q2 ( contributed by 吳柏儒, 安正, riversnow ) - [ ] 在 C 程式中,使用遞迴和 bit-wise operator 來實作乘法運算,請參考以下提示: * 加法器是用於執行加法的電路元件,通常由 AND 閘、OR 閘 和 XOR 閘構成 - 也可用加法器實作減法,只要將減數轉成二補數,並注意溢位即可 * 半加器:將兩個一位二進位數相加 (input: A, B) (output: S, C) ![](https://i.imgur.com/HRN0c1D.png) * 全加器:將兩個一位二進位數相加 (input: A, B, Cin) (output: S, Cout) ![](https://i.imgur.com/cypKq1H.png) * 波紋進位加法器:使用多個一位全加器構成 N 位加法器 ![](https://i.imgur.com/X5fQcYn.png) * 半加器可用以下 C 程式來實作: ```c uint32_t half_add(uint32_t a, uint32_t b) { if (b == 0) return a; uint32_t sum = a ^ b; /* 相加但不進位 */ uint32_t carry = (a & b) << 1; /* 進位但不相加 */ return half_add(sum, carry); } ``` ## 思路 回顧國中數學教的乘法運算: ``` 123 x 456 ------- 738 -> 123x6 615 -> 123x5 + 492 -> 123x4 ------- 56088 ``` 此法是否能套用於二進位形式? ``` 1101 -> 13 in decimal x 1001 -> 9 in decimal --------- 1101 -> 1101 & 1 0000 -> 1101 & 0 shift 1 position to the left 0000 -> 1101 & 0 shift 2 position to the left + 1101 -> 1101 & 1 shift 3 position to the left --------- 1110101 -> 117 in decimal = 13 x 9 ! ``` 可以結合位移運算、AND運算與半加器來實現乘法運算: 重複上面例子,假設A = 0b1101, B = 0b1001,重新思考如下: ``` 1101 -> A x 1001 -> B --------- 1101 -> A shift 0 & B first bit 0000 -> A shift 1 & B second bit 0000 -> A shift 2 & B third bit + 1101 -> A shift 3 & B fourth bit --------- 1110101 ``` 若與A做AND運算的bit為0時,則無須做加法,因此可以化簡為: ``` 1101 -> A x 1001 -> B --------- 1101 -> A shift 0 & B first bit + 1101 -> A shift 3 & B fourth bit --------- 1110101 ``` 以C語言來實現乘法運算如下: ```C uint32_t mul(uint32_t a, uint32_t b) { uint32_t product = 0; while(b != 0) { if(b & 0x1) { //if this bit is not 0, than continue the addition product = add(product, a); //use the half adder } a = a << 1; b = b >> 1; } return product; } ``` ## 全加器 首先實做全加器,函式參數增加低位進位信號: ```clike= // full_add uint32_t full_add(uint32_t a, uint32_t b, uint32_t c) { if (c == 0 && b == 0) return a; uint32_t sum = a ^ b; /* 相加但不進位 */ uint32_t carry = ((a & b) | (c & sum)) << 1; /* 進位但不相加 */ sum = sum ^ c; /* 相加但不進位 */ return full_add(sum, carry, 0); } ``` ## 波紋進位加法器 接著實做,波紋進位加法器,原本打算取bit後相加,這需要額外的空間和運算,因此就直接在變數中用mask的方式移動,進行加法運運算,接著把進位信號加到下一位並移動mask,此時就利用遞迴的方式實現: ```clike= // ripple_add uint32_t ripple_add(uint32_t a, uint32_t b, uint32_t c, uint32_t m) { if (m == 0) return a; uint32_t sum = (a ^ b) & m; /* 相加但不進位 */ uint32_t carry = ((a & b) & m | (c & sum) & m) << 1; /* 進位但不相加 */ sum = sum ^ c; /* 相加但不進位 */ a = (sum & m) ? (a | sum) : (a & ~m); return ripple_add(a, b, carry, m << 1); /* 往下一位移動 */ } ``` ## 乘法器 1 乘法器的實做,則是簡單的利用加法器,把被乘數相加到乘數的次數為止,有更快速的方式可以利用shift加速,這是第一個版本。 ```clike= // multiply uint32_t mult(uint32_t a, uint32_t b) { uint32_t sum = a; if (a == 0 | b == 0) return 0; while (b = del(b, 1), b > 0) { sum = add(sum, a); if (a > sum) /* Overflow!!! */ return -1; } return sum; } ``` ## 乘法器 2 這個版本比循環加法有效率,採用shift的方式移動被乘數的位元到高位進行相加。 做了簡單的效能測式,比純用加法快超多~ mult calc in **8.2** seconds mult shift calc in **0.0077** seconds 效能提升100倍! ```clike= // multiply2 uint32_t mult_shift_imp(uint32_t a, uint32_t b, uint32_t m, uint32_t sum) { if (m == 0) { return sum; } if (a == 0 | b == 0) return sum; if (b & m) { sum = add(sum, a); if (a > sum) return -1; } mult_shift_imp(a << 1, b, m << 1, sum); } uint32_t mult_shift(uint32_t a, uint32_t b) { uint32_t sum = 0; sum = mult_shift_imp(a, b, 0x1, sum); return sum; } ``` 減法則如題目提示,利用二補數進行加法。 ```clike= // del uint32_t del(uint32_t a, uint32_t b) { uint32_t ret = ripple_add(a, ~b + 1, 0, 1); if (ret > a) return -1; // overflowed!! } ``` ## 思考 overflow 以下的程式實作了 x * y 的運算 ```Clike #include <stdio.h> #include <stdbool.h> int add(int result, int x); int main(int argc, char const *argv[]) { int x, y; int result = 0; bool isfalse = false; bool isoverflow = false; scanf("%d%d", &x, &y); if ((x < 0) || (y < 0)) { if ((x < 0) && (y < 0)) { isfalse = false; x = (-x); y = (-y); } else { isfalse = true; if (x < 0) x = (-x); if (y < 0) y = (-y); } } if (x == 0 || y == 0) { //如果x或y為0,可以直接說結果是0 result = 0; } else { /* * 想法是每當y為奇數(LSB為1)時,進行一次加法運算 * 為何對的原因是,每當LSB為0時,我們雖然不執行加法, * 但我們將x*2,讓每一個二進位下>的y的每一個1所對應到的都是一個加法 * 加的值是:(2^n)*x,n是從y的LSB數來的第幾位數, * 因此我們可以說這樣的運算是正確的 */ while (y != 0) { if (y & 1) { result = add(result, x); if (result == -1) { printf("overflow\n"); isoverflow = true; break; } } y >>= 1; // y向右推 if ((y != 0) && (x & 0x80000000)) { printf("overflow\n"); result = -1; isoverflow = true; break; } x <<= 1; // x*2 } } if (isfalse) { result = (-result); } if (isoverflow) { return 0; } else { printf("result=%d", result); } return 0; } ```