牛顿迭代法和二分法都是手撸开根比较常见的方法,原以为Java sqrt的源码是用牛顿迭代法实现的,结果看了一下发现不是牛顿迭代法。
Java sqrt方法是一个native方法,需要下载openjdk源码包进行查看
官网下载/提取码:p9qsopenjdk8提取码:p9qspan.baidu.com
sqrt代码路径为:openjdk\jdk\src\share\native\java\lang\fdlibm\src\e_sqrt.c
或者戳下面这个链接在线查看代码http://www.netlib.org/fdlibm/e_sqrt.cwww.netlib.org
先解释一下牛顿迭代法:
求解方程的近似根时,先选取一个近似值作为初始值,然后不断取函数在这个点的切线与x轴的交点作为新的迭代值,最终迭代值会收敛至零点处。牛顿迭代法
牛顿迭代公式:
求解一个数的算术平方根例如
,这个问题可以转化成求解
的正根,将 f(x)代入迭代公式即可求出求解算术平方根的牛顿迭代公式:例如求解
先选取一个数作为迭代初始值,这里选取36/2的值也就是18作为初始值
则迭代过程为:18->10->6.8->6.047->...
然而Java sqrt源码中并不是用这个方法实现的。
Java sqrt源码中给出的方法描述为:Bit by bit method using integer arithmetic. (Slow, but portable)
逐位使用数值运算的方法,更慢但是更合适(泛化和鲁棒)
具体实现上也给出了解释:
1、标准化
假如现在要求
,那么通过2的偶数次幂,将x映射到[1,4)的范围内,使其满足下式:
则求
可以转化为求
2、逐位计算
设
为最终要求的
,
从
逐二进制位计算迭代而来,初始化
为1,因为y的范围是[1,4),所以
的范围是[1,2)。迭代方法如下:判断
是否成立;
如果成立则
;
否则
举个例子便于理解,假设现在要求
则根据第一步得到:
迭代: ,初始化
,
,
,
······
最终
,
可以看出这种方法不同于牛顿迭代法,它将带求解的数映射于[1,4)范围内,通过逐位计算,逐步缩小解的精度,逼近结果。
代码注释中还给出了一个优化方法,因为以上迭代过程涉及到平方的操作,为了优化这一点,可以将
其中
,迭代过程转化为:判断
是否成立;
如果成立则
;
否则
由此消除平方操作
可以知道这种逐位计算的方法求解收敛速度某些时候或许比不上牛顿迭代法,但避免了许多乘法和除法操作,所以鲁棒性很好。
此外,代码注释中还介绍了另一种相比牛顿迭代法而言不需要除法运算的方法,倒根迭代(自己乱翻译的)。将牛顿迭代法中的迭代值换成原来的倒数进行迭代。但是增加了许多乘法的操作。
论文链接:Square Root Without Divisionpeople.eecs.berkeley.edu
源码处理的double数据有52位有效位,在处理时将其分成了高位和低位分开处理,涉及到许多位运算,代码可读性不高,所以不详细讨论。