Try   HackMD

How to use digital-by-digital implement sqrt() in C

二進位逼近

在實作前先介紹何謂二進位逼近。

先看以下範例,我們利用此法求出

13 的整數部分

  1. 13=23+22+20
    , 因此我們依以下假設使用
    (Sm)2
    來逼近
    13=(a323+a222+a121+a020)2=(i=03ai2i)2

Sm=(a323++am2m)=k=m3ak2k

  1. 由最大項
    m=3
    開始逼近
  • (S3)2=(a323)2,(23)2>13, therefore a3=0

  • (S2)2=(a323+a222)2=(a222)2,(22)2>13, therefore a2=0

  • (S1)2=(a323+a222+a111)2=(a121)2,(21)213, therefore a1=1

  • (S0)2=(a323+a222+a111+a020)2=(21+a020)2,(21+20)213, therefore a0=1

  1. 所以
    13
    的整數部分為
    (21+20)=3

換個角度想

以上方法是從最高位元逐步向最低位元遞增逼近目標數值。
同樣地,我們也可以透過扣除的方式來逼近。接下來將說明如何使用此方法。

我們都知道簡單的平方和公式:

(a+b)2=a2+2ab+b2

若套用在二進位並多加一位:

let n1>n2>n30

(2n1+2n2+2n3)2=(2n1+(2n2+2n3))2=(2n1)2+2(2n1)(2n2+2n3)+(2n2+2n3)2

因此可以透過類似遞迴逐漸減去最高位及中間項,再重複利用同樣的方法得到下一位,這便是程式實作的核心想法。

Impliment in 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;
}

N的整數部分一定能使用二進位表示,因此假設
N
的整數部分為n,且:
n=i=1k2ni, for ni>ni+11i<k

N
(i.e.x)
i=1k2ni=(2n1+2n2++2nk)2

看完後可以推測出m為最高位的平方,y"可能"為中間項,會說可能就是因為y長得非常神奇,無法看出是如何得到中間項。可以想像從第一項開始,若y

2(2n1)(2n2++2nk)應該會是個遠大於此算出的值。

然而,他確實是平方和中間項的值。

但並不是

2(2ni)(2n2++2nk) ,讓我們觀察每次扣除時y的變化:

m 的部分明顯為

(2ni)2,因此只針對 y 來做分析

  1. m =
    (2n1)2
    第一次進入if (x >= b),扣除的 y 為 0
  2. m =
    (2n2)2
    第二次進入if (x >= b),扣除的 y 為
    y=22n1(n1n21)=2n1+n2+1=2(2n1)(2n2)

    並且 y 更新為
    2n1+n2+22n2
  • m 每次右移 2 bit,所以 m 從
    22n1
    右移成
    22n2
    時,迴圈總共跑了
    n1n2
  • 並且,因為是更新 b 才右移 y,所以 b 中的 y 會比同個迴圈的 y 少更新一次,所以扣除的 y 從
    2n1
    右移了
    n1n21
  • 後續也是相同的概念
  1. m =
    (2n3)2
    第二次進入if (x >= b),扣除的 y 為
    y=2n1+n2+22n22n2n31=2n1+n3+1+2n2+n3+1=2(2n3)(2n1+2n2)

    並且 y 更新為
    2n1+n3+2n2+n3+22n3

最後就能得到

y=2n1+2n2++2nk

More details

同樣假設

N 的整數部分為
n=i=1k2ni

N(i=1k2ni)2

在此實作中,找到

2ni 後的算法為:

  • 扣除
    (2ni)2
  • 在找到
    2ni+1
    時扣除
    2(2ni)(2ni+1)

也就是說這不是根據

(2n1+2n2++2nk)2=(2n1)2+2(2n1)(2n2++2nk)+(2n2++2nk)2

來實現,而是用了類似像累積的方式,若將每次扣除的值條列出來如下:

m b = y + m y (after shifted)
(2n1)2
0+(2n1)2
0
(2n2)2
2(2n1)(2n2)+(2n2)2
2n1+n2
(2n3)2
2(2n1+2n2)(2n3)+(2n3)2
2n1+n3+2n2+n3
(2n4)2
2(2n1+2n2+2n3)(2n4)+(2n4)2
2n1+n4+2n2+n4+2n3+n4

乍看之下,扣除的數就像在累積成下一個平方和一樣,只不過這是用減的
事實上也是如此,每階段總扣除如下:

m
b
(2n1)2
(2n1)2
(2n2)2
(2n1+2n2)2
(2n3)2
(2n1+2n2+2n3)2
(2n4)2
(2n1+2n2+2n3+2n4)2

最後,腦中出現了個問題:

若在迴圈中每次扣除的數不是像

(2n1)2+
2(2n1)(2n2++2nk)
來保證
N
在扣除後剩餘的數為
2n2+2n3++2nk
,那會不會發生存在某數
t,n1>t>n2
且在m還未右移成
(2n2)2
時進入 if 條件式?
也就是說
m
(2n1)2
右移成
(2n2)2
前,在
m=(2t)2
時,進入了 if 條件式?

不會。

因為 b = y + m,儘管過程中可能出現 x > m 的情形,但還有 y 這個變數在把關,y 在右移成相對應的數時,過程中仍是很大的數,所以過程中不會出現此情形進入條件式,此部分就不再詳加證明。

這就是個屌方法。