我试图在C中实现正弦函数,但我得到了奇怪的结果。以下是我用来计算正弦的三个函数:
mPivot->GotFocus += ref new RoutedEventHandler([](Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
{
mControl->Focus(Windows::UI::Xaml::FocusState::Programmatic);
});
为了测试它,我一直在对内置的正弦函数进行测试,如下所示:
#define PI 3.14159265358979323846
#define DEPTH 16
double sine(long double);
long double pow(long double, unsigned int);
unsigned int fact(unsigned int);
double sine(long double x) {
long double i_x = x *= PI/180;
int n = 3, d = 0, sign = -1;
// fails past 67 degrees
for (; d < DEPTH; n += 2, d++, sign *= -1) {
x += pow(i_x, n) / fact(n) * sign;
}
return x;
}
long double pow(long double base, unsigned int exp) {
double answer = 1;
while (exp) {
answer *= base;
exp--;
}
return answer;
}
unsigned int fact(unsigned int n) {
unsigned int answer = 1;
while (n > 1) {
answer *= n--;
}
return answer;
}
直到67度,它计算与内置函数相同。虽然,随着它增加到67以上,它通常偏离实际值越来越远。
以下是输出示例:
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
main() {
for (int i = 0; i <= 180; i++) {
printf("sin(%i) = %lf, %lf\n", i, sine(i), sin(i*3.14159265358979323846/180));
}
exit(EXIT_SUCCESS);
}
有人知道为什么会这样吗?我在Windows 7上使用Visual Studio 2017,如果它提供任何有用的信息。
答案 0 :(得分:7)
每当for loop
进展时,n
增加2
,因此DEPTH = 16
增加30
,接近循环结束时,您必须计算大小的数字因子unsigned int
并且您使用的2^32 = 4294967296 ~= 12!
只能存储与long double
一样大的值,这会导致您的阶乘函数溢出,从而导致错误的阶乘。
即使您使用了long double
,我在评论中已经说明MSCRT中的double
已映射到double
(Reference),您仍会看到一些异常可能在更大的角度,因为虽然1.8E+308
可以存储与2^53 = 9007199254740992 ~= 18!
一样大的值,但它在2^53 + 1
处丢失其粒度(即double
存储为2^53
是等于printf()
)。因此,一旦你在角度上升,这种行为的效果会变得越来越大,以至于你在0
使用的6位小数精度中显而易见。
虽然您走在正确的轨道上,但您应该使用bignum库,例如 GMP 或 libcrypto 。他们可以在不损失精度的情况下执行这些计算。
BTW,因为您正在使用Windows 7进行开发,这意味着您使用的是x86或x86-64。在这些平台上,x87能够以80位执行扩展精度(按照754标准)操作,但我不知道编译器内在函数可以在不使用汇编代码的情况下为您提供该功能。
我还想引导您注意范围缩减技术。虽然我仍然建议使用bignum libs,如果你在90
和0
度(45
和sine()
之间表现良好,如果我要更加严格),你可以计算所有其他角度的double
仅通过简单的三角标识。
<强>更新强>
实际上我会纠正自己在因子计算中使用double
的问题。在编写了一个简单的程序后,我确认当我使用18
来存储阶乘时,即使我高于double
,它们也是正确的。在给出一些想法后,我意识到在 factorials 的情况下,19! = 19 * 18 * ... * 2 * 1
粒度的情况稍微复杂一些。我会举个例子来说清楚:
18, 16, 14, ... , 2
此数字2
中的都是2
的倍数,因为乘以19!
相当于以二进制表示向左移位,{{1}中的所有较低位已经0
,因此当double
的舍入为整数大于2^53
时,这些因子不受影响。您可以通过计算19!
的{{1}}的数量来计算2
的二进制表示中的最低有效零的数量。 (对于16
,它是20!
)
我要去18
并检查所有因子是否不受影响。我会告诉你结果。
更新2:
如果我们使用1.8e+308
来保存阶乘,则会受double
之后的舍入影响。它可以很容易地显示出来,因为23!
意味着需要至少75位的精度来表示它,但是因为2^74 < 23! < 2^75
具有23!
个最低有效位,其值为{{1}所以它需要19
,大于0
提供的75 - 19 = 56
位。
对于53
,它是double
位(您可以自己计算)。
答案 1 :(得分:4)
您的代码中存在多个问题:
您使用不同的原型重新定义标准函数pow()
。将程序链接为可执行文件时,这可能会导致问题。使用其他名称,例如pow_int
。
您应该在pow_int
函数之前将fact
和static
函数定义为sine
。它可以在编译时进行更好的优化。
确实fact
受类型unsigned int
范围的限制,远小于long double
类型的精度。超出12
的因子具有不正确的值,导致精度损失。
您实际上可以逐步计算这些术语,从而节省大量计算并避免可能的精度损失。
没有参数的main()
原型为int main(void)
PI/180
的计算执行时间为double
,不如long double
精确。您应该将表达式写为x = x * PI / 180;
DEPTH
以提高精确度。至少还有4个术语带来了实质性的改进。
您应该应用范围缩小:利用正弦函数对称和周期性,可以在x
模90或甚至45度的较少项上执行计算。
以下是修改后的版本:
#include <stdio.h>
#include <math.h>
#define PI_L 3.14159265358979323846264338327950288L
#define PI 3.14159265358979323846264338327950288
#define DEPTH 24
double sine(long double x) {
long double res, term, x2, t1;
int phase;
x = remquol(x, 90, &phase);
if (phase & 1)
x = 90 - x;
x = x * PI_L / 180; // convert x to radians
x2 = x * x; // pre-compute x^2
// compute the sine series: x - x^3/3! + x^5/5! ...
res = term = x; // the first term is x
for (int n = 1; n < DEPTH; n += 4) {
// to reduce precision loss, compute 2 terms for each iteration
t1 = term * x2 / ((n + 1) * (n + 2));
term = t1 * x2 / ((n + 3) * (n + 4));
// update the result with the difference of the terms
res += term - t1;
}
if (phase & 2)
res = -res;
return (double)res;
}
int main(void) {
printf("deg sin sine delta\n\n");
for (int i = 0; i <= 360; i += 10) {
double s1 = sin(i * PI / 180);
double s2 = sine(i);
printf("%3i %20.17f %20.17f %g\n", i, s1, s2, s2 - s1);
}
return 0;
}
输出结果为:
deg sin sine delta
0 0.00000000000000000 0.00000000000000000 0
10 0.17364817766693033 0.17364817766693036 2.77556e-17
20 0.34202014332566871 0.34202014332566871 0
30 0.49999999999999994 0.50000000000000000 5.55112e-17
40 0.64278760968653925 0.64278760968653936 1.11022e-16
50 0.76604444311897801 0.76604444311897801 0
60 0.86602540378443860 0.86602540378443860 0
70 0.93969262078590832 0.93969262078590843 1.11022e-16
80 0.98480775301220802 0.98480775301220802 0
90 1.00000000000000000 1.00000000000000000 0
100 0.98480775301220802 0.98480775301220802 0
110 0.93969262078590843 0.93969262078590843 0
120 0.86602540378443882 0.86602540378443860 -2.22045e-16
130 0.76604444311897812 0.76604444311897801 -1.11022e-16
140 0.64278760968653947 0.64278760968653936 -1.11022e-16
150 0.49999999999999994 0.50000000000000000 5.55112e-17
160 0.34202014332566888 0.34202014332566871 -1.66533e-16
170 0.17364817766693025 0.17364817766693036 1.11022e-16
180 0.00000000000000012 -0.00000000000000000 -1.22465e-16
190 -0.17364817766693047 -0.17364817766693036 1.11022e-16
200 -0.34202014332566866 -0.34202014332566871 -5.55112e-17
210 -0.50000000000000011 -0.50000000000000000 1.11022e-16
220 -0.64278760968653925 -0.64278760968653936 -1.11022e-16
230 -0.76604444311897790 -0.76604444311897801 -1.11022e-16
240 -0.86602540378443837 -0.86602540378443860 -2.22045e-16
250 -0.93969262078590821 -0.93969262078590843 -2.22045e-16
260 -0.98480775301220802 -0.98480775301220802 0
270 -1.00000000000000000 -1.00000000000000000 0
280 -0.98480775301220813 -0.98480775301220802 1.11022e-16
290 -0.93969262078590854 -0.93969262078590843 1.11022e-16
300 -0.86602540378443860 -0.86602540378443860 0
310 -0.76604444311897812 -0.76604444311897801 1.11022e-16
320 -0.64278760968653958 -0.64278760968653936 2.22045e-16
330 -0.50000000000000044 -0.50000000000000000 4.44089e-16
340 -0.34202014332566855 -0.34202014332566871 -1.66533e-16
350 -0.17364817766693127 -0.17364817766693036 9.15934e-16
360 -0.00000000000000024 0.00000000000000000 2.44929e-16
从上面可以看出,sine()
函数似乎比我系统上的标准sin
函数更精确:sin(180 * M_PI / 128)
应该精确0
。同样,sin(150 * M_PI / 128)
应该是0.5
。
答案 2 :(得分:3)
您的多项式系列评估方法是numerically unstable。尝试horner's method,这比功率计算更稳定。
答案 3 :(得分:2)
你的问题在这里:
for (; d < DEPTH; n += 2, d++, sign *= -1) {
x += pow(i_x, n) / fact(n) * sign;
}
如果d < DEPTH
n < DEPTH
错误使用,d
,则for (; n < DEPTH; n += 2, sign *= -1) {
x += pow(i_x, n) / fact(n) * sign;
}
与循环内的计算无关。以下应该可以工作 - 虽然我没有编译测试。
DEPTH
注意: 12
1, 3, 5, ... 11
(例如,使用条件3e-10
的泰勒级数展开)足以导致60-degrees
错误 - 3十亿万分之一0-360
。 (虽然误差随着DEPTH
之间的角度增加而增加,但20
1.0e-8
会在整个范围内保持误差小于d
。)
启用编译器警告会捕获sine
中未使用的M_PI
。
以下是包含更改的代码示例(注意:Gnu为PI提供了常量#include <stdio.h>
#include <stdint.h>
#include <math.h>
#define DEPTH 16
/* n factorial */
uint64_t nfact (int n)
{
if (n <= 0) return 1;
uint64_t s = n;
while (--n)
s *= n;
return s;
}
/* y ^ x */
double powerd (const double y, const int x)
{
if (!x) return 1;
double r = y;
for (int i = 1; i < x; i++)
r *= y;
return r;
}
double sine (double deg)
{
double rad = deg * M_PI / 180.0,
x = rad;
int sign = -1;
for (int n = 3; n < DEPTH; n += 2, sign *= -1)
x += sign * powerd (rad, n) / nfact (n);
return x;
}
int main (void) {
printf (" deg sin sine\n\n");
for (int i = 0; i < 180; i++)
printf ("%3d %11.8f %11.8f\n", i, sin (i * M_PI / 180.0), sine (i));
return 0;
}
):
$ ./bin/sine
deg sin sine
0 0.00000000 0.00000000
1 0.01745241 0.01745241
2 0.03489950 0.03489950
3 0.05233596 0.05233596
4 0.06975647 0.06975647
5 0.08715574 0.08715574
6 0.10452846 0.10452846
7 0.12186934 0.12186934
8 0.13917310 0.13917310
9 0.15643447 0.15643447
10 0.17364818 0.17364818
11 0.19080900 0.19080900
12 0.20791169 0.20791169
13 0.22495105 0.22495105
14 0.24192190 0.24192190
15 0.25881905 0.25881905
16 0.27563736 0.27563736
17 0.29237170 0.29237170
18 0.30901699 0.30901699
19 0.32556815 0.32556815
20 0.34202014 0.34202014
21 0.35836795 0.35836795
22 0.37460659 0.37460659
23 0.39073113 0.39073113
24 0.40673664 0.40673664
25 0.42261826 0.42261826
26 0.43837115 0.43837115
27 0.45399050 0.45399050
28 0.46947156 0.46947156
29 0.48480962 0.48480962
30 0.50000000 0.50000000
31 0.51503807 0.51503807
32 0.52991926 0.52991926
33 0.54463904 0.54463904
34 0.55919290 0.55919290
35 0.57357644 0.57357644
36 0.58778525 0.58778525
37 0.60181502 0.60181502
38 0.61566148 0.61566148
39 0.62932039 0.62932039
40 0.64278761 0.64278761
41 0.65605903 0.65605903
42 0.66913061 0.66913061
43 0.68199836 0.68199836
44 0.69465837 0.69465837
45 0.70710678 0.70710678
46 0.71933980 0.71933980
47 0.73135370 0.73135370
48 0.74314483 0.74314483
49 0.75470958 0.75470958
50 0.76604444 0.76604444
51 0.77714596 0.77714596
52 0.78801075 0.78801075
53 0.79863551 0.79863551
54 0.80901699 0.80901699
55 0.81915204 0.81915204
56 0.82903757 0.82903757
57 0.83867057 0.83867057
58 0.84804810 0.84804810
59 0.85716730 0.85716730
60 0.86602540 0.86602540
61 0.87461971 0.87461971
62 0.88294759 0.88294759
63 0.89100652 0.89100652
64 0.89879405 0.89879405
65 0.90630779 0.90630779
66 0.91354546 0.91354546
67 0.92050485 0.92050485
68 0.92718385 0.92718385
69 0.93358043 0.93358043
70 0.93969262 0.93969262
71 0.94551858 0.94551858
72 0.95105652 0.95105652
73 0.95630476 0.95630476
74 0.96126170 0.96126170
75 0.96592583 0.96592583
76 0.97029573 0.97029573
77 0.97437006 0.97437006
78 0.97814760 0.97814760
79 0.98162718 0.98162718
80 0.98480775 0.98480775
81 0.98768834 0.98768834
82 0.99026807 0.99026807
83 0.99254615 0.99254615
84 0.99452190 0.99452190
85 0.99619470 0.99619470
86 0.99756405 0.99756405
87 0.99862953 0.99862953
88 0.99939083 0.99939083
89 0.99984770 0.99984770
90 1.00000000 1.00000000
91 0.99984770 0.99984770
92 0.99939083 0.99939083
93 0.99862953 0.99862953
94 0.99756405 0.99756405
95 0.99619470 0.99619470
96 0.99452190 0.99452190
97 0.99254615 0.99254615
98 0.99026807 0.99026807
99 0.98768834 0.98768834
100 0.98480775 0.98480775
101 0.98162718 0.98162718
102 0.97814760 0.97814760
103 0.97437006 0.97437006
104 0.97029573 0.97029573
105 0.96592583 0.96592583
106 0.96126170 0.96126170
107 0.95630476 0.95630476
108 0.95105652 0.95105652
109 0.94551858 0.94551858
110 0.93969262 0.93969262
111 0.93358043 0.93358043
112 0.92718385 0.92718385
113 0.92050485 0.92050485
114 0.91354546 0.91354546
115 0.90630779 0.90630779
116 0.89879405 0.89879405
117 0.89100652 0.89100652
118 0.88294759 0.88294759
119 0.87461971 0.87461971
120 0.86602540 0.86602540
121 0.85716730 0.85716730
122 0.84804810 0.84804810
123 0.83867057 0.83867057
124 0.82903757 0.82903757
125 0.81915204 0.81915204
126 0.80901699 0.80901699
127 0.79863551 0.79863551
128 0.78801075 0.78801075
129 0.77714596 0.77714596
130 0.76604444 0.76604444
131 0.75470958 0.75470958
132 0.74314483 0.74314482
133 0.73135370 0.73135370
134 0.71933980 0.71933980
135 0.70710678 0.70710678
136 0.69465837 0.69465836
137 0.68199836 0.68199835
138 0.66913061 0.66913060
139 0.65605903 0.65605902
140 0.64278761 0.64278760
141 0.62932039 0.62932038
142 0.61566148 0.61566146
143 0.60181502 0.60181501
144 0.58778525 0.58778523
145 0.57357644 0.57357642
146 0.55919290 0.55919288
147 0.54463904 0.54463901
148 0.52991926 0.52991924
149 0.51503807 0.51503804
150 0.50000000 0.49999996
151 0.48480962 0.48480958
152 0.46947156 0.46947152
153 0.45399050 0.45399045
154 0.43837115 0.43837109
155 0.42261826 0.42261820
156 0.40673664 0.40673657
157 0.39073113 0.39073105
158 0.37460659 0.37460651
159 0.35836795 0.35836786
160 0.34202014 0.34202004
161 0.32556815 0.32556804
162 0.30901699 0.30901686
163 0.29237170 0.29237156
164 0.27563736 0.27563720
165 0.25881905 0.25881887
166 0.24192190 0.24192170
167 0.22495105 0.22495084
168 0.20791169 0.20791145
169 0.19080900 0.19080873
170 0.17364818 0.17364788
171 0.15643447 0.15643414
172 0.13917310 0.13917274
173 0.12186934 0.12186895
174 0.10452846 0.10452803
175 0.08715574 0.08715526
176 0.06975647 0.06975595
177 0.05233596 0.05233537
178 0.03489950 0.03489886
179 0.01745241 0.01745170
示例使用/输出
sin
基于DEPTH的错误检查
在回复有关计算错误的评论时,您可以通过改变cos
和DEPTH
来调查与EMAX 1.0e-8
和0-360
的Taylor-Series扩展相关的错误。在[{1}}(或0-2PI
)范围内使用与以下类似的内容设置#define DEPTH 20
#define EMAX 1.0e-8
...
/* sine as above */
...
/* cos with taylor series expansion to n = DEPTH */
long double cose (const long double deg)
{
long double rad = deg * M_PI / 180.0,
x = 1.0;
int sign = -1;
for (int n = 2; n < DEPTH; n += 2, sign *= -1)
x += sign * powerd (rad, n) / nfact (n);
return x;
}
int main (void) {
for (int i = 0; i < 180; i++) {
long double sinlibc = sin (i * M_PI / 180.0),
coslibc = cos (i * M_PI / 180.0),
sints = sine (i),
costs = cose (i),
serr = fabs (sinlibc - sints),
cerr = fabs (coslibc - costs);
if (serr > EMAX)
fprintf (stderr, "sine error exceeds limit of %e\n"
"%3d %11.8Lf %11.8Lf %Le\n",
EMAX, i, sinlibc, sints, serr);
if (cerr > EMAX)
fprintf (stderr, "cose error exceeds limit of %e\n"
"%3d %11.8Lf %11.8Lf %Le\n",
EMAX, i, coslibc, costs, cerr);
}
return 0;
}
的最大误差,
DEPTH 20
如果您检查,您会发现,对于低于1.0e-8
的任何内容(每次扩展中有10个字词),对于更高的角度,错误将超过DEPTH
。令人惊讶的是,对于12
低至0-90
(6项)的值,扩展在第一象限上非常准确。
Addemdum - 使用libc sin/cos
&amp ;;提高Taylor系列精度象限强>
在正常的泰勒级数展开中,误差随角度增大而增大。并且...因为有些人不能不修补,我想进一步比较0-90
和泰勒系列之间的准确度,如果计算限制在90-360
度,则余下的时间来自2, 3 & 4
{1}}由象限(0-90
)镜像0-90
的结果处理。它非常有效。
例如,只处理角度90 - 180
和180 - 270
,270 - 360
和angle % 360
与初始libc
之间的包围角度的结果会产生与8
数学函数库函数。 libc与10
&amp;之间的最大误差sin/cos
术语泰勒系列扩展是可敬的:
来自libc的最大错误 TSLIM 16
使用sine_ts max err at : 90.00 deg -- 6.023182e-12
cose_ts max err at : 270.00 deg -- 6.513370e-11
TSLIM 20
使用sine_ts max err at : 357.00 deg -- 5.342948e-16
cose_ts max err at : 270.00 deg -- 3.557149e-15
sine
(大量角度根本没有差异)
Taylor-Series的cose
和double sine (const double deg)
{
double fp = deg - (int64_t)deg, /* save fractional part of deg */
qdeg = (int64_t)deg % 360, /* get equivalent 0-359 deg angle */
rad, sine_deg; /* radians, sine_deg */
int pos_quad = 1, /* positive quadrant flag 1,2 */
sign = -1; /* taylor series term sign */
qdeg += fp; /* add fractional part back to angle */
/* get equivalent 0-90 degree angle, set pos_quad flag */
if (90 < qdeg && qdeg <= 180) /* in 2nd quadrant */
qdeg = 180 - qdeg;
else if (180 < qdeg && qdeg <= 270) { /* in 3rd quadrant */
qdeg = qdeg - 180;
pos_quad = 0;
}
else if (270 < qdeg && qdeg <= 360) { /* in 4th quadrant */
qdeg = 360 - qdeg;
pos_quad = 0;
}
rad = qdeg * M_PI / 180.0; /* convert to radians */
sine_deg = rad; /* save copy for computation */
/* compute Taylor-Series expansion for sine for TSLIM / 2 terms */
for (int n = 3; n < TSLIM; n += 2, sign *= -1) {
double p = rad;
uint64_t f = n;
for (int i = 1; i < n; i++) /* pow */
p *= rad;
for (int i = 1; i < n; i++) /* nfact */
f *= i;
sine_deg += sign * p / f; /* Taylor-series term */
}
return pos_quad ? sine_deg : -sine_deg;
}
的调整版本如下:
cos
和double cose (const double deg)
{
double fp = deg - (int64_t)deg, /* save fractional part of deg */
qdeg = (int64_t)deg % 360, /* get equivalent 0-359 deg angle */
rad, cose_deg = 1.0; /* radians, cose_deg */
int pos_quad = 1, /* positive quadrant flag 1,4 */
sign = -1; /* taylor series term sign */
qdeg += fp; /* add fractional part back to angle */
/* get equivalent 0-90 degree angle, set pos_quad flag */
if (90 < qdeg && qdeg <= 180) { /* in 2nd quadrant */
qdeg = 180 - qdeg;
pos_quad = 0;
}
else if (180 < qdeg && qdeg <= 270) { /* in 3rd quadrant */
qdeg = qdeg - 180;
pos_quad = 0;
}
else if (270 < qdeg && qdeg <= 360) /* in 4th quadrant */
qdeg = 360 - qdeg;
rad = qdeg * M_PI / 180.0; /* convert to radians */
/* compute Taylor-Series expansion for sine for TSLIM / 2 terms */
for (int n = 2; n < TSLIM; n += 2, sign *= -1) {
double p = rad;
uint64_t f = n;
for (int i = 1; i < n; i++) /* pow */
p *= rad;
for (int i = 1; i < n; i++) /* nfact */
f *= i;
cose_deg += sign * p / f; /* Taylor-series term */
}
return pos_quad ? cose_deg : -cose_deg;
}
import pandas as pd
import numpy as np
O = [1, np.nan, 5, np.nan]
H = [5, np.nan, 5, np.nan]
L = [1, np.nan, 2, np.nan]
C = [5, np.nan, 2, np.nan]
timestamps = ["2017-07-23 03:13:00", "2017-07-23 03:14:00", "2017-07-23 03:15:00", "2017-07-23 03:16:00"]
dict = {'Open': O, 'High': H, 'Low': L, 'Close': C}
df = pd.DataFrame(index=timestamps, data=dict)
ohlc = df[['Open', 'High', 'Low', 'Close']]
兔子踪迹终结......
答案 4 :(得分:0)
将main中的角度范围更改为-90到90仍将覆盖整个正弦范围。但是当Taylor系列从零开始时,DEPTH值可以减少到7.如前所述,使事实函数64位无符号 将解决67度问题。