处理.NET中的浮点错误

处理.NET中的浮点错误,.net,floating-point,numerical,.net,Floating Point,Numerical,我在C#/.NET上做一个科学计算和可视化项目,我们使用doubles来表示所有的物理量。由于浮点数总是包含一些舍入,我们有一些简单的方法来进行相等比较,例如: static double EPSILON = 1e-6; bool ApproxEquals(double d1, double d2) { return Math.Abs(d1 - d2) < EPSILON; } 静态双ε=1e-6; 布尔近似相等(双d1,双d2){ 返回Math.Abs(d1-d2)

我在C#/.NET上做一个科学计算和可视化项目,我们使用
double
s来表示所有的物理量。由于浮点数总是包含一些舍入,我们有一些简单的方法来进行相等比较,例如:

static double EPSILON = 1e-6;

bool ApproxEquals(double d1, double d2) {
    return Math.Abs(d1 - d2) < EPSILON;
}
静态双ε=1e-6;
布尔近似相等(双d1,双d2){
返回Math.Abs(d1-d2)
相当标准

然而,当我们遇到“相等”量的误差大于我们预期的情况时,我们经常发现自己不得不调整ε的大小。例如,如果将5个大的
double
s相乘,然后再除以5次,则会损失很多精度。它已经到了一个地步,我们不能使ε太大,否则它会给我们假阳性,但我们仍然会得到假阴性

一般来说,我们的方法是寻找数值上更稳定的算法,但这个程序计算性很强,我们能做的也就这么多

有人有什么好的策略来处理这个问题吗?我已经研究了一下
Decimal
类型,但是我担心性能,我对它的了解还不够,不知道它是否能解决问题,或者只是模糊了问题。如果能解决这些问题,我愿意通过使用
Decimal
来接受适度的性能损失(比如2x),但性能肯定是一个问题,因为代码主要受浮点运算的限制,我不认为这是一个不合理的问题。我见过有人引用100倍的差异,这绝对是不可接受的

此外,切换到
Decimal
还有其他复杂问题,例如
Math
库中普遍缺乏支持,因此我们必须编写自己的平方根函数

有什么建议吗

编辑:顺便说一句,我使用的是常数ε(而不是相对比较),这不是我问题的重点。我只是把它作为一个例子,它实际上并不是我代码的一部分。改变为相对比较不会对问题产生影响,因为当数字变得非常大,然后又变小时,问题产生于失去精度。例如,我可能有一个值1000,然后我对它做了一系列计算,结果应该是完全相同的数字,但由于精度的损失,我实际上有1001。如果我接着比较这些数字,那么我是否使用相对或绝对比较并不重要(只要我以对问题和规模有意义的方式定义了比较)


无论如何,正如米奇·麦特(Mitch Wheat)所说,算法的重新排序确实有助于解决问题。

这不是.NET独有的问题。减少精度损失的策略是重新排列计算顺序,以便将大数量乘以小数量,并添加/减去类似大小的数量(显然,不改变计算的性质)

在您的示例中,与其将5个大数量相乘,然后除以5个大数量,不如重新排序,将每个大数量除以其中一个除数,然后将这5个部分结果相乘


有兴趣吗?(如果您还没有读过):

由于实数的典型表示方式,您可以在C中(可能在不安全的C中)执行此操作:


if(LLAB(*(长)和x-*(长)和y)当然,你最好的答案总是更好的算法。但在我看来,如果你的值不都在1的几个数量级内,那么使用固定ε不是一个好策略。你要做的是确保这些值在合理的精度范围内相等

// are values equal to within 12 (or so) digits of precision?
//
bool ApproxEquals(double d1, double d2) {
    return Math.Abs(d1 - d2) < (Math.Abs(d1) * 1e-12);
}
//值是否等于精度的12位左右?
//
布尔近似相等(双d1,双d2){
返回数学Abs(d1-d2)<(数学Abs(d1)*1e-12);
}

如果这是C++,那么你也可以单独使用一些技巧来比较尾数和指数,但是我想不出有什么办法能安全地在无芒代码中做到这一点。技术?Epsilon通常必须按您希望处理的数字的大小成比例缩放,这使得它容易受到实际数据大小的意外影响。这正是问题中所述的问题。比较数字的整数表示形式不容易受到此类缩放问题的影响(请注意,我的答案中ε的选择是由期望的容差决定的,而不是由预期的输入规模决定的)。@John Knoeller的答案是最好的安全和可移植的答案,但它更昂贵(尽管可能略微如此).嗯,我一直在研究我们的算法,并按照你的建议重新排序,这似乎有所帮助。我已经开始努力阅读那篇文章,尽管它不是很轻松!谢谢你的帮助。@High Performance Mark:相对/绝对比较的问题不是我要问的问题。请参阅我文章的编辑部分问题。正如我在问题的编辑中所添加的,这并没有真正解决我的问题。但当对数量级不同的数字进行比较时,相对比较确实是合适的。

// are values equal to within 12 (or so) digits of precision?
//
bool ApproxEquals(double d1, double d2) {
    return Math.Abs(d1 - d2) < (Math.Abs(d1) * 1e-12);
}