余弦函数精度损失问题

Problems with precision loss in my cosine function

提问人:universelement 提问时间:12/9/2022 最后编辑:Jabberwockyuniverselement 更新时间:12/9/2022 访问量:154

问:

这是我的任务:

编写一个 C 函数来计算级数 // cos(x) = x-(x2 /2!)+(x4 /4!)-(x6 /6!)+...等。变量 realNuber 使用弧度而不是 度

我失去了精确度,但我不明白在哪里。realNumber = 60 的答案必须是 0.500,但我有 0.501。请帮忙。

#include "stdio.h"
#include "inttypes.h"

double power(float N, uint32_t P){
    double buffer = 1;

    for (int i = 0; i < P; ++i) {
        buffer *= N;
    }

    return buffer;
}

float factorial(float number){
    float result = number;

    if (number == 0) {
        return 0;
    }

    for (int i = 0; i < number - 1; ++i) {
        result *= i + 1;
    }

    return result;
}

float cos(float x){
    float result = x * (3.14159265359 / 180.);
    float polar = -1;

    for (int i = 2; i < 10; i += 2) {
        result += power(result, i) / factorial(i) * polar;
        polar *= -1;
    }

    return result;
}

int main(void){
    float realNumber = 0;
    float result = 0;

    scanf("%f", &realNumber);

    result = cos(realNumber);

    printf("%.13f", result);
}

我尝试更改函数cos();也许问题出在另一个地方?

c 数学 精度 三角函数 阶乘

评论

1赞 Andreas Wenzel 12/9/2022
您是否尝试过在调试器中逐行运行代码,同时监视所有变量的值,以确定浮点偏差在哪一行中变得不可接受?如果你没有尝试过这个,那么你可能想读这个:什么是调试器,它如何帮助我诊断问题?
5赞 Some programmer dude 12/9/2022
也不要用于浮点数或计算。用。floatdouble
7赞 12/9/2022
我完全不同意建议的副本。这与浮点表示无关。
2赞 tadman 12/9/2022
float老实说,这是很垃圾的,除非你正在做一些对精度基本不敏感的事情,比如 ML 或 3D 渲染的某些方面,否则你应该使用 .double
2赞 Ian Abbott 12/9/2022
您传递了错误的值作为 的第一个参数。它应该是以弧度为单位的角度,而不是当前结果。power

答:

1赞 Gul Den 12/9/2022 #1

cos函数中的小误差。试试这个。

float mycos(float x){
    float result = 1.0;
    float polar = -1;
    float xrad = x * (3.14159265359 / 180.);

    for (int i = 2; i < 10; i += 2) {
        result += power(xrad, i) / (factorial(i) * polar);
        polar *= -1;
    }

    return result;
}

评论

0赞 Jonathan Leffler 12/9/2022
您应该解释您进行了哪些更改以及原因。简单地发布代码没有多大帮助。
2赞 Jabberwocky 12/9/2022 #2

你的函数是完全错误的。解释在评论中。cos

float cos(float x) {
  float anglerad = x * 3.14159265359 / 180; // multiply first, then divide, but 
                                            // it probably doesn't matter much here

  float result = 1;                         // initial result must be 1
  float sign = -1;                          // use proper naming

  for (int i = 2; i < 10; i += 2) {
    // you need power(anglerad,.... not power(result,...)
    result += power(anglerad, i) / factorial(i) * sign;

    sign *= -1;
  }

  return result;
}

余弦的公式是1-(x^2/2!) + (x^4/4!) + ...

你试图使用哪个是错误的。x-(x^2/2!) + (x^4/4!) + ...


一些一般性评论:

尽管校正后的函数是正确的,但效率不是很高。cos

  • 通过使用上一次迭代的结果,可以避免对阶乘函数的重复调用。记得:。您甚至可以使用一个表,其中包含从 2 到 10 的阶乘硬编码值(如果您想要更多迭代,也可以使用其他上限)。x! = x * (x-1)!
  • 可以避免对电源函数的重复调用。记得:。x^n = x * x^(n-1)
  • 您可以使用更多迭代。
  • 您可以使用代替 .doublefloat
  • 可能还有更多的事情。

评论

