Try   HackMD

2022q1 Homework3 (quiz3)

contribute by < kdnvt >

測驗 11

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

直式開方法

雖然教授問答的時候是從二進位的角度去想,但我第一次看到類似的原理其實是從十進位的直式開方法。不過直式開方法所有進位都適用,所以從十進位的想法轉成二進位也沒有很難。
直式開方法是把要開方的數拆成二的冪相加(如果是十進位就是十的冪),就可以拆成以下的形式:

x=(000b0b1b2bn1bn)2

上面的

000b0b1b2bn1bn 為 bits pattern 。
b0
為最高位的 1 ,
寫成數學式如下:

x=(2nb0+2n1b1++21bn1+20bn)2

ai=2nibi

x=(a0+a1++an1+an)2=a02+2a0a1+a12+2(a0+a1)a2+a22++an12+2(i=0n1ai)an+an2=a02+(2a0+a1)a1+[2(a0+a1)+a2]a2++[2(i=0n1ai)+an]an

最後的形式為

[2(i=0n1ai)+an]an 的相加,而這每一項其實就是程式中每一輪迴圈

    if (x >= b) {
        x -= b;
        y += m;
    }

要判斷並可能減掉的 b

以下大致講解原理:
迴圈從 0 開始。

yi 為每個第
i
個迴圈一開始的 y 值。
mi
為每個第
i
個迴圈一開始的 m 值。
li
mi

y0=0

mi=li2

li=2ni

因為確定

a0 是最高位不為零,所以
a0=l0

y1=m0=l02=a02=l0a0

而每經過一輪

m 會右移兩位,他的平方根
l
就等於右移一位:
mi=mi+1×4

li=li+1×2

y1=l0a0
y1=2l1a0

y1+m1=2l1a0+l12=(2a0+l1)l1

如果此時
y1+m1
的結果小於當前的
x
,代表
x
的展開式中
(2a0+a1)a1
這項不為零,也就是
a1=2n1=l1
。將
y1
右移一位後加上
m1
,就會變成
y2=y12+m1=l1a0+l12=l1(a0+a1)=2×l2(a0+a1)

而如果此時

y1+m1 的結果大於當前的 x ,代表
x
的展開式中
(2a0+a1)a1
這項等於零,也就是
a1=0
。將
y1
右移一位後就會變成
y2=y12=l1a0=l1(a0+a1)=2×l2(a0+a1)

照同樣的原理推下去可以發現,

yj 其實就等於
2(i=0j1ai)lj
。因為不是所有的
ljaj
,所以我用另一個變數
zj=2(i=0j1ai)aj
來表達原式中的部份,
zj=yjajlj
,如果
aj=lj
也就是
aj0
,則
zj
會等於
yj
而且下圖的
ai2
會等於
mi

a02z0+a02+(2a0+a1)a1z1+a12+[2(a0+a1)+a2]a2z2+a22++[2(i=0n1ai)+an]anzn+an2

zi+ai2={0,if ai=0yi+mi,if ai=li

每一輪迴圈就是判斷

x 有沒有
zi+ai2
並更新
yi

到了最後的

yn=2(i=0n1ai)ln=2(i=0n1ai)20=2(i=0n1ai)
yn
會先往右移變成
i=0n1ai
,在加上最後的
an
,變成
i=0nai
,就是我們要求的平方根。