在 C++ 中创建正弦查找表
Posted
技术标签:
【中文标题】在 C++ 中创建正弦查找表【英文标题】:Create sine lookup table in C++ 【发布时间】:2011-04-10 23:01:58 【问题描述】:如何用 C++ 重写以下伪代码?
real array sine_table[-1000..1000]
for x from -1000 to 1000
sine_table[x] := sine(pi * x / 1000)
我需要创建一个 sine_table 查找表。
【问题讨论】:
任务的哪一部分需要帮助?调用sin
函数?应对您想要负索引的事实?声明一个数组?在 C++ 中编写 for
循环?在 C++ 中找出代表实数的类型的名称?全部?
您可能希望在查找表完成后对其进行性能测试,并验证它实际上是否比仅调用 sin()(或 sinf())更快。现代 CPU 的运行速度比现代 RAM 快得多,以至于您可能会发现在表中查找结果比再次计算结果需要更长的时间(因为计算结果可以完全在 CPU 上完成)
不仅如此,你还有更多的代码,你会从 CPU 缓存中踢出一些行。您需要在真实的例程中对其进行性能测试,而不仅仅是在测试平台中。
这可能适用于嵌入式系统或 FPGA,其中查找比计算更快x — (x^3)/(3!) + (x^5)/(5!) — (x^7)/(7!) + ...
所选答案为“如何在不使用语言内置 sin
方法的情况下计算正弦函数的值”问题提供了解决方案。在这种情况下,泰勒是最著名的软件实现算法,但计算机 ALU、计算器和其他数字系统通常通过硬件模拟正弦函数(和许多其他),实现 CORDIC (examples)。它比完整的查找表占用更少的空间。
【参考方案1】:
您需要来自<cmath>
的std::sin()
函数。
【讨论】:
【参考方案2】:long double sine_table[2001];
for (int index = 0; index < 2001; index++)
sine_table[index] = std::sin(PI * (index - 1000) / 1000.0);
【讨论】:
1) 不要忘记#include <cmath>
2) 这实际上减少了一项;相当于伪代码中的sine_table[-1000..999]
。【参考方案3】:
double table[1000] = 0;
for (int i = 1; i <= 1000; i++)
sine_table[i-1] = std::sin(PI * i/ 1000.0);
double getSineValue(int multipleOfPi)
if(multipleOfPi == 0) return 0.0;
int sign = 1;
if(multipleOfPi < 0)
sign = -1;
return signsine_table[signmultipleOfPi - 1];
您可以通过技巧 sin(pi/2 +/- 角度) = +/- cos(角度) 将数组长度减少到 500。 所以将 sin 和 cos 从 0 存储到 pi/4。 我不记得了,但它提高了我的程序的速度。
【讨论】:
一个警告。 getSineValue() 的参数名称可能具有误导性。它确实代表 Pi/1000 的倍数,因此可以称为千分之 Pi。不想学究气,但它很容易混淆 getSineValue(500) 意味着 sin(pi/2) 我对从 1 而非 0 开始 for 循环的优化感到很有趣。 :) 请注意,虽然循环有一个错误,但它写入 table[1000] 这不是数组中的有效索引(它已经结束了) 很抱歉出现循环错误。实际上,我的意思是存储 1 到 1000,因为 0 是不明显的情况。我已编辑代码以更正此问题。 我觉得还是不太对……例如,现在sine_table[0]的值是sin(PI*1/1000.0),当sin(0)的值正确时应该为零。 这就是它存在的原因:return signsine_table[signmultipleOfPi - 1];【参考方案4】:您可以通过仅存储第一象限的值(即 [0,pi/2] 中的 x 值)将表的大小减少到原始的 25%。
为此,您的查找例程只需使用简单的三角标识将 x 的所有值映射到第一象限:
sin(x) = - sin(-x),从象限 IV 映射到 I sin(x) = sin(pi - x),从象限 II 映射到 I要从象限 III 映射到 I,应用两个恒等式,即 sin(x) = - sin (pi + x)
此策略是否有帮助取决于您的情况有多少内存使用量。但是,为了避免在查找过程中进行一两次比较和减法,存储四倍于所需的值似乎很浪费。
我赞同 Jeremy 的建议,即衡量构建表是否比仅使用 std::sin() 更好。即使使用原始的大表,您也必须在每次查找表期间花费循环来将参数转换为最接近的 pi/1000 增量,并且您会在此过程中失去一些准确性。
如果您真的想以准确性换取速度,您可以尝试仅使用泰勒级数展开式的前几项来逼近 sin() 函数。
sin(x) = x - x^3/3! + x^5/5! ...,其中 ^ 表示提升到幂和 !代表阶乘。当然,为了提高效率,您应该预先计算阶乘并利用 x 的较低幂来计算较高的幂,例如计算 x^5 时使用 x^3。
最后一点,上面截断的泰勒级数对于接近零的值更准确,因此在计算近似正弦之前映射到第一或第四象限仍然值得。
附录: 基于两项观察,还有一项潜在的改进: 1. 如果您可以计算第一个八分圆 [0,pi/4] 中的正弦和余弦,则可以计算任何三角函数 2. 以零为中心的泰勒级数展开在零附近更准确
因此,如果您决定使用截断的泰勒级数,则可以通过映射到正弦或余弦来提高准确度(或使用更少的项来获得相似的准确度)以获得 [0,pi/4] 范围内的角度除了上述之外,还使用 sin(x) = cos(pi/2-x) 和 cos(x) = sin(pi/2-x) 等身份(例如,如果 x > pi/4映射到第一象限。)
或者,如果您决定对正弦和余弦都使用表查找,则可以使用仅覆盖范围 [0,pi/4] 的两个较小的表来解决问题,而代价是查找时可能会进行另一个可能的比较和减法映射到较小的范围。然后,您可以为表使用更少的内存,或者使用相同的内存但提供更精细的粒度和准确性。
【讨论】:
另外,四分之一正弦表可用于通过类似的象限重映射方法找到余弦。 第一个身份有错别字,应该是sin (-x) = -sin(x)
感谢您发现本,我相应地更正了我的答案
"您可以通过仅存储第一象限的值将表的大小减少到原始大小的 25%"。无论如何要获得 [-pi, +pi] 的实际值,系列顺序必须至少为 10,顺序 6 为 [-pi/2, +pi/2] 提供了很好的近似值 (plots)【参考方案5】:
还有一点:调用三角函数的成本很高。如果您想以恒定步长为正弦准备查找表 - 您可以节省计算时间,但会损失一些潜在的精度。
假设您的最小步骤是“a”。也就是说,你需要 sin(a), sin(2a), sin(3a), ...
然后你可以做以下技巧:首先计算 sin(a) 和 cos(a)。然后对于每个连续的步骤,使用以下三角等式:
sin([n+1] * a) = sin(n*a) * cos(a) + cos(n*a) * sin(a) cos([n+1] * a) = cos(n*a) * cos(a) - sin(n*a) * sin(a)这种方法的缺点是在这个过程中会累积舍入误差。
【讨论】:
【参考方案6】:书或其他东西的另一个近似值
streamin ramp;
streamout sine;
float x,rect,k,i,j;
x = ramp -0.5;
rect = x * (1 - x < 0 & 2);
k = (rect + 0.42493299) *(rect -0.5) * (rect - 0.92493302) ;
i = 0.436501 + (rect * (rect + 1.05802));
j = 1.21551 + (rect * (rect - 2.0580201));
sine = i*j*k*60.252201*x;
这里有完整的讨论: http://synthmaker.co.uk/forum/viewtopic.php?f=4&t=6457&st=0&sk=t&sd=a
我想你知道,使用除法比乘以十进制数要慢得多,/5 总是比 *0.2 慢
这只是一个近似值。
还有:
streamin ramp;
streamin x; // 1.5 = Saw 3.142 = Sin 4.5 = SawSin
streamout sine;
float saw,saw2;
saw = (ramp * 2 - 1) * x;
saw2 = saw * saw;
sine = -0.166667 + saw2 * (0.00833333 + saw2 * (-0.000198409 + saw2 * (2.7526e-006+saw2 * -2.39e-008)));
sine = saw * (1+ saw2 * sine);
【讨论】:
以上是关于在 C++ 中创建正弦查找表的主要内容,如果未能解决你的问题,请参考以下文章
oracle 怎么在存储过程中创建一个临时表,在里面插入数据,再查找这个临时表的所有数据,最后drop这个表。