问题描述&背景
描述
给定一个大整数(十进制下可能长达数百位) N N N,并保证其是某个整数的平方,如何精确又快速的求出其平方根?
背景
最近正在学习密码学相关的一些问题,遇到了一个题目,其中需要对几个巨大的整数(密码学日常 -_-)开平方根。遇到巨大的整数,自然想Python大法,毕竟对大整数的支持确实很棒,几乎无限位数的加法与乘法,运算速度也是巨快。没想到在给大整数开平方根这个看似轻巧的问题上卡住了,于是经过一番搜索,找到了一个基于二分思想、非常快速的逐比特验证方法,在这里和大家总结分享一下。
逐比特校验开平方根算法
参考来源
这篇文章比较全面总结了整型开根的各算法,包括精确解与近似的迭代法等:点我查看
二分思想
对大数做平方根时,如果直接做开根,大部分语言都会将其视作浮点型,从而带来误差,无法得到精确解;要想直接对大整型开根也是相当费时的操作。为了精确解,便想到通过循环迭代不断试错,找到精确的那个解,其平方等于 N N N,因为平方操作本质即乘法,是比较快速且准确的。
暴力迭代循环的次数过多,时间上一般不可忍受,于是可采用二分查找,显著减少搜索时间。
更进一步-比特法校验
二分查找仍然需要包含许多平方后比较的操作,运行速度仍然不尽人意。我们可以换一个思路:对于一个整数进行开平方根的操作,本质就是根据一个写为二进制后长 l l l位的整数 N N N,构造一个二进制长为 ⌈ l 2 ⌉ \lceil \frac{l}{2} \rceil ⌈2l⌉位的整数 s q r t N sqrtN sqrtN,从高位到低位逐个比特验证每一位是0还是1。
bit- k k k | k 15 k_{15} k15 | k 14 k_{14} k14 | k 13 k_{13} k13 | k 12 k_{12} k12 | k 11 k_{11} k11 | k 10 k_{10} k10 | k 9 k_9 k9 | k 8 k_8 k8 | k 7 k_7 k7 | k 6 k_6 k6 | k 5 k_5 k5 | k 4 k_4 k4 | k 3 k_3 k3 | k 2 k_2 k2 | k 1 k_1 k1 | k 0 k_0 k0 | Dec |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
N N N | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 43681 |
s q r t N sqrtN sqrtN | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 1 | 209 |
引言1:注意到假设 s q r t N sqrtN sqrtN的二进制长为 k k k位,则其最小可能的数的平方为 N = ( 2 k − 1 ) 2 = 2 2 k − 2 N = (2^{k-1})^2 = 2^{2k-2} N=(2k−1)2=22k−2,长为 2 k − 1 2k-1 2k−1位;最大可表示的数的平方为 N = ( 2 k − 1 ) 2 = 2 2 k − 2 k + 1 + 1 N = (2^k - 1)^2 = 2^{2k} - 2^{k+1} + 1 N=(2k−1)2=22k−2k+1+1,长为 2 k + 1 2k+1 2k+1位。故若输入 N N N的二进制长为 l l l,那么其平方根的二进制长最多为 ⌈ l 2 ⌉ \lceil \frac{l}{2} \rceil ⌈2l⌉位。所以我们只需判断末尾 ⌈ l 2 ⌉ \lceil \frac{l}{2} \rceil ⌈2