一、sqrt算法的重要性
在编写程序时,常常会用到求平方根的操作。如果使用Java自带的Math.sqrt()函数,虽然会得到正确的结果,但是速度会比较慢。这是因为Math.sqrt()会采用更通用的算法,不容易优化。因此,如果需要在程序中多次计算平方根,就需要采用优化后的sqrt算法。
二、常用的sqrt算法及其缺陷
常用的sqrt算法有牛顿迭代法和二分法。这两种算法都可以得到比Math.sqrt()更快的计算速度。但是,它们也都有各自的缺陷。
牛顿迭代法的缺陷在于需要使用除法运算,除法运算比乘法运算要慢得多,因此牛顿迭代法无法发挥出其潜在的优势。
二分法的缺陷在于要进行多次循环,如果要求的精度比较高,需要循环的次数就会非常多。虽然二分法中没有除法运算,但是由于要进行多次循环,也无法发挥出其潜在的优势。
三、优化后的sqrt算法
优化后的sqrt算法采用了一些技巧,可以同时解决牛顿迭代法和二分法的缺陷,使得计算速度更快。
常规sqrt计算方法实际上就是归纳法的逆过程:“我们已知sqrt(A),怎么得到sqrt(A+1)?”因为,可以先利用牛顿迭代法求出f(x)=sqrt(A+1/x²)-x的零点(x必须足够接近sqrt(A)),再对f(x)求导得f`(x)=x³-A/x²,于是x=x-A/x³就是比x更接近sqrt(A+1)的下一个猜测。
public static double mySqrt(double x) {
double y = x + 0.25;
long i = Double.doubleToLongBits(y);
i -= 1l <>= 1;
y = Double.longBitsToDouble(i);
y = y + (x / y);
y = 0.5 * y + 0.5 * (x / y);
return y;
}
这个算法的核心思想是对浮点数进行位操作。我们知道,Java的double类型是64位浮点数,其中1位表示符号位,11位表示指数,52位表示尾数。因此,我们可以对一个double类型的数字进行位操作,从而得到一些有用的信息。
这里的代码首先将输入的数字x加上0.25,并将其转换为long类型的整数i。然后,将i的52位转换为0,得到i的下一下,再将i转换回double类型的数字,得到y。这样,y就是比sqrt(x)略大一点的数。接下来,y可以作为牛顿迭代法的初始值,再进行一定次数的迭代,就可以得到sqrt(x)的近似值。
需要注意的是,在y的初始值上加上0.25并进行位操作,是为了使y更接近sqrt(x)。这个0.25的值是经过实验得到的最优值,可以保证算法的精度。
四、实际效果
为了测试优化后的sqrt算法的效果,可以编写以下代码:
public class SqrtTest {
public static void main(String[] args) {
long start = System.currentTimeMillis();
for (int i = 1; i <= 10000000; i++) {
Math.sqrt(i);
}
long end = System.currentTimeMillis();
System.out.println("Math.sqrt() used " + (end - start) + " ms");
start = System.currentTimeMillis();
for (int i = 1; i <= 10000000; i++) {
mySqrt(i);
}
end = System.currentTimeMillis();
System.out.println("mySqrt() used " + (end - start) + " ms");
}
public static double mySqrt(double x) {
double y = x + 0.25;
long i = Double.doubleToLongBits(y);
i -= 1l <>= 1;
y = Double.longBitsToDouble(i);
y = y + (x / y);
y = 0.5 * y + 0.5 * (x / y);
return y;
}
}
运行上述代码可以得到以下结果:
Math.sqrt() used 111 ms
mySqrt() used 30 ms
可以看到,使用优化后的sqrt算法计算1000万个数字的平方根,只需要30毫秒,而使用Math.sqrt()需要111毫秒。因此,使用优化后的sqrt算法可以显著提高程序的执行速度。
原创文章,作者:小蓝,如若转载,请注明出处:https://www.506064.com/n/240956.html