怎么用代码实现正弦值计算
泰勒展开
根据泰勒展开, 我们都知道:
只取前几项的话
发现 f(x) 从 [0, π/2] 的值和 sin(x) 的值差的也不是很多嘛~(我的要求没那么高):
就很好办辣~
Coding
先来第一步取模, 把自变量的取值缩小到 [0, 2π), 再缩到 [0, π], 接着是 [0, π/2]. 现在就可以用 f(x) 计算近似的正弦值啦.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | |
我测试了一下, 精度还是能用的, 当 x=π/2 时, 有 f(x)-sin(x) 的最大值≈ 0.00452485553.
还行
编程语言实现中的正弦函数具体写法
很多编程语言标准库中都是包含 sin(x) 函数的, 当然, 它肯定不能像咱的 f(x) 写的这么糙, 来瞅眼
glibc
接下来我会用"输入"这个字眼来代替 sin(x) 的自变量的值.
glibc 中的 sin() 实现可以在这里查看, 函数入口是这个文件中的 __sin() 函数.
不得不说还得是 glibc, 它在不同输入范围时, 会同时确保精度和速度, 使用不同的方法计算(当然, 核心还是泰勒展开).
先介绍一些 __sin() 所调用的函数:
reduce_sincos(): 范围缩减, 把范围缩到-π/4~π/4. (类似咱的 f(x) 中的to [0, 2π)和to [0, π/2])__branred(): 同样是范围缩减, 但比reduce_sincos()更复杂, 更适合大数. 在这里可以查看它的实现.do_sin(),do_cos(): 计算的核心, 在输入较小时, 它会直接干脆利落地用泰勒级数(这时误差较小); 而... 当输入大于某个阈值时, 为了缩小误差, 在利用泰勒级数的同时会查表来补偿误差(没错, 就是查表, 查 400 行的表).do_sincos(): 我们知道, 泰勒级数计算总归有点误差, 而当输入更接近原点的误差更小, 这个函数会在判断后有选择地使用 do_sin() 或 do_cos() 来计算.
下面正式介绍一下 __sin(), 它所做的主要是判范围, 选方法, 去用:
我们下面所说的"大小"指输入的绝对值大小
- 当输入极小时, 函数图像与
y=x极接近, 直接用输入值代替返回值. - 再大些时, 范围还是比较小, 可以直接用
do_sin(). - 再大些时, 范围相对小, 用
do_cos(). - 再大些时, 范围大了, 需要
reduce_sincos()去缩, 再调用do_sincos(). - 再大些时, 范围很大啦, 直接使用
reduce_sincos()会产生误差, 所以这里会使用__branred()来缩范围(开销更大也更精确), 再调用do_sincos(). - 再大些时, 这时已经极大了, 还算什么算啊, 返回
NaN得了.
CPython
给 C 语言的 sin() 函数套了个壳, 包括其他很多数学函数也是这样干的
(最初我还以为它会自己实现一个呢, 不过我很理解)
可以到这里来看, 直接套了宏