我对CLR中内在触发功能的不准确性感到非常恼火。众所周知,
Math.Sin(Math.PI)=0.00000000000000012246063538223773
而不是0. Math.Cos(Math.PI/2)
发生了类似的事情。
但是当我进行一系列长时间的计算时,特殊情况下评估为
Math.Sin(Math.PI/2+x)-Math.Cos(x)
,对于x = 0.2,结果为零,但对于x = 0.1,则结果为零(尝试)。另一个问题是当论证数量很大时,不准确性会成比例地变大。
所以我想知道是否有人在C#中编写了一些更好的trig函数表示,以便与世界共享。 CLR是否调用了一些实现CORDIC或类似的标准C数学库?链接:wikipedia CORDIC
答案 0 :(得分:18)
这与三角函数的准确性无关,但与CLS类型系统有关。根据文档,double具有15-16位精度(这正是您得到的),因此您无法使用此类型更精确。因此,如果您想要更高的精度,则需要创建一个能够存储它的新类型。
另请注意,您永远不应该编写如下代码:
double d = CalcFromSomewhere();
if (d == 0)
{
DoSomething();
}
你应该这样做:
double d = CalcFromSomewhere();
double epsilon = 1e-5; // define the precision you are working with
if (Math.Abs(d) < epsilon)
{
DoSomething();
}
答案 1 :(得分:9)
我听到了你的声音。我对分裂的不准确感到非常恼火。前几天我做了:
Console.WriteLine(1.0 / 3.0);
和我0.333333333333333,而不是正确的答案是0.333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 33333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 ...
也许现在你看到问题所在。 Math.Pi不等于pi 任何超过1.0 / 3.0等于三分之一。它们都与真值相差几百万亿分之一,因此您使用Math.Pi或1.0 / 3.0执行的任何计算也将减少几百万亿分之一,包括采用正弦。
如果您不喜欢近似算术是近似,则不要使用近似算术。使用精确算术。当我需要精确的算术时,我曾经使用滑铁卢枫树;也许你应该买一份。
答案 2 :(得分:6)
这是浮点精度的结果。您可以获得一定数量的有效数字,并且任何无法准确表示的数字都是近似的。例如,pi不是一个有理数,因此不可能得到精确的表示。由于你无法得到精确的pi值,你不会得到包括pi在内的数字的精确正弦和余弦(在大多数情况下你也不会得到正弦和余弦的精确值)。
最好的中间解释是"What Every Computer Scientist Should Know About Floating-Point Arithmetic"。如果你不想进入那个,只要记住浮点数通常是近似值,浮点计算就像在地上移动成堆的沙子:你用它做的一切,你会失去一点沙子和拿起一点污垢。
如果你想要精确的表示,你需要找到一个象征性的代数系统。
答案 3 :(得分:2)
答案 4 :(得分:1)
我拒绝这个错误是由于四舍五入造成的。可以做的是定义sin(x)
如下,使用泰勒的6个术语扩展:
const double π=Math.PI;
const double π2=Math.PI/2;
const double π4=Math.PI/4;
public static double Sin(double x)
{
if (x==0) { return 0; }
if (x<0) { return -Sin(-x); }
if (x>π) { return -Sin(x-π); }
if (x>π4) { return Cos(π2-x); }
double x2=x*x;
return x*(x2/6*(x2/20*(x2/42*(x2/72*(x2/110*(x2/156-1)+1)-1)+1)-1)+1);
}
public static double Cos(double x)
{
if (x==0) { return 1; }
if (x<0) { return Cos(-x); }
if (x>π) { return -Cos(x-π); }
if (x>π4) { return Sin(π2-x); }
double x2=x*x;
return x2/2*(x2/12*(x2/30*(x2/56*(x2/90*(x2/132-1)+1)-1)+1)-1)+1;
}
典型错误为1e-16
,最差情况为1e-11
。它比CLR更糟糕,但它可以通过添加更多术语来控制。好消息是,对于OP中的特殊情况和Sin(45°)
,答案是准确的。
答案 5 :(得分:1)
我们目前正弦和余弦的实现是
public static double Sin(double d) {
d = d % (2 * Math.PI); // Math.Sin calculates wrong results for values larger than 1e6
if (d == 0 || d == Math.PI || d == -Math.PI) {
return 0.0;
}
else {
return Math.Sin(d);
}
}
public static double Cos(double d) {
d = d % (2 * Math.PI); // Math.Cos calculates wrong results for values larger than 1e6
double multipleOfPi = d / Math.PI; // avoid calling the expensive modulo function twice
if (multipleOfPi == 0.5 || multipleOfPi == -0.5 || multipleOfPi == 1.5 || multipleOfPi == -1.5) {
return 0.0;
}
else {
return Math.Cos(d);
}
}