目錄
問題描述&背景
描述
給定一個大整數(十進制下可能長達數百位) ,並保證其是某個整數的平方,如何精確又快速的求出其平方根?
背景
最近正在學習密碼學相關的一些問題,遇到了一個題目,其中需要對幾個巨大的整數(密碼學日常 -_-)開平方根。遇到巨大的整數,自然想Python大法,畢竟對大整數的支持確實很棒,幾乎無限位數的加法與乘法,運算速度也是巨快。沒想到在給大整數開平方根這個看似輕巧的問題上卡住了,於是經過一番搜索,找到了一個基於二分思想、非常快速的逐比特驗證方法,在這裏和大家總結分享一下。
逐比特校驗開平方根算法
參考來源
這篇文章比較全面總結了整型開根的各算法,包括精確解與近似的迭代法等:點我查看
二分思想
對大數做平方根時,如果直接做開根,大部分語言都會將其視作浮點型,從而帶來誤差,無法得到精確解;要想直接對大整型開根也是相當費時的操作。爲了精確解,便想到通過循環迭代不斷試錯,找到精確的那個解,其平方等於 ,因爲平方操作本質即乘法,是比較快速且準確的。
暴力迭代循環的次數過多,時間上一般不可忍受,於是可採用二分查找,顯著減少搜索時間。
更進一步-比特法校驗
二分查找仍然需要包含許多平方後比較的操作,運行速度仍然不盡人意。我們可以換一個思路:對於一個整數進行開平方根的操作,本質就是根據一個寫爲二進制後長 位的整數 ,構造一個二進制長爲 位的整數 ,從高位到低位逐個比特驗證每一位是0還是1。
bit- | Dec | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 43681 | |
1 | 1 | 0 | 1 | 0 | 0 | 0 | 1 | 209 |
引言1:注意到假設 的二進制長爲 位,則其最小可能的數的平方爲 ,長爲 位;最大可表示的數的平方爲 ,長爲 位。故若輸入 的二進制長爲 ,那麼其平方根的二進制長最多爲 位。所以我們只需判斷末尾 位即可。
引言2:假設 最終能表示成某一二進制數,其在 位有值,則其平方將表示成
算法過程
由於均是比特操作,有關2的次方和乘除的操作都可以通過左移操作實現,效率上會快很多。以上面的 爲例,我們來逐步看一下算法流程:
- 初始時,輸入
N
= 43681。sqrtN
爲0,shift
爲 = 8位。 - 查看所需看的最高位 ,設定一個
mask
= 100000000,即僅 位被置1的一個掩碼。假如sqrtN
的 位應該爲0的話,那麼N
必定不應包含 一項。換言之,mask << shift
一定超過了N
。假如sqrtN
的 位應該爲1的話,那麼mask << shift
一定N
。根據此來判斷得, 位應爲0。 shift
置爲7,對應mask
= 10000000。此時sqrtN
仍爲0,同上判斷知, 應爲1。更新sqrtN += mask
,並且N
要減去該項,此時N
僅留下了部分。shift
置爲6,對應mask
= 1000000。此時查看 位,因爲已知 應爲1,於是假如sqrtN
的 位應該爲0的話,那麼N
必定不應包含 一項。換言之,此時的(sqrtN
(即之前得到的 )* 2 + mask) << shift
一定超過了N
。假如sqrtN
的 位應該爲1的話,那麼該式一定N
。根據此來判斷得, 位應爲1。更新sqrtN += mask
,並且N
要減去該項,此時N
僅留下了部分。shift
置爲5,對應mask
= 100000。同樣得到 位應爲0。shift
置爲4,對應mask
= 10000。此時查看 位,因爲已知 應爲1,於是假如sqrtN
的 位應該爲0的話,那麼N
必定不應包含 一項。換言之,此時的(sqrtN
(即之前得到的 )* 2 + mask) << shift
一定超過了 。假如 的 位應該爲1的話,那麼該式一定N
。根據此來判斷得, 位應爲1。更新sqrtN += mask
,並且N
要減去該項,此時N
僅留下了部分。- 以此類推,直到 停止。
相信經過上面的敘述,大家已經瞭解了這一算法的精髓:以二進制比特的視角,充分運用移位操作的快速性。
算法實現和驗證
Python實現
Python代碼實現該算法如下,還是非常簡潔的。
## Bit-by-Bit Large Number Fast sqrt
from math import ceil, sqrt
# Fast bit-by-bit sqrt func
def bit_by_bit_sqrt(N):
# Initialize
shift = ceil(len(bin(N)) - 2)
mask = 1 << shift
sqrtN = 0
# Loop starting from MSB
while N > 0:
# Generate intermediate term
tmp = ((sqrtN << 1) + mask) << shift
# Just if sqrtN includes that bit
if tmp <= N:
sqrtN += mask # If so, update
N -= tmp # sqrtN and N
# Advance
shift -= 1
mask >>= 1
# Return result
return sqrtN
# Testing
if __name__ == '__main__':
N = 43681
sqrtN = bit_by_bit_sqrt(N)
print('original N: %d' % N)
print('calc sqrtN: %d' % sqrtN)
print('true sqrtN: %d' % sqrt(N))
驗證結果
original N: 43681
calc sqrtN: 209
true sqrtN: 209
如果大家有任何疑問或評論,歡迎在評論區留言~ 有關非平方數精確開方和非整型開方相關的算法,也歡迎補充~