开平方算法
开平方算法的算法有很多,最简单的莫过于穷举法,例如在精度x下,每次增加一个x,直到逼近被开方数,还有二分法等。
以下谈论两种比较有实际意义的算法。
牛顿迭代法
inline float Newton(float n)
{
float x1 = n;
float x2 = n / 2;
while (fabs(x1 - x2) > 0.000001)//精度
{
x1 = x2;
x2 = (x1 + n / x1) / 2;
}
return x1;
}
牛顿法迭代法是二次收敛的,通过它我们可以得到一个近似解,可以自行提高计算精确。其实在实际生活中,只要精度足够用就好了。
结果检验:
cout << "Newton(2)\t"<<Newton(2)<<endl;
cout << "sqrt(2)\t"<<sqrt(2);
sqrt(2) 1.414214
Newton(2) 1.414214
可见精度还是相当高了
平方根倒数算法
inline float qrsqrt(float n)
{
long i;
float x, y;
x = n /2;
y = n;
i = *(long *)&y;
i = 0x5f3759df - (i >> 1);
y = *(float *)&i;
y = y * (1.5 - (x * y * y));
return y;
}
其中:
i = *(long *)&y 把浮点类型数强转为一个整形数字的二进制表示
i = 0x5f3759df - (i >> 1) 这一步不需要太明白0x5f3759df是什么,理解为到得到一个近似的整型解就可以了,它并没有提高迭代收敛速度,而是得到了一个优秀的迭代初值点。
y = *(float *)&i 这是 i = *(long *)&y的逆操作,将上一部的到的整型近似解转为
浮点型
y = y * (1.5 - (x * y * y)) 最后执行一次牛顿迭代,提高进度,这一步可以重复多次来提高精度
它的优点在于计算速度快,速度快了必然精度就差了,精度要想提高就必须增加迭代次数,迭代次数增加计算速度就的降低
结果检验:
cout << "1/qrsqrt(2)\t"<< 1/qrsqrt(2)<<endl;
1/qrsqrt(2) 1.41457
显然精度略低
当增加执行一次牛顿迭代 y = y * (1.5 - (x * y * y)) 后
1/qrsqrt(2) 1.414214
可见牛顿迭代是提高精度的核心
效率比较
#include "stdafx.h"
#include<iostream>
#include<time.h>
using namespace std;
int main() {
int start, end;
int i = 0;
start = clock(); //计时开始
cout << Q_rsqrt(4) << endl;
//在精度相同的情况下测试
for (long i = 0; i < 100000000; i++) {
sqrt(i);//end- start:1869ms
//1/qrsqrt(i);//end- start:4477ms
//Newton(i);//end- start:88329ms
}
end = clock(); //计时结束
cout<<"\end- start:%dms\n"<<(end - start);
}
不难看出Newton迭代法要低出一个数量级 ,qrsqrt算法得益于它优先找到了一个近似解,减少了不必要的迭代过程,如果Newton迭代法能找到一个优异的初值点的话收敛速度也会和qrsqrt算法起鼓相当。
当然,在原生函数 sqrt 高效的计算速度和超高精度的光芒下,一切都会黯然失色。
_Check_return_ inline float sqrt(_In_ float _Xx) _NOEXCEPT
{
return (_CSTD sqrtf(_Xx));
}
很无奈找不到sqrt的本尊,无法窥探究竟,我想可能是它有着更底层的实现或者是编译器的支持吧。