处理.NET中的浮点错误

时间:2010-02-12 00:10:45

标签: .net floating-point numerical

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

static double EPSILON = 1e-6;

bool ApproxEquals(double d1, double d2) {
    return Math.Abs(d1 - d2) < EPSILON;
}

很标准。

但是,我们经常发现自己必须调整EPSILON的大小,因为我们遇到的情况是“相等”数量的误差大于我们的预期。例如,如果将5个大的double乘以5然后再划分,则会失去很多准确性。它已经达到了我们无法使EPSILON过大的程度,或者它会给我们带来误报,但我们仍然会得到假阴性。

一般来说,我们的方法是寻找更多数值稳定的算法,但该程序非常计算,而且我们能够做到这一点。

有没有人有任何好的策略来处理这个问题?我稍微研究了Decimal类型,但我对性能感到担忧,我对它的了解并不是很清楚它是否会解决问题或者只是模糊它。如果它能解决这些问题,我愿意接受Decimal的中等性能命中(比如2x),但性能肯定是一个问题,因为代码主要受浮点运算的限制,我不要认为这是一个无理的担忧。我看到人们引用了100倍的差异,这肯定是不可接受的。

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

有什么建议吗?

编辑:顺便说一下,我使用常数epsilon(而不是相对比较)的事实不是我的问题。我只是把它作为一个例子,它实际上不是我的代码的snippit。改变相对比较不会对问题产生影响,因为问题是由于数字变得非常大而后来又变小而失去精确度。例如,我可能有一个值1000,然后我对它进行一系列计算,这将导致完全相同的数字,但由于精度损失,我实际上有1001.如果我然后去比较这些数字,它不会如果我使用相对或绝对比较,那就更重要了(只要我以对问题和比例有意义的方式定义比较)。

无论如何,正如Mitch Wheat所说,重新排序算法确实有助于解决这些问题。

3 个答案:

答案 0 :(得分:5)

这不是.NET特有的问题。降低精度损失的策略是重新排序计算,以便将大量乘以小数量并加/减相似大小的数量(显然不会改变计算的性质)。

在你的例子中,不是将5个大数量相乘,然后除以5个大数量,重新排序以将每个大数量除以其中一个除数,然后将这5个部分结果相乘。

有兴趣吗? (如果您还没有阅读过):What Every Computer Scientist Should Know About Floating-Point Arithmetic

答案 1 :(得分:2)

当然,你最好的答案总是更好的算法。但在我看来,如果你的价值不在1的几个数量级之内,那么使用固定的epsilon并不是一个好策略。你想要做的是确保值在一定的合理精度内等于。

// 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);
}

如果这是C ++,那么你也可以分别用一些技巧来比较尾数和指数,但我想不出任何方法可以在无人值守的代码中安全地做到这一点。

答案 2 :(得分:1)

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

if (llabs(*(long long)&x - *(long long)&y) <= EPSILON) {
    // Close enough
}

这显然是不可移植的,可能是一个坏主意,但它具有规模独立性的显着优势。也就是说,EPSILON可以是一些小常数,如1,10或100(取决于您所需的公差),它将正确处理比例舍入误差,无论指数如何。

免责声明:这是我自己的私人发明,并没有任何有线索的人(例如,具有离散算术背景的数学家)审查。