提问人:universelement 提问时间:12/9/2022 最后编辑:Jabberwockyuniverselement 更新时间:12/9/2022 访问量:154
余弦函数精度损失问题
Problems with precision loss in my cosine function
问:
这是我的任务:
编写一个 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();也许问题出在另一个地方?
答:
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)
- 您可以使用更多迭代。
- 您可以使用代替 .
double
float
- 可能还有更多的事情。
评论
0赞
Ian Abbott
12/9/2022
也可以以 和 开头,但随后需要更正该函数以返回 1 表示 。result = 0;
polar = 1;
for(i = 0
factorial
factorial(0)
1赞
Ian Abbott
12/9/2022
OP 的尝试比你描述的更错误,主要是因为将部分总和传递给 .所以它更像是.power
x - (x^2/2!) + ((x - (x^2/2!))^4/4!) - ...
0赞
chux - Reinstate Monica
12/9/2022
x * 3.14159265359 / 180;
,以及其他变化都是没有意义的,因为计算是浪费使用数学完成的。只需使用:一乘,一舍五入。x * (3.14159265359 / 180);
double
x * (3.14159265359.0f / 180)
4赞
abelenky
12/9/2022
#3
你最初写道:
编写一个 C 函数来计算级数 // cos(x) = x-(x2 /2!)+(x4 /4!)-(x6 /6!)
但这不是 Cos 的泰勒系列。
正确的公式是:
(注意第一项中的 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 个有效数字。
评论
float
double
float
老实说,这是很垃圾的,除非你正在做一些对精度基本不敏感的事情,比如 ML 或 3D 渲染的某些方面,否则你应该使用 .double
power