# 2022q1 Homework3 (quiz3) contribute by < `kdnvt` > ### 測驗 `11` ```c unsigned long i_sqrt(unsigned long x) { unsigned long b, m, y = 0; if (x <= 1) return x; m = 1UL << (fls(x) & ~1UL); while (m != 0) { b = y + m; y >>= 1; if (x >= b) { x -= b; y += m; } m >>= 2; } return y; } ``` [直式開方法](https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Digit-by-digit_calculation) 雖然教授問答的時候是從二進位的角度去想,但我第一次看到類似的原理其實是從十進位的直式開方法。不過直式開方法所有進位都適用,所以從十進位的想法轉成二進位也沒有很難。 直式開方法是把要開方的數拆成二的冪相加(如果是十進位就是十的冪),就可以拆成以下的形式: $x = (000b_0b_1b_2\cdots b_{n-1}b_{n})^2$ 上面的 $000b_0b_1b_2\cdots b_{n-1}b_{n}$ 為 bits pattern 。 $b_0$ 為最高位的 1 , 寫成數學式如下: $x = (2^{n}b_0+2^{n-1}b_1+\cdots+2^1b_{n-1}+2^0b_{n})^2$ $a_i = 2^{n-i}b_i$ $\begin{aligned}x &=(a_0+a_1+\cdots+a_{n-1}+a_{n})^2 \\ &= a_0^{2}+2a_0a_1 + a_1^2 + 2(a_0+a_1)a_2 + a_2^2 + \cdots + a_n-1^2 + 2\left(\sum\limits_{i = 0}^{n-1}{a_i}\right)a_n + a_n^2 \\ &=a_0^2 + (2a_0+a_1)a_1 + [2(a_0+a_1)+a_2]a_2 + \cdots + [2\left(\sum\limits_{i = 0}^{n-1}{a_i}\right)+a_n]a_n \end{aligned}$ 最後的形式為 $[2\left(\sum\limits_{i = 0}^{n-1}{a_i}\right)+a_n]a_n$ 的相加,而這每一項其實就是程式中每一輪迴圈 ``` if (x >= b) { x -= b; y += m; } ``` 要判斷並可能減掉的 `b` 。 以下大致講解原理: 迴圈從 0 開始。 $y_i$ 為每個第 $i$ 個迴圈一開始的 `y` 值。 $m_i$ 為每個第 $i$ 個迴圈一開始的 `m` 值。 $l_i$ 為 $\sqrt m_i$ 。 $y_0 = 0$ $m_i = l_i^2$ $l_i = 2^{n-i}$ 因為確定 $a_0$ 是最高位不為零,所以 $a_0 = l_0$ $y_1 = m_0 = l_0^2 = a_0^2 = l_0a_0$ 而每經過一輪 $m$ 會右移兩位,他的平方根 $l$ 就等於右移一位: $m_i = m_{i+1}\times4$ $l_i = l_{i+1}\times2$ $y_1 = l_0a_0$ $y_1 = 2l_1a_0$ $y_1+m_1 = 2l_1a_0 + l_1^2 = (2a_0+l_1)l_1$ 如果此時 $y_1+m_1$ 的結果小於當前的 $x$ ,代表 $x$ 的展開式中 $(2a_0+a_1)a_1$ 這項不為零,也就是 $a_1 = 2^{n-1} = l_1$ 。將 $y_1$ 右移一位後加上 $m_1$ ,就會變成 $y_2 = \dfrac{y_1}{2} + m_1 = l_1a_0 + l_1^2 = l_1(a_0+a_1) = 2\times l_2(a_0+a_1)$ 而如果此時 $y_1+m_1$ 的結果大於當前的 x ,代表 $x$ 的展開式中 $(2a_0+a_1)a_1$ 這項等於零,也就是 $a_1 = 0$ 。將 $y_1$ 右移一位後就會變成 $y_2 = \dfrac{y_1}{2} = l_1a_0 = l_1(a_0+a_1) = 2\times l_2(a_0+a_1)$ 照同樣的原理推下去可以發現, $y_j$ 其實就等於 $2\left(\sum\limits_{i = 0}^{j-1}{a_i}\right)l_j$ 。因為不是所有的 $l_j 都會等於 a_j$ ,所以我用另一個變數 $z_j = 2\left(\sum\limits_{i = 0}^{j-1}{a_i}\right)a_j$ 來表達原式中的部份,$z_j = \dfrac{y_ja_j}{l_j}$ ,如果 $a_j = l_j$ 也就是 $a_j \neq 0$ ,則 $z_j$ 會等於 $y_j$ 而且下圖的 $a_i^2$ 會等於 $m_i$ : $\underbrace{a_0^2}_{z_0+a_0^2} + \underbrace{(2a_0+a_1)a_1}_{z_1+a_1^2} + \underbrace{[2(a_0+a_1)+a_2]a_2}_{z_2+a_2^2} + \cdots + \underbrace{[2\left(\sum\limits_{i = 0}^{n-1}{a_i}\right)+a_n]a_n}_{z_n+a_n^2}$ $z_i+a_i^2 = \left\{ \begin{array}{r} 0 , & \text{if }a_i=0 \\ y_i + m_i , & \text{if } a_i=l_i \end{array} \right.$ 每一輪迴圈就是判斷 $x$ 有沒有 $z_i+a_i^2$ 並更新 $y_i$, 到了最後的 $y_n = 2\left(\sum\limits_{i = 0}^{n-1}{a_i}\right)l_n = 2\left(\sum\limits_{i = 0}^{n-1}{a_i}\right)2^0 = 2\left(\sum\limits_{i = 0}^{n-1}{a_i}\right)$ , $y_n$ 會先往右移變成 $\sum\limits_{i = 0}^{n-1}{a_i}$ ,在加上最後的 $a_n$ ,變成 $\sum\limits_{i = 0}^{n}{a_i}$ ,就是我們要求的平方根。