# 2020q3 Homework (quiz5)
contributed by < `shauming1020` >
###### tags: `homework`
## 測驗 1
```c=
#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 型態能表達的數值上界。
### 1. 解釋上述程式碼運作原理
- ```int od = slots & 1;``` 判斷 slots 是否為奇數
- ```double result = divop(orig / D1, od ? (slots + D2) >> 1 : slots >> 1);```
- ```od ? (slots + D2) >> 1 : slots >> 1)``` 當 slots 是奇數時,必須 + 1 後再進行右移 (/2) ,如此才會滿足遞迴中止條件 (slots == 1),因此 D2 為 1
- ```orig / D1``` 浮點數無法透過右移 bit 來進行除 2 的動作,因此 D1 為 2
- 將 orig 表示為分數,分子與分母同除 2 後不會影響最後結果,當分母為 1 時,分子即為商數
- ```if (od) result += divop(result, slots);```
- 當 slots 為奇數時,slots 加 1 後再去除 2 會產生誤差,導致求出的商數比原商數小
> e.g. $1 \div 3 = 0.333$ 在計算時會變成 $1 \div 4 = 0.250$,誤差為 $0.083$,恰近似於 $0.250 / 3$
> 將上述例子用數學式描述 $\dfrac {1}{3} = \dfrac{1}{4} + \dfrac{\dfrac{1}{4}}{3}=\dfrac{1}{4}+\dfrac{1}{12}=\dfrac{4}{12}$
- 因此需要將誤差給加上去,而 slots 為偶數時,可以直接同除 2,不會有誤差的產生
- 所以每次求誤差時會一直遞迴執行程式,e.g. 求 $\dfrac{\dfrac{1}{4}}{3}$ 時會再去計算誤差
### 2. 以編譯器最佳化的角度,推測上述程式碼是否可透過 tail call optimization 進行最佳化,搭配對應的實驗;
> 搭配閱讀 C 語言: 編譯器和最佳化原理 及 C 語言: 遞迴呼叫
觀察 gcc -S -O 編譯器行為
- Recursive
```c=
int fib(int n) {
if (n == 0) return 0;
if (n == 1) return 1;
return fib(n - 1) + fib (n - 2);
}
main()
{
fib(100);
}
```
- Assembly code
```ass
...
fib:
.LFB0:
.cfi_startproc
pushq %rbp # Push to stack
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
pushq %rbx # Push to stack
.cfi_def_cfa_offset 24
.cfi_offset 3, -24
subq $8, %rsp
.cfi_def_cfa_offset 32
movl %edi, %ebx
testl %edi, %edi
je .L2
cmpl $1, %edi
jne .L4
.L2:
movl %ebx, %eax
addq $8, %rsp
.cfi_remember_state
.cfi_def_cfa_offset 24
popq %rbx
.cfi_def_cfa_offset 16
popq %rbp
.cfi_def_cfa_offset 8
ret
.L4:
.cfi_restore_state
leal -1(%rdi), %edi
call fib
movl %eax, %ebp
leal -2(%rbx), %edi
call fib
leal 0(%rbp,%rax), %ebx
jmp .L2
.cfi_endproc
...
main:
.LFB1:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
movl $100, %edi
call fib
movl $0, %eax
addq $8, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
...
```
- Tail recursion
```c=
int fib(int n, int a, int b) {
if (n == 0) return a;
return fib(n - 1 , b, a + b);
}
main()
{
fib(100, 0, 1);
}
```
---
## 測驗 2
假設 float 為 32-bit 寬度,考慮以下符合 IEEE 754 規範的平方根程式,請補上對應數值,使其得以運作:
```c=
#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;
}
```
### 1. 解釋上述程式碼何以運作
> 搭配研讀 [以牛頓法求整數開二次方根的近似值](http://science.hsjh.chc.edu.tw/upload_works/106/44f6ce7960aee6ef868ae3896ced46eb.pdf) (第 19 頁)
---
## 測驗 3
$kx = N - \dfrac{(k-1)k}{2}$,由於 x 要為正整數,因此 $x = (N - \dfrac{(k-1)k}{2}) / k \in Z^+$,即 $(N - \dfrac{(k-1)k}{2}) \% k = 0$,而 k 的範圍可從近似解 $k < \sqrt{2N}$ 得知
```c=
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;
}
```
### 1. 解釋上述程式碼何以運作
- 撰寫與推導後的數學式相同的程式碼
```c=
int consecutiveNumbersSum(int N)
{
if (N < 1)
return 0;
int ret = 1;
int x = N;
for (int i = 2; i < sqrt(2*N); i++) {
if ((N - (i-1) * i / 2) % i == 0) ret++;
}
return ret;
}
```
![](https://i.imgur.com/guxNAD0.png)
- 觀察 $S_k = N - \dfrac{(k-1)k}{2}, 1 \le k < \sqrt{2N}$
|$k$| $S_k$ | $S_k - S_{k-1}$ |
| --| -------- | --------------- |
| 1 | N | 0 |
| 2 | N + (-1) | -1 |
| 3 | N + (-3) | -2 |
| 4 | N + (-6) | -3 |
| 5 | N + (-10)| -4 |
| n | N + $\dfrac{(-n+1)n}{2}$| 1-n |
- 檢驗 $S_k \% k = 0$ 是否成立來判斷正整數解 x,從上表可以觀察出 $S_k = S_{k-1} + (1-k)$,故 ZZZ 為 (1 - i)
> e.g. $S_5 = N + (-10) = S_4 + (1 - 5) = N +(-6) + (-4)$
- 而 $S_k \% k = 0$,且 k 為大於等於 2 的正整數,因此 $2 \le k \le S_k$
- 在 for 迴圈內的 ```x += (1-i) ``` 計算出 $S_k$ 後,**進入下個迴圈前,k 會先被加一再和 $S_k$ 比較大小**,因此範圍為 $(k+1) < S_k$,迴圈的條件判斷式才會是 ``` i < x ```
### 2. 嘗試改寫為更有效率的實作
---