contribute by < 93i7xo2
>
11
延伸問題:
本題為 Leetcode 69. Sqrt(x),常見作法是以 binary search 在範圍內逐一試誤。先列出要介紹的幾種方法在 Leetcode 的執行時間:
Method | Runtime | Memory |
---|---|---|
Brute-force | 58 ms | 5.4 MB |
Fast Integer Square Root | 3 ms | 5.5 MB |
Digit-by-digit calculation | 3 ms | 5.8 MB |
Newton's Method | 0 ms | 5.7 MB |
IEEE754 + Binary search | 6 ms | 5.4 MB |
Fast Inverse Square Root | - | - |
第一種方法雖然是暴力解,但試著縮小範圍,減少猜測的次數。例如 2 進位的 2 位數平方不會超過 4 位數;試著想 1 個 2 位數和 1 個 3 位數 0b100
相乘結果為 4 位數,因此 2 位數相乘不會超出 4 位數,同理反推小於 n 位數的開根號數會是 位。現在假設 為 n 位數,我們可大幅縮小試誤範圍為 :
第二種為實現 Microchip - Fast Integer Square Root 演算法。該方法是將每一 bit 逐一試驗,控制時間複雜度在 ,從 2 進位的角度來看也是一種 binary search:
第三種方法(測驗 11
)為 Digit-by-digit calculation,也就是常見的直式開方法,且可在 math/int_sqrt.c 找到原始碼。
fls
功能為取得 x 的位數再減去 1,其實可以替換成(63 - __builtin_clzll(x))
,但__builtin_clzll
並未對 0 進行定義,需在行 5 判斷:
原理是將欲求平方根的 拆成:
將其乘開得到
分成幾個部份觀察
原式可整理成
即為所求平方根
很明顯 。我們的目的是從試出所有 , or 。從 一路試到 ,而求得 只要試驗 是否成立,兩種可能性:
但總不可能每輪去計算 去試驗 ,成本太高。一種想法是從上一輪的差值 減去 得到
也就是紀錄上一輪的 來計算
更進一步最佳化,將 拆成
藉由位元運算從 推出下一輪
結合上述方法撰寫演算法來尋求,假設從 開始往下試,至於如何求得 後面再說明。因為是從 開始,所以:
我自己的理解是要猜出最大的 使得 。
最大為 [2n+1, 2n+2] bits,如果用 去試有 2n+1 bits,是能試出 的。 有 2n+3 bits 就會超過 。
因為 恆大於零,使用位移操作 到最後一定為零,我們可以使用 d!=0
作為條件。又 ,算到最後的 即為所求 , 也一併知曉
對比 quiz 11
,除了變數名稱和使用 fls()
來尋找 ,和上述演算法是一致的。
上述是針對倒數開根號所開發的演算法,因此,還需要將回傳值乘上自己以取得開根號
參考 Babylonian method,此法等同用牛頓法解 求 ,Uniswap v2 應用在 Math 函式庫。
撇除第 1 種暴力解對各種演算法進行測試:
Algorithm | Use division/multiplication | Use float/double | Details |
---|---|---|---|
Fast Integer Square Root |
Image Not Showing
Possible Reasons
|
執行時間與數值位數成正比 | |
Digit-by-digit Calculation | 則因為初始 d 值與數值位數相關,與執行時間成正比,又因 d 每次以 2 位元位移且避開乘法,理應耗時低於 Fast Integer Square Root。 | ||
IEEE754 + Binary Search | 一如預期的在較大的數值範圍執行時間較長 | ||
Fast Inverse Square Root |
Image Not Showing
Possible Reasons
|
Image Not Showing
Possible Reasons
|
以常數時間勝出。 |
Babylonian method |
Image Not Showing
Possible Reasons
|
如果因為硬體限制可用 Digit-by-digit Calculation 或是 IEEE754 + Binary Search。如果沒有硬體限制且不在意精度可用 Fast Inverse Square Root。