在C中执行正弦函数不起作用

时间:2017-07-23 03:34:20

标签: c sine

我试图在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,如果它提供任何有用的信息。

5 个答案:

答案 0 :(得分:7)

每当for loop进展时,n增加2,因此DEPTH = 16增加30,接近循环结束时,您必须计算大小的数字因子unsigned int并且您使用的2^32 = 4294967296 ~= 12!只能存储与long double一样大的值,这会导致您的阶乘函数溢出,从而导致错误的阶乘。

即使您使用了long double,我在评论中已经说明MSCRT中的double已映射到doubleReference),您仍会看到一些异常可能在更大的角度,因为虽然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,如果你在900度(45sine()之间表现良好,如果我要更加严格),你可以计算所有其他角度的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函数之前将factstatic函数定义为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的错误检查

在回复有关计算错误的评论时,您可以通过改变cosDEPTH来调查与EMAX 1.0e-80-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 - 180180 - 270270 - 360angle % 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的cosedouble 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度问题。