在科學運算、圖形學、遊戲等不少領域中,開方是很常見卻又很是耗時的運算,所以必須使用快速(有時還要求準確)的開方算法。算法
提及開方算法咱們通常想到的是牛頓迭代法,這裏我介紹一種更好的方法——逐比特確認法。網站
逐比特確認法從數字的本質出發,關注結果的每一比特位。它從最高位開始,向低位逐一確認某位是0仍是1。在數字很大時這種方法的速度比牛頓法快很多。spa
要理解這種方法,得先了解二進制乘法。例如,對於數字10(二進制爲0b1010
),平方爲100(二進制爲1100100
),它的二進制平方運算過程爲:code
1010 X 1010 ___________ 1010x1000 + 1010x 10 =========== 1000x1000 (*1) + 10x1000 (*2) + 1000x 10 (*3) + 10x 10 (*4) =========== 1000000 + 10000 + 10000 + 100 =========== = 1100100
開方則須要咱們反過來,已經有結果N = 1100100
,判斷根sqrt
的二進制:blog
首先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)這項get
1000 X 1000 ========== 1000x1000 (*1) ========== = 1000000 (n4) < 1100100 (N)
結果 n4 < N
。容易判斷,第4位必定爲1。否則乘不出N
這麼大的數。class
如今判斷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 }
此文章首發於個人我的網站:三種高效的整數開平方算法