一種高效整數開平方算法:逐比特確認法


在科學運算、圖形學、游戲等很多領域中,開方是很常見卻又非常耗時的運算,因此必須使用快速(有時還要求准確)的開方算法。

說起開方算法我們一般想到的是牛頓迭代法,這里我介紹一種更好的方法——逐比特確認法。

逐比特確認法從數字的本質出發,關注結果的每一比特位。它從最高位開始,向低位逐一確認某位是0還是1。在數字很大時這種方法的速度比牛頓法快不少。

要理解這種方法,得先了解二進制乘法。例如,對於數字10(二進制為0b1010),平方為100(二進制為1100100),它的二進制平方運算過程為:

 1010 X 1010 ___________ 1010x1000 + 1010x 10 =========== 1000x1000 (*1) + 10x1000 (*2) + 1000x 10 (*3) + 10x 10 (*4) =========== 1000000 + 10000 + 10000 + 100 =========== = 1100100

開方則需要我們反過來,已經有結果N = 1100100,判斷根sqrt的二進制:

首先1100100有8位,可以判斷sqrt起碼有4位且不超過4位。如果sqrt有5位,那么僅最高位10000*10000 = 1 0000 0000就已經大於N;如果sqrt只有3位,即使sqrt為111結果110001也不超過6位。

現在判斷sqrt的第4位:如果第4位為1 , sqrt平方運算中有上面(*1)這項

 1000 X 1000 ========== 1000x1000 (*1) ========== = 1000000 (n4) < 1100100 (N)

結果 n4 < N 。容易判斷,第4位一定為1。不然乘不出N這么大的數。

現在判斷sqrt的第3位:如果第3位為1,則sqrt為1100,它的平方為

 1100 X 1100 ========== 1000x1000 (*1) 100x1000 1000x 100 100x 100 ========== = 10010000 (n43) > 1100100 (N)

結果n43 > N,所以這一位不是1,只能是0。

到目前為止其實都是二分法的思路,先是2^3,然后是2^3 – (2^3 + 2^2),這樣逐次將范圍減半。

但是這里有個問題,后面每次都求了sqrt的平方,其實重復求了之前求過的一部分,例如在第二步中,我們算了 1000x1000(*1),這其實就是第一步中的算的。如果我們每次算完平方,確認了這一位為1后,就從N中減去這一部分的平方,那么下次比較大小的時候就可以少算這一位。

我們從第二步重新開始:

我們第一步確認了1000, 從N中減去它的平方 N = 1100100 - n4,結果為N = 1000100
如果第3位為1, 那么sqrt = 1100, 已確認的為1000, 正在確認的為100, 平方為:

 1100 X 1100 =========== (1000x1000)(已確認部分從N減去了,不計算) + 100x1000 (正在確認的*已確認的) + 1000x 100 (已確認的*正在確認的) + 100x 100 (正在確認的*正在確認的) =========== 2*(1000<<2) (1000*100 等於將1000左移2位) + 100<<2 =========== = 1010000 (n3) > 1000100 (N*)

和之前結果一樣, 大了,所以第3位為0. 因為是0, 所以沒必要從N*里減去.

現在判斷第2位: 如果為1 則sqrt = 1010.

 1010 X 1010 =========== (1000x1000)(已確認部分從N減去了,不計算) + 10x1000 (正在確認的*已確認的 = 將已確認部分前移1位) + 1000x 10 (已確認的*正在確認的 = 將已確認部分前移1位) + 10x 10 (正在確認的*正在確認的 = 將正在確認的前移1位) =========== 2*(1000<<1) + 10<<1 =========== = 1000100 (n2) = 1000100 (N*)

n2 = N*, 也就是說若這一位為1, sqrt就是N的根. 后面應該都是0,無需繼續判斷.

但我還想繼續探究, 繼續把N減去新確認的部分: N* = 1000100 – n2 = 0。

如果第1位為1,則sqrt= 1011, 平方運算為:

 1011 X 1011 =========== (1000x1000) (已確認部分從N減去了,不計算) ( 10x1000) (已確認部分從N減去了,不計算) (1000x 10) (已確認部分從N減去了,不計算) ( 10x 10 ) (已確認部分從N減去了,不計算) + 1x1010 (正在確認的*已確認的 = 將已確認部分前移0位) + 1010x 1 (已確認的*正在確認的 = 將已確認部分前移0位) + 1x 1 (正在確認的*正在確認的 = 將正在確認的前移0位) =========== 2*(1010<<0) + 1<<0 =========== = 10101 (n2) > 0 (N*)

所以這一位肯定只能為0. 最終結果為sqrt = 1010.

這就是逐比特確認法。

說了這么多,其實代碼很簡單:

 1 int sqrt_bv(int n)  2 {  3     int sqrt = 0;  4     int shift = 15;  5     int sqrt2;    //已確認部分的平方  6     while (shift >= 0)  7  {  8         sqrt2 = ((sqrt << 1) + (1 << shift)) << shift;  9         if (sqrt2 <= n) 10  { 11             sqrt += (1 << shift); 12             n -= sqrt2; 13  } 14         shift--; 15  } 16     return sqrt; 17 }

 

此文章首發於我的個人網站:三種高效的整數開平方算法

 




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM