求解 是我们会常常遇到的,c++里一个sqrt(x)就解决了。这个值怎么得到的,倒是个有趣的问题。比较容易想到的是把这个问题转换为 ,接着就一个个试了……有人会说妈的智障,好吧,怪我没讲清楚,试要试得有水平,不能瞎试吧。其中,二分法就是一个试的有水平的方法,二分法简而言之就是找有可能范围中的中间的那个数来试。下面会谈到二分法、牛顿迭代法和一种神奇的方法 。
二分法
以 为例,可能的范围是[0, 10],取中间的值5,试下乘法口诀表五五二十五,大了。可能的范围的就缩小为了[0, 5],接着取2.5,小了,取[2.5,5]……又有人说了,这得试到什么时候啊,这里就需要设定一个误差err,当 时就认为算对了。
c++代码如下:
float dichotomyMethod(float n, float err)
{
float bigNum = n;
float smallNum = 0;
float midNum = 0;
float errTemp = 0;
do
{
if(errTemp > 0)
bigNum = midNum;
else
smallNum = midNum;
midNum = (bigNum + smallNum) / 2.0;
errTemp = midNum * midNum - n;
}while(fabs(errTemp) > err);
return midNum;
}
牛顿迭代法
之前一直不知道这种方法,直到上了一门《数值分析》的课。当然是考试前复习的时候才直到的,O(╯□╰)O 。这种方法能够花比较少的次数接近结果,在单根附近有平方收敛。
对于来说可以通过 来迭代求解,直到达到精度的要求。
上面的式子很简单,几何上的意义是点Xn处的切线与x轴得到的交点就是 Xn+1。下面这张动图能比较好说明。
代数上是对 f(x) 进行泰勒展开,并取前两项。,由于 X0是零点,第一项则为0,即 ,和之前提到的迭代表达式一致。
c++代码如下:
float newtonIterationMethod(float n, float err)
{
float x = n;
float temp = x;
do
{
temp = x;
x = (x + n / x) / 2.0;
}while(fabs(x * x - n) > err);
return x;
}
对比下以上两种方法所花的时间。计算1000次总时间的对比:
- 二分法 625us
- 牛顿迭代法 193us
牛顿迭代法胜出,差距还是比较明显。
神奇方法
在博客上看到一种很神奇的方法,是来自游戏Quake III的源代码,求得是。代码如下:
float InvSqrt (float x)
{
float xhalf = 0.5f*x;
int i = *(int*)&x;
i = 0x5f3759df - (i>>1);
float y = *(float*)&i;
y = y*(1.5f - xhalf*y*y);
return y;
}
对于游戏来说,速度比精度重要,不像科学计算那样。这种方法做了一些近似,精度不一定高,但是速度快。可以看到,这方法仅仅用了一句i = 0x5f3759df - (i>>1);
就完成了计算,后面加了牛顿迭代提高精度(想得到更高的精度可以多迭代几次)。
具体原理可以看上面提高的博客,反正我是没看懂……站在数学制高点的人编程真的就是开挂啊,喂。
三种方法加自带函数的时间对比(1000次,只是宏观上的对比,编译器可能会有优化):
- 二分法 600us
- 牛顿迭代法 150us
- 神奇方法 26us
- 自带sqrt 19us
有趣的是,据说提到的这种神奇的方法在10几年前比自带的sqrt函数快4倍左右,现在编译器各种优化加硬件上的变化,也许也有算法上的改变,使得自带的sqrt函数变快了。一个sqrt都能玩的这么溜,也是服了。
csdn博客mardwon模式不能渲染Latex公式真的烦!
- 我的github上博客:http://zealerww.github.io/
- github主页:https://github.com/zealerww