如何使用C语言正确构建正弦查找表?

3
为了提高性能并处理更易于操作和保存的整数角度,而不是浮点数作为角度,我正在构建一个sin查找函数,其中4096个单位等于2pi弧度。为了节省内存,我只存储前1024个sin值,这相当于sin([0,pi/2))。
static const float SinTable[1024] = {0, 0.00153398, ..., 0.999995, 0.999999};

为了处理第三和第四象限的角度,我只需有条件地取反:
return Angle&2048 ? -UnsignedSin : UnsignedSin;

其中UnsignedSin是介于[0, 2048)之间的正弦值。但是我如何处理第二象限和第四象限?如何通过检查角度是否在第二或第四象限(例如使用 Angle&1024)有条件地将存储的正弦值从[0, 1)映射到[1, 0)?我尝试了这个方法,但不完全正确,因为1024角度的结果是 0.999999而不是应该为1。

const float UnsignedSin = SinTable[(Angle&1024 ? ~A : A)&1023];

在sin表中,1的值从未被存储,所以我认为需要使用1-SinTable[...]?但是我无法完全理解。


相关:https://dev59.com/gXE95IYBdhLWcg3wf-AF - Passerby
0.999999可能已经足够好了。 考虑到浮点数的相等性检查只能在一定精度范围内进行: https://dev59.com/P3VD5IYBdhLWcg3wU5-H - G. C.
即使它足够接近以至于无关紧要,但这仍意味着实现在概念上是不正确的。 - user16217248
3个回答

5

这就像是:

float getSine(unsigned int angle) {
    angle &= 4095;        // Reduce angle to the range of 1 circle

    if( (angle & 2048) == 0) {
        if( (angle & 1024) == 0) {
            // Angle is from 0 to 1023
            return SinTable[angle];
        } else {
            // Angle is from 1024 to 2047
            return SinTable[2048-angle];
        }
    } else {
        if( (angle & 1024) == 0) {
            // Angle is from 2048 to 3071
            return -SinTable[angle-2048];
        } else {
            // Angle is from 3072 to 4095
            return -SinTable[4096-angle];
        }
    }

请注意,对于这段代码,SinTable需要1025个条目,因此SinTable[1024] 是合法的,并包含值1.0。 这仅在原始角度为1024或3072(其中SinTable[2048-1024];SinTable [4096-3072]; 变成SinTable [1024];)时发生。这些角度可以作为特殊情况处理(如if((angle == 1024) || (angle == 3072)) return 1.0;),但这可能会更慢(由于分支不当等原因)。

另请注意,通过使用线性插值可以提高精度。例如,您可以将angle视为20位,并从0到1048575范围内进行选择;然后使用位8到19作为索引进入表格(如SinTable[angle >> 8])以确定较低值和下一个值; 然后执行int factions = angle & 0xFF; result = ( lower_value * (0x100 - factions) + upper * fractions ) / 0x100; 以创建估计值。


(angle & 2048) 等在我的情况下不起作用,(angle < 2048) 起作用了。 - trig-ger

1
您应该了解CORDIC算法,它允许您以完全精度获得正弦和余弦函数,并节省表格空间(自嵌入式架构以来长期使用三角函数)。并且使用定点数,而不是浮点数或纯整数值(后者根本没有子度精度)。假设您使用1/64度(或更好的1/2 ^ 32的完整2 * PI旋转或一个象限,需要大约两个32个条目表)的定点数以获得足够的精度。CORDIC算法将允许您使用两个简单的表格,每位精度一个条目,并进行简单快速的计算(仅进行求和和乘法),并为您提供完全的计算精度。

我如何在C中使用定点数? - user16217248
你可以使用整数,并将其中一部分位视为小数部分。如果尝试进行算术运算,则需要在乘法和除法上进行一些调整,但这是可能的。C中没有固定点类型,因此您需要 improvisation(即临时解决方案)。 - Luis Colorado

-1
我建议你避免位运算,因为将来可能会将浮点数更改为双精度浮点数。 我提出了一个更可移植的版本Brendan answer
`define PI_ADDR 2048

float getSine(unsigned int angle) {
    angle = angle % (2*PI_ADDR);  // Reduce angle to the range of 1 circle
    if( angle < PI_ADDR/2) {
        return SinTable[angle];
    } else if( angle < PI_ADDR) {
        return SinTable[PI_ADDR - angle];
    } else if( angle < (PI_ADDR*3/2) ) {
        return -SinTable[angle-PI_ADDR];
    } else {
        return -SinTable[2*PI_ADDR -angle];
    }
}

关于处理负角度,也要考虑可移植性:

return (Angle < 0) ? -UnsignedSin : UnsignedSin;

不确定为什么位操作会干扰将浮点数转换为双精度。 - user16217248
在代码中我可以看到 (Angle&2048) 和 (Angle&1024),它们不太可移植。 使用单个定义可以在未来节省很多工作。 你是对的,我的意思是查找表地址操作,而不是浮点/双精度。 - G. C.
为了提高精度,我可以相对容易地更改常量。例如,如果我想要16位精度而不是12位,我将分别使用(Angle&32768)和(Angle&16384)。此外,我仍然可以使用(1 << BITS-1)和(1 << BITS-2),因此问题不在位运算中。 - user16217248

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接