# [2020q3](http://wiki.csie.ncku.edu.tw/sysprog/schedule) 第 5 週測驗題
###### tags: `sysprog2020`
:::info
目的: 檢驗學員對 [浮點數運算](https://hackmd.io/@sysprog/c-floating-point), [數值系統](https://hackmd.io/@sysprog/c-numerics) 及 [bitwise 操作](https://hackmd.io/@sysprog/c-bitwise) 的認知
:::
==[作答表單](https://docs.google.com/forms/d/e/1FAIpQLSfMdc1mXSL4mxLygfMZi7A0XaoBDLZl9gjRpCXpbYD0ITSaIg/viewform)==
### 測驗 `1`
考慮到以下浮點數除法程式: (fdiv.c)
```cpp
#include <stdio.h>
#include <stdlib.h>
double divop(double orig, int slots) {
if (slots == 1 || orig == 0)
return orig;
int od = slots & 1;
double result = divop(orig / D1, od ? (slots + D2) >> 1 : slots >> 1);
if (od)
result += divop(result, slots);
return result;
}
```
假設 `divop()` 的第二個參數必為大於 `0` 的整數,而且不超過 `int` 型態能表達的數值上界。請補完程式碼。
==作答區==
`D1` = ?
* `(a)` 4
* `(b)` 3
* `(c)` 2
* `(d)` 1
`D2` = ?
* `(a)` 4
* `(b)` 3
* `(c)` 2
* `(d)` 1
:::success
延伸問題:
1. 解釋上述程式碼運作原理;
2. 以編譯器最佳化的角度,推測上述程式碼是否可透過 [tail call optimization](https://en.wikipedia.org/wiki/Tail_call) 進行最佳化,搭配對應的實驗;
* 搭配閱讀 [C 語言: 編譯器和最佳化原理](https://hackmd.io/s/Hy72937Me) 及 [C 語言: 遞迴呼叫](https://hackmd.io/s/rJ8BOjGGl)
3. 比照 [浮點數運算和定點數操作](https://hackmd.io/@NwaynhhKTK6joWHmoUxc7Q/H1SVhbETQ),改寫上述程式碼,提出效率更高的實作,同樣避免使用到 FPU 指令 (可用 `gcc -S` 觀察對應的組合語言輸出),並與使用到 FPU 的實作進行效能比較
:::
---
## 測驗 `2`
延續 [從 $\sqrt{2}$ 的運算談浮點數](https://hackmd.io/s/ryOLwrVtz),假設 float 為 32-bit 寬度,考慮以下符合 IEEE 754 規範的平方根程式,請補上對應數值,使其得以運作:
```cpp
#include <stdint.h>
/* A union allowing us to convert between a float and a 32-bit integers.*/
typedef union {
float value;
uint32_t v_int;
} ieee_float_shape_type;
/* Set a float from a 32 bit int. */
#define INSERT_WORDS(d, ix0) \
do { \
ieee_float_shape_type iw_u; \
iw_u.v_int = (ix0); \
(d) = iw_u.value; \
} while (0)
/* Get a 32 bit int from a float. */
#define EXTRACT_WORDS(ix0, d) \
do { \
ieee_float_shape_type ew_u; \
ew_u.value = (d); \
(ix0) = ew_u.v_int; \
} while (0)
static const float one = 1.0, tiny = 1.0e-30;
float ieee754_sqrt(float x)
{
float z;
int32_t sign = 0x80000000;
uint32_t r;
int32_t ix0, s0, q, m, t, i;
EXTRACT_WORDS(ix0, x);
/* take care of zero */
if (ix0 <= 0) {
if ((ix0 & (~sign)) == 0)
return x; /* sqrt(+-0) = +-0 */
if (ix0 < 0)
return (x - x) / (x - x); /* sqrt(-ve) = sNaN */
}
/* take care of +INF and NaN */
if ((ix0 & 0x7f800000) == 0x7f800000) {
/* sqrt(NaN) = NaN, sqrt(+INF) = +INF,*/
return x;
}
/* normalize x */
m = (ix0 >> 23);
if (m == 0) { /* subnormal x */
for (i = 0; (ix0 & 0x00800000) == 0; i++)
ix0 <<= 1;
m -= i - 1;
}
m -= 127; /* unbias exponent */
ix0 = (ix0 & 0x007fffff) | 0x00800000;
if (m & 1) { /* odd m, double x to make it even */
ix0 += ix0;
}
m >>= 1; /* m = [m/2] */
/* generate sqrt(x) bit by bit */
ix0 += ix0;
q = s0 = 0; /* [q] = sqrt(x) */
r = QQ0; /* r = moving bit from right to left */
while (r != 0) {
t = s0 + r;
if (t <= ix0) {
s0 = t + r;
ix0 -= t;
q += r;
}
ix0 += ix0;
r >>= 1;
}
/* use floating add to find out rounding direction */
if (ix0 != 0) {
z = one - tiny; /* trigger inexact flag */
if (z >= one) {
z = one + tiny;
if (z > one)
q += 2;
else
q += (q & 1);
}
}
ix0 = (q >> 1) + QQ1;
ix0 += (m << QQ2);
INSERT_WORDS(z, ix0);
return z;
}
```
Reference:
* [Floating point division and square root algorithms and implementation in the AMD-K7/sup TM/ microprocessor](http://ieeexplore.ieee.org/document/762835/)
* [√Square Roots](http://www.azillionmonkeys.com/qed/sqroot.html)
請補完程式碼,使上述程式碼得以運作。
==作答區==
QQ0 = ?
* `(a)` 0x01000000
* `(b)` 0x7ff00000
* `(c)` 0x00800000
* `(d)` 0x3f000000
QQ1 = ?
* `(a)` 0x01000000
* `(b)` 0x7ff00000
* `(c)` 0x00800000
* `(d)` 0x3f000000
QQ2 = ?
* `(a)` 31
* `(b)` 30
* `(c)` 28
* `(d)` 27
* `(e)` 25
* `(f)` 24
* `(g)` 23
* `(h)` 16
* `(i)` 3
:::success
延伸題目:
1. 解釋上述程式碼何以運作
* 搭配研讀 [以牛頓法求整數開二次方根的近似值](http://science.hsjh.chc.edu.tw/upload_works/106/44f6ce7960aee6ef868ae3896ced46eb.pdf) (第 19 頁)
2. 嘗試改寫為更有效率的實作,並與 FPU 版本進行效能和 precision /accuracy 的比較
3. 練習 LeetCode [69. Sqrt(x)](https://leetcode.com/problems/sqrtx/),提交不需要 FPU 指令的實作
:::
---
## 測驗 `3`
LeetCode [829. Consecutive Numbers Sum](https://leetcode.com/problems/consecutive-numbers-sum/) 給定一個正整數 N,問 N 能寫成多少種連續正整數之和,例如 `9` 可寫成 $4+5$,或者 $2+3+4$。由於要寫成連續正整數之和,則可用等差數列來思考,且差值為 1,這個等差數列不必從 1 開始,假設從 x 開始,且個數共有 k 個,則可寫出該等差數列為:
$$
x, x+1, x+2, ..., x+(k-1)
$$
令其和為 N,根據等差數列的求和公式,可寫出以下等式:
$$
kx + \frac{(k-1)k}{2} = N
$$
整理後可得:
$$
kx = N - \frac{(k-1)k}{2}
$$
對任意 k 值,倘若 x 能得到正整數解,就表示必定會有個對應的等差數列和為 N。由於 k 是等差數列的長度,首先必定大於 0,即為下限。由於 x 也必是正整數,可得:
$$
N - \frac{(k-1)k}{2} > 0
$$
從而得到近似解:
$$
k < \sqrt{2N}
$$
確認 k 的範圍後,即可走訪數值。
參考實作如下:
```cpp
int consecutiveNumbersSum(int N)
{
if (N < 1)
return 0;
int ret = 1;
int x = N;
for (int i = 2; i < x; i++) {
x += ZZZ;
if (x % i == 0)
ret++;
}
return ret;
}
```
請補完程式碼,使上述程式碼得以運作。
==作答區==
ZZZ = ?
* `(a)` i
* `(b)` i + 1
* `(c)` i - 1
* `(d)` 1 - i
* `(e)` 1
:::success
延伸題目:
1. 解釋上述程式碼何以運作
2. 嘗試改寫為更有效率的實作
:::
---