我最近实现了一个CORDIC函数库,以降低所需的计算能力(我的项目基于PowerPC,并且执行时间规范非常严格)。语言是ANSI-C。
其他函数(sin / cos / atan)在32位和64位实现中的精度限制范围内工作。
不幸的是,asin()函数系统地失败了某些输入。
出于测试目的,我已经在simulink S-Function中实现了.h
文件。 (这只是为了方便起见,您可以将以下内容编译为独立的.exe
并进行最少的更改)
注意:我强制进行32次迭代,因为我工作在32位精度,并且需要最大可能的精度。
Cordic.h:
#include <stdio.h>
#include <stdlib.h>
#define FLOAT32 float
#define INT32 signed long int
#define BIT_XOR ^
#define CORDIC_1K_32 0x26DD3B6A
#define MUL_32 1073741824.0F /*needed to scale float -> int*/
#define INV_MUL_32 9.313225746E-10F /*needed to scale int -> float*/
INT32 CORDIC_CTAB_32 [] = {0x3243f6a8, 0x1dac6705, 0x0fadbafc, 0x07f56ea6, 0x03feab76, 0x01ffd55b, 0x00fffaaa, 0x007fff55,
0x003fffea, 0x001ffffd, 0x000fffff, 0x0007ffff, 0x0003ffff, 0x0001ffff, 0x0000ffff, 0x00007fff,
0x00003fff, 0x00001fff, 0x00000fff, 0x000007ff, 0x000003ff, 0x000001ff, 0x000000ff, 0x0000007f,
0x0000003f, 0x0000001f, 0x0000000f, 0x00000008, 0x00000004, 0x00000002, 0x00000001, 0x00000000};
/* CORDIC Arcsine Core: vectoring mode */
INT32 CORDIC_asin(INT32 arc_in)
{
INT32 k;
INT32 d;
INT32 tx;
INT32 ty;
INT32 x;
INT32 y;
INT32 z;
x=CORDIC_1K_32;
y=0;
z=0;
for (k=0; k<32; ++k)
{
d = (arc_in - y)>>(31);
tx = x - (((y>>k) BIT_XOR d) - d);
ty = y + (((x>>k) BIT_XOR d) - d);
z += ((CORDIC_CTAB_32[k] BIT_XOR d) - d);
x = tx;
y = ty;
}
return z;
}
/* Wrapper function for scaling in-out of cordic core*/
FLOAT32 asin_wrap(FLOAT32 arc)
{
return ((FLOAT32)(CORDIC_asin((INT32)(arc*MUL_32))*INV_MUL_32));
}
这可以通过类似于:
的方式调用#include "Cordic.h"
#include "math.h"
void main()
{
y1 = asin_wrap(value_32); /*my implementation*/
y2 = asinf(value_32); /*standard math.h for comparison*/
}
结果如下:
左上角显示[-1;1]
输入超过2000步(0.001
增量),左下角是我的函数输出,右下角是标准输出,右上角是两个输出的差值。
立即看到错误不在32位精度范围内。
我已经通过我的代码分析了所执行的步骤(以及中间结果),在我看来,y
的某个值是&#34;足够接近&#34;到arc_in
的初始值以及与位移有关的原因导致解决方案发散。
我的问题:
+/- 0.6
以上,但即使删除了这些,也会有较小的值,尽管不是很明显)修改
由于有些评论提及它,是的,我测试了INT32
的定义,甚至是写作
#define INT32 int32_T
不会轻微改变结果。
目标硬件上的计算时间是通过在有效范围内随机输入的函数的10.000次迭代的数百次重复块来测量的。观察到的平均结果(对于函数的一次调用)如下:
math.h asinf() 100.00 microseconds
CORDIC asin() 5.15 microseconds
(显然先前的测试是错误的,新的交叉测试不会比有效范围内的平均100微秒更好)
答案 0 :(得分:4)
回顾评论中提到的一些事项:
arcsin(1)
时。 arcsin(0.60726)
到arcsin(0.68514)
的值都返回0.754805
。 /* |a| < 0.98 */
,这似乎强化了已知的不准确性接近1。作为几种不同方法的粗略比较,请考虑以下结果(所有在桌面上执行的测试,使用MSVC ++ 2010的Windows7计算机,使用arcsin()范围0-1上的10M迭代计时的基准测试:
这些结果在桌面上,因此它们与嵌入式系统的相关性是一个很好的问题。如果有疑问,建议对相关系统进行分析/基准测试。大多数经过测试的解决方案在该范围内(0-1)都没有非常好的准确度,除了一个之外的所有解决方案实际上都比内置的asin()
函数慢。
线性表查找代码发布在下面,当需要速度超过准确度时,它是我用于任何昂贵的数学函数的常用方法。它只使用带有线性插值的1024元素表。它似乎是所有测试方法中最快和最准确的,尽管内置的asin()
确实没有太慢(测试它!)。通过改变表格的大小,可以很容易地调整它的准确度。
// Please test this code before using in anything important!
const size_t ASIN_TABLE_SIZE = 1024;
double asin_table[ASIN_TABLE_SIZE];
int init_asin_table (void)
{
for (size_t i = 0; i < ASIN_TABLE_SIZE; ++i)
{
float f = (float) i / ASIN_TABLE_SIZE;
asin_table[i] = asin(f);
}
return 0;
}
double asin_table (double a)
{
static int s_Init = init_asin_table(); // Call automatically the first time or call it manually
double sign = 1.0;
if (a < 0)
{
a = -a;
sign = -1.0;
}
if (a > 1) return 0;
double fi = a * ASIN_TABLE_SIZE;
double decimal = fi - (int)fi;
size_t i = fi;
if (i >= ASIN_TABLE_SIZE-1) return Sign * 3.14159265359/2;
return Sign * ((1.0 - decimal)*asin_table[i] + decimal*asin_table[i+1]);
}
答案 1 :(得分:3)
当参数刚好大于'x'的初始值时,“单旋转”反正弦非常错误,其中那是神奇的比例因子 - 1 / An~ = 0.607252935~ = 0x26DD3B6A。
这是因为,对于所有参数&gt; 0,第一步总是具有y = 0&lt; arg,所以d = +1,设置y = 1 / An,并且离开x = 1 / An。看第二步:
如果arg&lt; = 1 / An,则d = -1,后面的步骤会收敛到一个好的答案
如果arg&gt; 1 / An,然后d = +1,此步骤远离正确答案,对于比1 / An大一点的值范围,后续步骤都有d = -1,但无法纠正结果: - (
我找到了:
arg = 0.607 (ie 0x26D91687), relative error 7.139E-09 -- OK
arg = 0.608 (ie 0x26E978D5), relative error 1.550E-01 -- APALLING !!
arg = 0.685 (ie 0x2BD70A3D), relative error 2.667E-04 -- BAD !!
arg = 0.686 (ie 0x2BE76C8B), relative error 1.232E-09 -- OK, again
该方法的描述警告abs(arg)&gt; = 0.98(左右),我发现在0.986之后的某个地方,该过程无法收敛,相对误差跳至~5E-02并达到1E-01 (!!)在arg = 1: - (
正如您所做的那样,我也发现0.303&lt; arg&lt; 0.313相对误差跳至~3E-02,并缓慢减小直至恢复正常。 (在这种情况下,步骤2过冲到目前为止,其余步骤无法纠正它。)
所以......针对arcsine的单一旋转CORDIC看起来像垃圾: - (
稍后添加...当我在单个旋转CORDIC上看得更近时,我发现了更多的小区域,其中相对错误是坏的......
...所以我根本不会把它当成一种方法......这不仅仅是垃圾,而是无用。
答案 2 :(得分:2)
我在问题末尾链接的additional source显然包含解决方案。
建议的代码可以简化为以下内容:
#define M_PI_2_32 1.57079632F
#define SQRT2_2 7.071067811865476e-001F /* sin(45°) = cos(45°) = sqrt(2)/2 */
FLOAT32 angles[] = {
7.8539816339744830962E-01F, 4.6364760900080611621E-01F, 2.4497866312686415417E-01F, 1.2435499454676143503E-01F,
6.2418809995957348474E-02F, 3.1239833430268276254E-02F, 1.5623728620476830803E-02F, 7.8123410601011112965E-03F,
3.9062301319669718276E-03F, 1.9531225164788186851E-03F, 9.7656218955931943040E-04F, 4.8828121119489827547E-04F,
2.4414062014936176402E-04F, 1.2207031189367020424E-04F, 6.1035156174208775022E-05F, 3.0517578115526096862E-05F,
1.5258789061315762107E-05F, 7.6293945311019702634E-06F, 3.8146972656064962829E-06F, 1.9073486328101870354E-06F,
9.5367431640596087942E-07F, 4.7683715820308885993E-07F, 2.3841857910155798249E-07F, 1.1920928955078068531E-07F,
5.9604644775390554414E-08F, 2.9802322387695303677E-08F, 1.4901161193847655147E-08F, 7.4505805969238279871E-09F,
3.7252902984619140453E-09F, 1.8626451492309570291E-09F, 9.3132257461547851536E-10F, 4.6566128730773925778E-10F};
FLOAT32 arcsin_cordic(FLOAT32 t)
{
INT32 i;
INT32 j;
INT32 flip;
FLOAT32 poweroftwo;
FLOAT32 sigma;
FLOAT32 sign_or;
FLOAT32 theta;
FLOAT32 x1;
FLOAT32 x2;
FLOAT32 y1;
FLOAT32 y2;
flip = 0;
theta = 0.0F;
x1 = 1.0F;
y1 = 0.0F;
poweroftwo = 1.0F;
/* If the angle is small, use the small angle approximation */
if ((t >= -0.002F) && (t <= 0.002F))
{
return t;
}
if (t >= 0.0F)
{
sign_or = 1.0F;
}
else
{
sign_or = -1.0F;
}
/* The inv_sqrt() is the famous Fast Inverse Square Root from the Quake 3 engine
here used with 3 (!!) Newton iterations */
if ((t >= SQRT2_2) || (t <= -SQRT2_2))
{
t = 1.0F/inv_sqrt(1-t*t);
flip = 1;
}
if (t>=0.0F)
{
sign_or = 1.0F;
}
else
{
sign_or = -1.0F;
}
for ( j = 0; j < 32; j++ )
{
if (y1 > t)
{
sigma = -1.0F;
}
else
{
sigma = 1.0F;
}
/* Here a double iteration is done */
x2 = x1 - (sigma * poweroftwo * y1);
y2 = (sigma * poweroftwo * x1) + y1;
x1 = x2 - (sigma * poweroftwo * y2);
y1 = (sigma * poweroftwo * x2) + y2;
theta += 2.0F * sigma * angles[j];
t *= (1.0F + poweroftwo * poweroftwo);
poweroftwo *= 0.5F;
}
/* Remove bias */
theta -= sign_or*4.85E-8F;
if (flip)
{
theta = sign_or*(M_PI_2_32-theta);
}
return theta;
}
以下是需要注意的:
angles
表的结构与旧表不同。theta -= sign_or*4.85E-8F;
段删除。下图显示了旧实现(顶部)与本答案(底部)中包含的实现的绝对(左)和相对错误(右)。
只有将CORDIC输出除以内置math.h实现的输出才能获得相对误差。由于这个原因,它围绕1
而不是0
绘制。
峰值相对误差(未除以零时)为1.0728836e-006
。
平均相对误差为2.0253509e-007
(几乎与32位精度一致)。
答案 3 :(得分:0)
为了迭代过程的收敛,任何“错误”的第 i 个 迭代可以在随后的第 (i+1) 次、第 (i+2) 次、第 (i+3) 次中“纠正”, 等等等等迭代。或者,换句话说,至少有一半的“错误” 第 i 次迭代可以在下一个 (i+1) 次迭代中得到纠正。 对于 atan(1/2^i) 满足这个条件,即:
atan(1/2^(i+1)) > 1/2*atan(1/2^i)
阅读更多 http://cordic-bibliography.blogspot.com/p/double-iterations-in-cordic.html 和: http://baykov.de/CORDIC1972.htm
(注意我是这些页面的作者)