开平方的快速算法(C程序)
(2015-02-04 11:48:29)
标签:
快速开平方 |
分类: 算法、协议、信号处理 |
目录:
算法一、采用移位、加减法、判断和循环实现
算法二、效率远高于牛顿迭代法
1、原理
2、实现代码
算法三、卡马克快速开平方算法
附录
-----------------------------------------------------------------------------------------------------------------
算法一
本算法只采用移位、加减法、判断和循环实现,因为它不需要浮点运算,也不需要乘除运算,因此可以很方便地运用到各种芯片上去。我们先来看看10进制下是如何手工计算开方的。先看下面两个算式:
x = 10*p +
q
公式(1)左右平方之后得:
x^2 =
100*p^2 + 20pq + q^2 (2)
现在假设我们知道x^2和p,希望求出q来,求出了q也就求出了x^2的开方x了。
我们把公式(2)改写为如下格式:
q = (x^2 -
100*p^2)/(20*p+q) (3)
这个算式左右都有q,因此无法直接计算出q来,因此手工的开方算法和手工除法算法一样有一步需要猜值。
我们来一个手工计算的例子:计算1234567890的开方
首先我们把这个数两位两位一组分开,计算出最高位为3。也就是(3)中的p,最下面一行的334为余数,也就是公式(3)中的(x^2
- 100*p^2)近似值
下面我们要找到一个0-9的数q使它最接近满足公式(3)。我们先把p乘以20写在334左边:
我们看到q为5时(60+q*q)的值最接近334,而且不超过334。于是我们得到:
接下来就是重复上面的步骤了,这里就不再啰嗦了。
这个手工算法其实和10进制关系不大,因此我们可以很容易的把它改为二进制,改为二进制之后,公式(3)就变成了:
q = (x^2 - 4*p^2)/(4*p+q)
(4)
我们来看一个例子,计算100(二进制1100100)的开方:
这里每一步不再是把p乘以20了,而是把p乘以4,也就是把p右移两位,而由于q的值只能为0或者1,所以我们只需要判断余数(x^2
-
4*p^2)和(4*p+1)的大小关系,如果余数大于等于(4*p+q)那么该上一个1,否则该上一个0。
下面给出完成的C语言程序,其中root表示p,rem表示每步计算之后的余数,divisor表示(4*p+1),通过a>>30取a的最高
2位,通过a<<=2将计算后的最高2位剔除。其中root的两次<<1相当于4*p。程序完全是按照手工计算改写的,应该不难理解。
unsigned short
sqrt(unsigned long a)
{
}
-----------------------------------------------------------------------------------------------------------------
算法二
因为工作的需要,要在单片机上实现开根号的操作。目前开平方的方法大部分是用牛顿迭代法。我在查了一些资料以后找到了一个比牛顿迭代法更加快速的方法。不敢独享,介绍给大家,希望会有些帮助。
1、原理
因为排版的原因,用pow(X,Y)表示X的Y次幂,用B[0],B[1],...,B[m-1]表示一个序列,其中[x]为下标。
假设:
(1)
N的最高位b[n-1]可以根据M的最高位B[m-1]直接求得。
(2) N的次高位b[n-2]可以采用试探法来确定。
(3)
同理,可以从高位到低位逐位求出M的平方根N的各位。
使用这种算法计算32位数的平方根时最多只须比较16次,而且每次比较时不必把M的各位逐一比较,尤其是开始时比较的位数很少,所以消耗的时间远低于牛顿迭代法。
-----------------------------------------------------------
2、实现代码
这里给出实现32位无符号整数开方得到16位无符号整数的C语言代码。
unsigned int
sqrt_16(unsigned long M)
{
}
-----------------------------------------------------------------------------------------------------------------
算法三、卡马克快速开平方算法
据说能比float sqrt(x)快4倍,在TC2.0下编译通过,计算结果准确度高。
float
SquareRootFloat(float number)
{
//
}
Quake
III中不可思议的求解平方根实现方法
任何一个3D引擎都是通过其内部的数学模型和实现工具来展现它的力量与速度的,and
trust John Carmack of ID software for using really good hacks.
结果,Quake III中使用了一个非常有意思的技巧来计算平方根倒数(inverse square
root)
前言
ID
software最近发布了它的带有Gpl许可证的Quake
III引擎源代码,在这篇文章中我们将会看到Carmark是怎样用他的black
magic来极其迅速地计算一个浮点数的平方根的。
Carmack's
不寻常平方根倒数
对文件game/code/q_math.c的快速一瞥就显示出了许多有趣的performance
hacks。
第一个跳出来的便是对函数Q_rsqrt中对0x5f3759df的使用,这个数计算了一个浮点数的inverse
square root,但是为什么这个函数有这样的功能呢?
观察q_math.c原本的函数:
float Q_rsqrt( float number
)
{
}
br/>它不仅有效,甚至在某些CPU上,Carmack的Q_rsqrt
比(float)(1.0/sqrt(x)的计算快4倍,尽管sqrt()通常使用的是FSQRT的汇编指令!
在另一个文件code/common/cm_trace.c 中,我们发现了更简洁的对同样HACK的实现。这一次,它被用来计算一个float - sqrt(x)的平方根。注意,其中的唯一不同是在返回值上--用返回*y取代了返回y。
float SquareRootFloat(float
number)
{
}
牛顿对根的近似值
上面的代码执行了众所周知的牛顿对根的近似值[3],像绝大多数其它迭代求近似值的计算一样,牛顿近似值假定是迭代的;每一次迭代都增强了它的准确度直至达到需要的准确度。
在牛顿近似值中的一般想法是我们我们猜测一个数x的平方根值y,我们可能通过一个简单的操作用x/y来拉平y来取得更好的猜测,使其更接近实际的平方根,例如,我们像下面这样计算2的平方根,我们假定初始的猜测是1:
2/1 = 2 ;
2/1.5 = 1.3333; ( 1.5 + 1.3333 ) / 2 =
1.4167
2/1.4167 = 1.4117;
And so on...
如前面所提到的,牛顿的近似值是一个大家所熟知的用以快速计算平方根的方法。但是,Carmack在初始的猜测中就选取的不寻常的值,它彻底加强了准确度并且将Quake
III中计算所要的值的迭代次数降到了1次!
魔数:
函数中真正有意思的方面是神奇的常量0x5f3759df,用来计算初始猜测的,在i
因此,把输入除以2并从神奇常量中减去。这个常数工作起来几乎是完美的--对于一个 low
relative error of 10^-3来说只要一次牛顿近似值迭代就够了。如评论中第二次迭代中展示的,这个近似值对Quake
III引擎来说已经足够了。
结果,这个神奇的常数0x5f3759df成了一个迷了,在文章"Fast Inverse
Square Root" [2] ,普度大学的数学家Chris
Lomont研究了这个常数,用了几种精细的技术,Lomont想自己用数学方法求出这个常数来,结果令人惊奇--Lomont用数学方法计算出来的最佳常数(0x5f37642f)有一点点不同,并且除了理论上强一些之外,它产生的结果并没有源代码中使用的原始常数好!确实,John
Carmack 一定用了天才般的黑盒来找到这个常数。
只在仅仅从数字上来找的方法中,Lomont找到了一个更好的常数,这个数比原始的那个强了那么一点点。然而,实践中两个常数产生了大概相同的结果,Lomont提出这个使用了更好的常数的函数:
float InvSqrt(float
x)
{
}
-----------------------------------------------------------------------------------------------------------------
附录
1、参考http://blog.csdn.net/zmazon/article/details/8217866
-----------------------------------------------------------------------------------------------------------------

加载中…