0赞 Ian Abbott 12/9/2022
也可以以 和 开头,但随后需要更正该函数以返回 1 表示 。result = 0;polar = 1;for(i = 0factorialfactorial(0)
1赞 Ian Abbott 12/9/2022
OP 的尝试比你描述的更错误,主要是因为将部分总和传递给 .所以它更像是.powerx - (x^2/2!) + ((x - (x^2/2!))^4/4!) - ...
0赞 chux - Reinstate Monica 12/9/2022
x * 3.14159265359 / 180;,以及其他变化都是没有意义的,因为计算是浪费使用数学完成的。只需使用:一乘,一舍五入。x * (3.14159265359 / 180);doublex * (3.14159265359.0f / 180)
4赞 abelenky 12/9/2022 #3

你最初写道:

编写一个 C 函数来计算级数 // cos(x) = x-(x2 /2!)+(x4 /4!)-(x6 /6!)

但这不是 Cos 的泰勒系列。

正确的公式是:

enter image description here

(注意第一项中的 1 而不是 x
来源链接

通过对泰勒级数的更正和其他一些修复,我得到了:

输出

Success #stdin #stdout 0s 5392KB
0.4999999701977

我的代码:

#include "stdio.h"
#include "inttypes.h"

// No Changes
double power(float N, uint32_t P){
    double buffer = 1;

    for (int i = 0; i < P; ++i) {
        buffer *= N;
    }

    return buffer;
}

// No Changes
float factorial(float number){
    float result = number;

    if (number == 0) {
        return 0;
    }

    for (int i = 0; i < number - 1; ++i) {
        result *= i + 1;
    }

    return result;
}

// Minor changes, explained in comments
float cos(float x){
    x = x * (3.14159265359 / 180.); // Convert Degrees to Radians
    float result = 1;               // Taylor series starts with 1, not with x !!! 
    float polar = -1;

    for (int i = 2; i <= 10; i += 2) {
        result += power(x, i) / factorial(i) * polar;
        polar *= -1;
    }

    return result;
}

// Skipped the scanf in favor of hard-coded value, for simplicity.
int main(void){
    float realNumber = 60;
    float result = 0;

    result = cos(realNumber);

    printf("%.13f", result);
}

当我重写 cos 函数以消除使用幂函数和阶乘函数时,我得到了这个:

double cos(float x){
    x = x * (3.14159265359 / 180.); // Convert Degrees to Radians
    double num   = 1;  // Numerator of the fraction (x^2, x^4...)
    int    sgn   = +1; // Sign, alternating -1, +1
    uint64_t den = 1;  // Denominator: Factorials, 2!, 4!, 6!...
    float ans    = 1;  // Accumulated answer
    for (int i = 2; i <= 10; i += 2) {
        num *= x*x;
        den *= i*i-i;
        sgn *= -1;
        ans += num / den * sgn;
    }

    return ans;
}

评论

0赞 universelement 12/10/2022
是的,这是工作。我在cos()之前决定了sin()。函数 sin() 没有 1。谢谢)
-1赞 duffymo 12/9/2022 #4

以下是我在 Java 中实现它的方法。代码应该与 C 语言足够相似,以便您轻松翻译。无需幂或阶乘调用。我希望你会同意它要简单得多。

public class TrigTaylorSeries {

    /**
     * Taylor series for cosine
     * @param x angle in radians
     * @param n terms to include (must be greater than zero)
     * @return cosine(x)
     * @link https://en.wikipedia.org/wiki/Taylor_series
     */
    public static double cos(double x, int n) {
        if (n <= 0) throw new IllegalArgumentException("Number of terms must be positive");
        double result = 0.0;
        double factor = 1.0;
        for (int i = 0; i < n; ++i) {
            result += factor;
            factor *= -x*x/(2*i+1)/(2*i+2);
        }
        return result;
    }
}

下面是一个 Junit 测试,显示它正常工作:

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

public class TrigTaylorSeriesTest {

    @Test
    public void testCosine() {
        // setup
        int nterms = 20;
        int npoints = 20;
        double t = 0.0;
        double dt = 2.0*Math.PI/npoints;
        double eps = 1.0e-9;
        // exercise
        // assert
        for (int i = 0; i < npoints+1; ++i) {
            Assertions.assertEquals(Math.cos(t), TrigTaylorSeries.cos(t, nterms), eps, String.format("Incorrect result for %d", i));
            t += dt;
        }
    }
}

使用 20 个术语精确到 9 个有效数字。