在實作前先介紹何謂二進位逼近。
先看以下範例,我們利用此法求出
以上方法是從最高位元逐步向最低位元遞增逼近目標數值。
同樣地,我們也可以透過扣除的方式來逼近。接下來將說明如何使用此方法。
我們都知道簡單的平方和公式:
若套用在二進位並多加一位:
因此可以透過類似遞迴逐漸減去最高位及中間項,再重複利用同樣的方法得到下一位,這便是程式實作的核心想法。
程式實作如下
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;
}
x
)
看完後可以推測出m
為最高位的平方,y
"可能"為中間項,會說可能就是因為y
長得非常神奇,無法看出是如何得到中間項。可以想像從第一項開始,若y
為
然而,他確實是平方和中間項的值。
但並不是 y
的變化:
m 的部分明顯為
,因此只針對 y 來做分析
m
= if (x >= b)
,扣除的 y 為 0m
= if (x >= b)
,扣除的 y 為m
= if (x >= b)
,扣除的 y 為最後就能得到
同樣假設
的整數部分為
且
在此實作中,找到
也就是說這不是根據
來實現,而是用了類似像累積的方式,若將每次扣除的值條列出來如下:
m | b = y + m | y (after shifted) |
---|---|---|
0 | ||
乍看之下,扣除的數就像在累積成下一個平方和一樣,只不過這是用減的
事實上也是如此,每階段總扣除如下:
m | |
---|---|
最後,腦中出現了個問題:
若在迴圈中每次扣除的數不是像 m
還未右移成
不會。
因為 b = y + m,儘管過程中可能出現 x > m 的情形,但還有 y 這個變數在把關,y 在右移成相對應的數時,過程中仍是很大的數,所以過程中不會出現此情形進入條件式,此部分就不再詳加證明。
這就是個屌方法。