Try   HackMD

2024q1 Homework4 (quiz3+4)

contributed by < ranvd >

第三周測驗 1

根據 Digit-by-digit calculation 的方法,一個數字

Z 可以表示為
(x+y)2
,假設以 10 為底的話
Z=(an++a0)2
其中
am=10m
0
,同樣的方式可以推廣至以 2 為底。如果將整個公式展開的可以表示成下面公式
Z=a02+[2a0+a1]a1+[2(a0+a1)+a2]a2++[2(i=0n1ai)+an]an

接著假設

Pm=(an++am);Z=P02,其中
ai
代表
2i
0
,因此可以列出遞迴關係式
Pm=Pm+1+am
。在
m
n0
的迭代過程中判斷
Pm2
是否大於
Z
即可判斷
am
是 0 或
2m
。但為了不要每次都重新做平方運算,因此定義
Xm=ZPm2
,並列出遞迴關係式
Xm=Xm+1Ym
,其中
Ym=Pm2Pm+12=2Pm+1am+am2

又可以將

Ym 拆解成
Ym={cm+dmif am=2m0if am=0
其中
cm=Pm+12m+1;dm=(2m)2
,從這裡可以得知,當
m=1
時,
c1=P020=P0
,因此
c1
z
,接著可以推導出
cm
dm
的遞迴關係式。
cm={cm+12+dmif am+1=2m+1cm+12if am+1=0
dm=dm+14

因此從上面可以看到

Xn=Xn+1Yn+1=Xncn+1+dn+1,其中
dn+1=4n+1
因此在初始化時須將其設為不超過 x 且為 4 的冪的數值。因此才會使用 int d = 1UL << ((31 - __builtin_clz(x)) & ~1UL) 做初始化。由於
Xm0
因此可以透過判斷
Xm+1Ym
來判斷
am+1=2m+1 or 0
,並根據上述的公式來更新數值。

int i_sqrt(int x)
{
    if (x <= 1) /* Assume x is always positive */
        return x;

    int c = 0;
    for (int d = 1UL << ((31 - __builtin_clz(x)) & ~1UL); d; d >>= 2) {
        int b = c + d; // Y_m
        c >>= 1;
        if (x >= b)
            x -= b, c += d;
    }
    return c;
}

使用 fls 取代 __builtin_clz

依據上述題到的內容,int d = 1UL << ((31 - __builtin_clz(x)) & ~1UL) 是要找出不大於 x 的最大 4 的冪。因此透過 fls 就可以正確的找出正確的數值,如下更動。

+    int d = 1UL << (fls(x)-1 & ~1UL)
-    int d = 1UL << ((31 - __builtin_clz(x)) & ~1UL)

第三周測驗 2

第三周測驗 3

透過判斷 i 是否大於

2n 來減少運算的次數,之所以
n
使用 16, 8 ,4, 1 的方式是使用了二分搜尋的概念,減少需判斷的次數。

Linux 應用案例

include/linux/log2.h 中有 log2 相關的實做。使用 fls 函式實做。

第三周測驗 4

第三周測驗 5

ceil_ilog2ilog2 的不同在於,ceil_ilog2

log2(x)ilog
log2(x)
。這導致 ceil_ilog2 不能直接使用 fls 找出最高為 1 的 bit。例如 ceil_ilog2(0b1000)=3ceil_ilog2(0b1001)=4

因此可以在取 log 之前先將數值減 1,這可以讓數值剛好是

2n 的資料取 log 後比原本還要小 1,但對於數值不等於
2n
的資料則不會有影響。因此在取完 log 後再無條件加 1 就可以得到
log2(x)

改進程式碼

參考 include/linux/log2.h 可以得知當 n == 0 時,ilog2 回傳的數值為 -1。因此以下改進也會遵循此規則。

static __always_inline __attribute__((const))
int __ilog2_u32(u32 n)
{
	return fls(n) - 1;
}

目前的實作有兩個缺陷,第一個是 n==0 時,函式的回傳值為 32,應該要為 -1;第二個是 n==1 時,函式的回傳值為 1,應該為 0。

根據上述的規則套用到現在的程式碼上,可以理解為當 n==1 時,最後回傳時不需要加 1;同時,因為 n==0 時必須回傳 -1,因此可以考慮讓 n==0 時的運算結果等於 1,接著再做二補數,也就是讓 0 直接取 log 後在加上 1,最後再進行反轉,即可得到 -1。

int ceil_ilog2(uint32_t x)
{
    uint32_t r, shift;
+    uint32_t t = x;
-    x--;
+    x -= !!t;
    r = (x > 0xFFFF) << 4;
    x >>= r;
    shift = (x > 0xFF) << 3;
    x >>= shift;
    r |= shift;
    shift = (x > 0xF) << 2;
    x >>= shift;
    r |= shift;
    shift = (x > 0x3) << 1;
    x >>= shift;
-    return (r | shift | x > 1) + 1;  
+    return (((r | shift | x > 1) + !!(t-1)) ^ -!t) + !t ;
}

Linux 應用案例

代補