Try   HackMD

2024q1 Homework3 (quiz3+4)

contributed by < devarajabc >

第三週測驗 1-1

1. 解釋上述程式碼運作原理,並嘗試用第 2 週測驗題提到的 ffs / fls 取代 __builtin_clz,使程式不依賴 GNU extension。

參考 wikipediaquiz3浮點數運算和定點數操作

解釋程式碼運作原理

在解釋程式碼之前我們要先思考如何計算 x 的平方根?
可以分成以下幾個步驟:

1.

假設 x 的平方根是 N ,則 x=N2
而其又可再拆解成:

N2=(an+an1+an2+...+a0)2,am=2m or am=0

利用乘法公式可將 (an+an1+an2+...+a0)2 拆解成:
an2+[2an+an1]an1+[2(an+an1)+an2]an2+...+[2(i=1nai)+a0]a0

2.

為了方便計算,使用 Pm 來表示 (i=mnai) ,也就是 Pm=an+an1+...+am ,不難發現 P0=an+an1+...+a0 即為平方根 N

至於要如何得到 P0 呢? 可以將 Pm 寫成以下遞迴式:
Pm=Pm+1+am 

接著利用 Pm2N2 的特性來決定 am=2m 還是 am=0

將遞迴式改寫成:

{Pm=Pm+1+2m,if Pm2N2Pm=Pm+1,otherwise
遞迴的初始條件如下:

Pn=an=2nn 為能使 an2=(2n)24nN2 的最大值。

3.

若在每次遞迴的過程中都要計算 N2Pm2 的值會消耗大量的計算資源,因改以變數 Xm 來記錄N2Pm2 的值,並以遞迴的方式計算差值 Xm ,遞迴式如下:

Xm=N2Pm2=Xm+1Ym

其中:

Ym=Pm2Pm+12=2Pm+1am+am2
為了方便計算,將 Ym 分別以 cm=2Pm+1amdm=am2 來表示:

Ym={cm+dmif am=2m0if am=0

cm,dm 稍作整理之後可以得到:

  • cm=Pm+12m+1
  • dm=(2m)2

cm,dm 可以透過下列遞迴式求得:

  • cm1=(Pm+1+am)2m=Pm+12m+am2m={cm/2+dmif am=2mcm/2if am=0
  • dm1=dm4
4.

這個時候 有 趣 的 事 情 發生了,當 m=1 時:

c1=P020=P0=N

竟然透過計算 cm 就可以得到所求的平方根 N ,連 Ym,Xm 之類的都不用管了嗎? 當然不是,遞迴式需要透過 Xm 的值來判斷 am=0 或是 am=2m ,而 Xm 的值需要透過 Ym 來計算。

5.

接著就是如何透過程式碼執行以下遞迴式:

  • cm1={cm/2+dmif am=2mcm/2if am=0
  • dm1=dm4

初始條件改為:

{cn=0dn=2n

n 為能使 an2=(2n)2=dn2=4nN2 的最大值,
此時 cn0 , 為什麼呢?
由於 Pn=an=2nPm=Pm+1+2m
Pn+1=0, cn=0

6.

程式運作的流程如下:

  1. 利用變數 x 來儲存並計算 Xm+1=N2Pm+12=N2
  2. 利用變數 m = 1UL << ((31 - __builtin_clz(x)) & ~1UL) 來記錄 dn=2n
  3. 利用 dm1=dm4 來更新 m
  4. 利用變數 b 來計算 Ym ,也就是 cm+dm
  5. 計算 cm1=cm2
  6. 如果 Xm > Ym ,代表 am 不為零
  • 利用 Xm=Xm+1Ym 更新 Xm 的值
  • cm1 再加上 dm
int i_sqrt(int x)
{
    if (x <= 1) /* Assume x is always positive */
        return x;

    int z = 0;//c_n
    for (int m = 1UL << ((31 - __builtin_clz(x)) & ~1UL); m; m >>= 2) {
        int b = z + m;//b = y_m, m = d_m
        z >>= 1;
        if (x >= b)// x = x_n
            x -= b, z += m;               
    }
    return z;
}
sqrt_(26) = 5 // 正確答案應為 5.09901951359.....

測試完之後發現問題很大,在計算非完全平方數的平方根時會出現錯誤答案,
錯誤的原因不難理解,因為在程式的運算過程皆以整數 (int) 形態來儲存變數,為了讓輸出更接近真實答案,我打算使用定點數的技巧來增加精準度。

修改

試用 ffs / fls 取代 __builtin_clz

參考 ffs :
The ffs() function returns the position of the first (least
significant) bit set in the word i. The least significant bit is
position 1 and the most significant position is, for example, 32
or 64.

2. 在 Linux 核心原始程式碼找出對整數進行平方根運算的程式碼,並解說其應用案例,至少該包含 block 目錄的程式碼。

3. 閱讀〈Analysis of the lookup table size for square-rooting〉和 Timing Square Root,嘗試撰寫更快的整數開平分根運算程式。

quiz-4