# How to use [digital-by-digital](https://hackmd.io/@sysprog/sqrt#Digit-by-digit-Method) implement [sqrt()](https://hackmd.io/@sysprog/linux2025-quiz2#測驗-2) in C
## 二進位逼近
在實作前先介紹何謂二進位逼近。
先看以下範例,我們利用此法求出 $\sqrt{13}$ 的整數部分
1. $13 = 2^3 + 2^2 + 2^0$, 因此我們依以下假設使用 $(S_{m})^2$ 來逼近
$$13 = \left( a_{3}2^3 + a_{2}2^2 + a_{1}2^1 + a_{0}2^0 \right)^2 = \left(\sum^{3}_{i=0} a_{i}2^{i} \right)^2 \text{和}$$
$$S_{m} = (a_{3}2^3 + \dots + a_{m}2^m) = \sum^{3}_{k=m} a_{k}2^k$$
2. 由最大項 $m=3$ 開始逼近
- $(S_{3})^2 = (a_{3}2^3)^2, \quad (2^3)^2 > 13, \text{ therefore }a_{3}=0$
- $(S_{2})^2 = (a_{3}2^3 + a_{2}2^2)^2=(a_{2}2^2)^2, \quad (2^2)^2 > 13, \text{ therefore } a_{2}=0$
- $(S_{1})^2 = (a_{3}2^3 + a_{2}2^2 + a_{1}1^1)^2=(a_{1}2^1)^2, \quad (2^1)^2 \leq 13, \text{ therefore } a_{1}=1$
- $(S_{0})^2 = (a_{3}2^3 + a_{2}2^2 + a_{1}1^1 + a_{0}2^0)^2=(2^1 + a_{0}2^0)^2, \quad (2^1+2^0)^2 \leq 13, \text{ therefore } a_{0}=1$
3. 所以 $\sqrt{13}$ 的整數部分為 $(2^1 + 2^0) = 3$
## 換個角度想
以上方法是從最高位元逐步向最低位元**遞增**逼近目標數值。
同樣地,我們也可以透過扣除的方式來逼近。接下來將說明如何使用此方法。
我們都知道簡單的平方和公式:
$\left(a+b \right)^2 = a^2+2ab+b^2$
若套用在二進位並多加一位:
> $\text{let } n1 > n2 > n3 \geq 0$
$\begin{align}
(2^{n1}+2^{n2}+2^{n3})^2
&= (2^{n1}+(2^{n2}+2^{n3}))^2 \\
&= (2^{n1})^2 + 2(2^{n1})(2^{n2}+2^{n3}) + (2^{n2}+2^{n3})^2
\end{align}$
因此可以透過類似遞迴逐漸減去最高位及中間項,再重複利用同樣的方法得到下一位,這便是程式實作的核心想法。
## Impliment in C
程式實作如下
```c
void sqrti(uint64_t x)
{
uint64_t m, y = 0;
int total_bits = 64;
/* clz64(x) returns the count of leading zeros in x.
* (total_bits - 1 - clz64(x)) gives the index of the highest set bit.
* Rounding that index down to an even number ensures our starting m is a
* power of 4.
*/
int shift = (total_bits - 1 - clz64(x)) & ~1;
m = 1ULL << shift;
while (m) {
uint64_t b = y + m;
y >>= 1;
if (x >= b) {
x -= b;
y += m;
}
m >>= 2;
}
// return y;
return y;
}
```
:::info
$N$的整數部分一定能使用二進位表示,因此假設$N$的整數部分為n,且:
$n = \sum^{k}_{i=1}2^{n_i}, \text{ for }n_i>n_{i+1} \quad \forall 1 \leq i < k$
$N$ (i.e.`x`) $\approx \sum^{k}_{i=1}2^{n_i} = ( 2^{n_1} + 2^{n_2} + \dots + 2^{n_k})^2$
:::
看完後可以推測出`m`為最高位的平方,`y`"可能"為中間項,會說可能就是因為`y`長得非常神奇,無法看出是如何得到中間項。可以想像從第一項開始,若`y`為 $2(2^{n_1})(2^{n_2} + \dots + 2^{n_k})$應該會是個遠大於此算出的值。
然而,他確實是平方和中間項的值。
但並不是 $2(2^{n_i})(2^{n_2} + \dots + 2^{n_k})$ ,讓我們觀察每次扣除時`y`的變化:
> m 的部分明顯為 $(2^{n_i})^2$,因此只針對 y 來做分析
1. `m` = $(2^{n_1})^2$ 第一次進入`if (x >= b)`,扣除的 y 為 0
2. `m` = $(2^{n_2})^2$ 第二次進入`if (x >= b)`,扣除的 y 為
$$ y = 2^{2n_1 - (n_1-n_2-1)}=2^{n_1+n_2+1} = 2(2^{n_1})(2^{n_2})$$
並且 y 更新為 $2^{n_1+n_2}+2^{2n_2}$
:::warning
- m 每次右移 2 bit,所以 m 從 $2^{2n_1}$ 右移成 $2^{2n_2}$ 時,迴圈總共跑了 $n_1 - n_2$ 次
- 並且,因為是更新 b 才右移 y,所以 b 中的 y 會比同個迴圈的 y 少更新一次,所以扣除的 y 從 $2n_1$ 右移了 $n_1 - n_2 -1$ 次
- 後續也是相同的概念
:::
3. `m` = $(2^{n_3})^2$ 第二次進入`if (x >= b)`,扣除的 y 為
$$y = \frac{2^{n_1+n_2}+2^{2n_2}}{2^{n_2-n_3-1}} = 2^{n_1+n_3+1}+2^{n_2+n_3+1} = 2(2^{n_3})(2^{n_1}+2^{n_2})$$
並且 y 更新為 $2^{n_1+n_3}+2^{n_2+n_3}+2^{2n_3}$
最後就能得到 $y = 2^{n_1} + 2^{n_2} + \dots + 2^{n_k}$
## More details
> 同樣假設 $\sqrt{N}$ 的整數部分為 $n = \sum^{k}_{i=1}2^{n_i}$
且 $N \approx (\sum^{k}_{i=1}2^{n_i})^2$
在此實作中,找到 $2^{n_i}$ 後的算法為:
- 扣除 $(2^{n_i})^2$
- 在找到 $2^{n_{i+1}}$ 時扣除 $2(2^{n_i})(2^{n_{i+1}})$
也就是說這不是根據
$$( 2^{n_1} + 2^{n_2} + \dots + 2^{n_k})^2 = (2^{n_1})^2 + 2(2^{n_1})(2^{n_2} + \dots + 2^{n_k}) +( 2^{n_2} + \dots + 2^{n_k})^2$$
來實現,而是用了類似像累積的方式,若將每次扣除的值條列出來如下:
| m | b = y + m | y (after shifted) |
| -------- | -------- | -------- |
| $(2^{n_1})^2$ | $0+(2^{n_1})^2$ | 0 |
| $(2^{n_2})^2$ | $2(2^{n_1})(2^{n_2})+(2^{n_2})^2$ | $2^{n_1+n_2}$ |
| $(2^{n_3})^2$ | $2(2^{n_1}+2^{n_2})(2^{n_3})+(2^{n_3})^2$ | $2^{n_1+n_3}+2^{n_2+n_3}$ |
| $(2^{n_4})^2$ | $2(2^{n_1}+2^{n_2}+2^{n_3})(2^{n_4})+(2^{n_4})^2$ | $2^{n_1+n_4} + 2^{n_2+n_4} + 2^{n_3+n_4}$ |
乍看之下,扣除的數就像在累積成下一個平方和一樣,只不過這是用減的
事實上也是如此,每階段總扣除如下:
| m | $\sum{b}$ |
| -------- | -------- |
| $(2^{n_1})^2$ | $(2^{n_1})^2$ |
| $(2^{n_2})^2$ | $(2^{n_1} + 2^{n_2})^2$ |
| $(2^{n_3})^2$ | $(2^{n_1} + 2^{n_2} + 2^{n_3})^2$ |
| $(2^{n_4})^2$ | $(2^{n_1} + 2^{n_2} + 2^{n_3}+ 2^{n_4})^2$ |
---
最後,腦中出現了個問題:
若在迴圈中每次扣除的數不是像 $(2^{n_1})^2 +$ ==$2(2^{n_1})(2^{n_2} + \dots + 2^{n_k})$== 來保證 $N$ 在扣除後剩餘的數為 $2^{n_2}+2^{n_3} + \dots + 2^{n_k}$,那會不會發生**存在某數 $t, \quad n_1 > t > n_2$ 且在`m`還未右移成 $(2^{n_2})^2$ 時進入 if 條件式?** 也就是說 $m$ 在 $(2^{n_1})^2$ 右移成 $(2^{n_2})^2$ 前,在 $m = (2^{t})^2$ 時,進入了 if 條件式?
不會。
因為 b = y + m,儘管過程中可能出現 x > m 的情形,但還有 y 這個變數在把關,y 在右移成相對應的數時,過程中仍是很大的數,所以過程中不會出現此情形進入條件式,此部分就不再詳加證明。
這就是個屌方法。