# G10: quiz7 #### tags: `sysprog2019` contributed < `Benny1117Yen` > ## 測驗 `1` 考慮貌似簡單卻蘊含實作深度的 [FizzBuzz](https://en.wikipedia.org/wiki/Fizz_buzz) 題目: * 從 1 數到 n,如果是 3的倍數,印出 "Fizz" * 如果是 5 的倍數,印出 "Buzz" * 如果是 15 的倍數,印出 "FizzBuzz" * 如果都不是,就印出數字本身 直覺的實作程式碼如下: (`naive.c`) ```cpp #include <stdio.h> int main() { for (unsigned int i = 1; i < 100; i++) { if (i % 3 == 0) printf("Fizz"); if (i % 5 == 0) printf("Buzz"); if (i % 15 == 0) printf("FizzBuzz"); if ((i % 3) && (i % 5)) printf("%u", i); printf("\n"); } return 0; } ``` 觀察 `printf` 的(格式)字串,可分類為以下三種: 1. 整數格式字串 `"%d"` : 長度為 2 B 2. "Fizz" 或 "Buzz" : 長度為 4 B 3. "FizzBuzz" : 長度為 8 B 考慮下方程式碼: ```cpp #define MSG_LEN 8 char fmt[MSG_LEN + 1]; strncpy(fmt, &"FizzBuzz%u"[start], length); fmt[length] = '\0'; printf(fmt, i); printf("\n"); ``` 我們若能精準從給定輸入 `i` 的規律去控制 `start` 及 `length`,即可符合 FizzBuzz 題意: ```cpp string literal: "fizzbuzz%u" offset: 0 4 8 ``` 以下是利用 bit-wise 和上述技巧實作的 FizzBuzz 程式碼: (`bitwise.c`) ```cpp #define MSG_LEN 8 static inline bool is_divisible(uint32_t n, uint64_t M) { return n * M <= M - 1; } static uint64_t M3 = UINT64_C(0xFFFFFFFFFFFFFFFF) / 3 + 1; static uint64_t M5 = UINT64_C(0xFFFFFFFFFFFFFFFF) / 5 + 1; int main(int argc, char **argv) { for (size_t i = 1; i <= 100; i++) { uint8_t div3 = is_divisible(i, M3); uint8_t div5 = is_divisible(i, M5); unsigned int length = (2 << div3) << div5; char fmt[MSG_LEN + 1]; strncpy(fmt, &"FizzBuzz%u"[(MSG_LEN >> KK1) >> (KK2 << KK3)], length); fmt[length] = '\0'; printf(fmt, i); printf("\n"); } return 0; } ``` 其中 `is_divisible` 函式技巧來自 [Faster remainders when the divisor is a constant: beating compilers and libdivide](https://lemire.me/blog/2019/02/08/faster-remainders-when-the-divisor-is-a-constant-beating-compilers-and-libdivide/),甚至 gcc-9 還內建了 [FizzBuzz optimization](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82853) (Bug 82853 - Optimize x % 3 == 0 without modulo)。 Ans: `KK1` = `div5`, `KK2` = `div3`, `KK3` = `2` ### 延伸問題 待補 程式內註解 待補 --- ## 測驗 `2` [CS:APP 第 3 版](https://hackmd.io/@sysprog/S1vGugaDQ) 一書的第 2 章家庭作業 `2.97` (Page 97) 題目: > 依據浮點數位元編碼規則,實作出符合以下原型定義和作用的函式: (`float-i2f.c`) ```cpp /* compute (float) i */ float_bits float_i2f(int i); ``` > 給定整數 `i`, 此函式計算 (float) i 逐位元的表示法。 > 其中 ```cpp typedef unsigned float_bits; ``` > 並假設 `int` 長度為 32-bit 且在 little endian 機器上執行。 可用的工具函式: ```cpp /* Assuming i > 0, calculate an integer's bit length */ /* * e.g. 0x3 => 2, 0xFF => 8 */ static inline int bits_length(int i) { return 32 - __builtin_clz(i); } /* generating bitmask. e.g. 3 => 0x00000007, 16 => 0x0000FFFF */ static inline unsigned bits_mask(int l) { return (unsigned) -1 >> (32 - l); } ``` ==本題採問答形式進行== ```cpp= #include <stdio.h> #include <limits.h> typedef unsigned float_bits; float_bits float_i2f(int i); static inline int bits_length(int i) { return 32 - __builtin_clz(i); } static inline unsigned bits_mask(int l) { return (unsigned) -1 >> (32 - l); } // Compute (float) i float_bits float_i2f(int i) { unsigned sig, exp, frac, rest; unsigned exp_sig /* exp w/ sig */, round_part; unsigned bits, fbits; unsigned bias = 0x7F; // 01111111 = 127 if (i == 0) { sig = 0; exp = 0; frac = 0; return sig << 31 | exp << 23 | frac; } if (i == INT_MIN) { sig = 1; exp = bias + 31; frac = 0; return sig << 31 | exp << 23 | frac; } sig = 0; /* 2's complatation */ if (i < 0) { sig = 1; i = -i; } bits = bits_length(i); /* ex. 14; 25; 26 */ fbits = bits - 1; /* 13; 24; 25 */ exp = bias + fbits; /* 127 + 13; 127 + 24; 127 + 25 */ rest = i & bits_mask(fbits); /* 11101101101101 & (u)-1 >> 19 = 00000000000000000001101101101101; 25 bits & (u)-1 >> 8 剩下 24 bits; 26 bits & -1 >> 7 剩下25 bits */ if (fbits <= 23) { frac = rest << (23 - fbits); exp_sig = exp << 23 | frac; } else { int offset = fbits - 23; // if fbits > 23 int round_mid = 1 << (offset - 1); /* ex. 0000 ~ 1111, mid = 1000 */ round_part = rest & bits_mask(offset); /* 等於拿多出來的一段 & 整段, 得到多出來的 part */ frac = rest >> offset; // 因為多於 23 exp_sig = exp << 23 | frac; /* round to even */ if (round_part < round_mid) { // 代表不會進位 /* nothing */ } else if (round_part > round_mid) { // 進位 +1 exp_sig += 1; } else { /* 當兩個相同時, 拿 frac 來 & 1, 等於 1 就是看 frac 最小位為 1 時就把 sig + 1 */ /* round_part == round_mid */ if ((frac & 0x1) == 1) { /* round to even */ exp_sig += 1; } } } return sig << 31 | exp_sig; } int main (in argc, char* argv[]) { float i2f = (float)(int) 15213; printf("0x%.8X\t%d\t", 15213, 15213); printf("0x%.8X\t%.50f\n", float_i2f(i2f), i2f); } ``` #### Results ``` 0x00003B6D 15213 0x466DB400 15213.00000000000000000000000000000000000000000000000000 ``` ---