是否有一种聪明/有效的算法来确定角度的斜边(即sqrt(a² + b²)
),在没有硬件乘法的嵌入式处理器上使用定点数学运算?
答案 0 :(得分:22)
如果结果不一定非常准确,你可以得到原油 近似非常简单:
获取a
和b
的绝对值,并在必要时进行交换,以便拥有a <= b
。然后:
h = ((sqrt(2) - 1) * a) + b
为了直观地了解其工作原理,请考虑在像素显示器上绘制浅角度线的方式(例如,使用Bresenham算法)。它看起来像这样:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | | | | | | | | | | | | | | | |*|*|*| ^
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| | | | | | | | | | | | |*|*|*|*| | | | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| | | | | | | | |*|*|*|*| | | | | | | | a pixels
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| | | | |*|*|*|*| | | | | | | | | | | | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
|*|*|*|*| | | | | | | | | | | | | | | | v
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
<-------------- b pixels ----------->
对于b
方向的每个步骤,要绘制的下一个像素要么是紧邻右边,要么是向上和向右一个像素。
从一端到另一端的理想线可以通过将每个像素的中心连接到相邻像素的中心的路径来近似。这是长度为a
的一系列sqrt(2)
段,长度为1的b-a
段(将像素作为度量单位)。因此上面的公式。
这清楚地为a == 0
和a == b
提供了准确的答案;但是对两者之间的值进行了高估。
错误取决于比率b/a
; b = (1 + sqrt(2)) * a
时出现最大错误,结果为2/sqrt(2+sqrt(2))
,或超过真实值约8.24%。这不是很好,但如果它对你的应用程序来说足够好,这种方法具有简单快速的优点。 (乘以常数可以写成一系列的移位和加法。)
答案 1 :(得分:9)
为了记录,这里有几个近似值,大致列出 增加复杂性和准确性的顺序。所有这些假设0≤a≤b。
h = b + 0.337 * a // max error ≈ 5.5 %
h = max(b, 0.918 * (b + (a>>1))) // max error ≈ 2.6 %
h = b + 0.428 * a * a / b // max error ≈ 1.04 %
编辑:回答Ecir Hana的问题,以下是我如何推导出这些问题 近似值。
第一步。近似两个变量的函数可以是a 复杂的问题。因此,我首先将其转化为问题 近似一个变量的函数。这可以通过选择来完成 最长的一面作为“比例”因素,如下:
h =√(b 2 + a 2 )
=b√(1 +(a / b) 2 )
= b f(a / b)其中f(x)=√(1 + x 2 )
添加约束0≤a≤b意味着我们只关注 在区间[0,1]中近似f(x)。
下面是相关区间中f(x)的图,以及 Matthew Slattery给出的近似值(即(√2-1)x + 1)。
第二步。下一步是盯着这个情节,一边问 你自己的问题是“我怎么能廉价地估算这个功能?”。 由于曲线看起来粗略抛物线,我的第一个想法是使用a 二次函数(第三近似)。但是因为这仍然存在 相对昂贵,我也看了线性和分段线性 近似。以下是我的三个解决方案:
数值常数(0.337,0.918和0.428)最初是免费的 参数。选择特定值以最小化 近似的最大绝对误差。最小化可以 当然可以通过一些算法完成,但我只是“手工”做到了, 绘制绝对误差并调整常数直到它为止 最小化。在实践中,这非常快。编写代码 自动化这将花费更长的时间。
第三步是回到近似a的初始问题 两个变量的功能:
答案 2 :(得分:7)
考虑使用CORDIC方法。 Dobb博士有一篇文章和相关的图书馆资源here。平方根,乘法和除法在本文末尾处理。
答案 3 :(得分:6)
一种可能性如下:
#include <math.h>
/* Iterations Accuracy
* 2 6.5 digits
* 3 20 digits
* 4 62 digits
* assuming a numeric type able to maintain that degree of accuracy in
* the individual operations.
*/
#define ITER 3
double dist(double P, double Q) {
/* A reasonably robust method of calculating `sqrt(P*P + Q*Q)'
*
* Transliterated from _More Programming Pearls, Confessions of a Coder_
* by Jon Bentley, pg. 156.
*/
double R;
int i;
P = fabs(P);
Q = fabs(Q);
if (P<Q) {
R = P;
P = Q;
Q = R;
}
/* The book has this as:
* if P = 0.0 return Q; # in AWK
* However, this makes no sense to me - we've just insured that P>=Q, so
* P==0 only if Q==0; OTOH, if Q==0, then distance == P...
*/
if ( Q == 0.0 )
return P;
for (i=0;i<ITER;i++) {
R = Q / P;
R = R * R;
R = R / (4.0 + R);
P = P + 2.0 * R * P;
Q = Q * R;
}
return P;
}
每次迭代仍会进行几次除法和四次乘法,但每次输入很少需要三次以上的迭代(两次通常就足够了)。至少对于我见过的大多数处理器来说,这通常比sqrt
本身更快。
目前它是为double
编写的,但假设您已经实现了基本操作,将其转换为使用固定点非常困难。
答案 4 :(得分:4)
如果您需要sqrt
,可以先重新评估。很多时候你只是计算斜边以将其与另一个值进行比较 - 如果你将你所比较的值平方,你可以完全消除平方根。
答案 5 :(得分:4)
除非你在> 1kHz处进行此操作,否则即使在没有硬件MUL
的MCU上也是如此。更糟糕的是sqrt
。我会尝试修改我的应用程序,因此根本不需要计算它。
标准库可能是最好的,如果你真的需要它,但你可以看看使用牛顿的方法作为一种可能的替代方案。但是,它需要几个乘法/除法循环才能执行。
sqrt
function关于AVR Freaks论坛答案 6 :(得分:1)
也许你可以使用一些Elm Chans Assembler Libraries并使ihypot功能适应你的ATtiny。您需要更换MUL,并且可能(我没有检查过)其他一些说明。