如何使用 C 正确构建 sin 查找 table?

How can I properly build a sin lookup table with C?

为了节省 sin 调用的性能,并处理整数角度,这些角度更多地被 table 操纵和保存,而不是浮点作为角度,我正在构建一个 sin 查找函数,其中 4096 个单位等于 2pi 弧度。为了节省内存,我只存前1024个sin值,相当于sin( [0, pi/2) ).

static const float SinTable[1024] = {0, 0.00153398, ..., 0.999995, 0.999999};

为了处理第3和第4象限的角度,我只是有条件地否定:

return Angle&2048 ? -UnsignedSin : UnsignedSin;

其中 UnsignedSin 是包含在 [0, 2048) 之间的查找的 sin 值。但是我该如何处理第二和第四象限呢?我如何通过检查角度是否在第二或第四象限(例如 Angle&1024)来有条件地将 [0, 1) 的存储 sin 值正确映射到 [1, 0)?我试过了,但这不太正确,因为 1024 角度的结果是 0.999999 而不是它应该是的 1。

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

1 的值永远不会存储在 sin table 中,所以我假设 1-SinTable[...] 是必需的?但是我不能完全正确。

这就像:

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 位作为 table(如 SinTable[angle >> 8])的索引来确定较低的值和下一个值;然后 int factions = angle & 0xFF; result = ( lower_value * (0x100 - factions) + upper * fractions ) / 0x100; 进行估算。

我建议您避免位操作,因为将来您可以将 float 更改为 double。 我建议

更便携的版本
`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;

您应该检查一下 CORDIC algorithm,它允许您获得完全精确的正弦和余弦函数,并完全节省 space 的表格(这些在嵌入式体系结构的三角函数中使用了很长时间时间)。并使用定点,而不是浮点数或只是简单的整数值(它根本不提供亚度精度)假设您使用 1/64 的度数(或更好的 1/2^32 的完整 2*PI 旋转或一个象限,需要大约两个 32 个条目的表)固定点才能达到足够的精度。 CORDIC 算法将允许您使用两个简单的表,每一个条目都有一个您感兴趣的精度,并且计算简单快速(仅完成加法和乘法),并且将在计算中为您提供完整的精度。