8

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

static double EPSILON = 1e-6;

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

很标准。

EPSILON然而,当我们遇到“相等”量的误差大于我们预期的情况时,我们经常发现自己不得不调整量级。例如,如果将 5 个大doubles 相乘,然后再除以 5 次,就会损失很多准确度。它已经到了我们不能让 EPSILON 变得更大的地步,否则它会给我们带来误报,但我们仍然会得到误报。

一般来说,我们的方法是寻找更多数值稳定的算法来使用,但该程序的计算量很大,我们能做的只有这么多。

有没有人有任何好的策略来处理这个问题?我对这种Decimal类型进行了一些研究,但我担心性能,而且我对它的了解还不够,不知道它是否会解决问题或只会掩盖问题。如果它可以解决这些问题,我愿意接受适度的性能损失(比如 2 倍)Decimal,但性能绝对是一个问题,并且由于代码主要受浮点运算的限制,我不认为这是一个不合理的担忧。我见过有人引用 100 倍的差异,这绝对是不可接受的。

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

有什么建议吗?

编辑:顺便说一句,我使用常量 epsilon(而不是相对比较)这一事实不是我的问题的重点。我只是把它放在那里作为一个例子,它实际上并不是我的代码片段。更改为相对比较不会对问题产生影响,因为当数字变得非常大然后又变小时,问题就会出现精度下降。例如,我可能有一个值 1000,然后我对其进行一系列计算,结果应该完全相同,但由于精度损失,我实际上有 1001。如果我再去比较这些数字,它不会如果我使用相对或绝对比较并不重要(只要我以对问题和规模有意义的方式定义了比较)。

无论如何,正如 Mitch Wheat 建议的那样,算法的重新排序确实有助于解决问题。

4

3 回答 3

5

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

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

出于兴趣?(如果你还没有读过):每个计算机科学家都应该知道的关于浮点运算的知识

于 2010-02-12T00:32:16.530 回答
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++,那么您还可以使用一些技巧来分别比较尾数和指数,但我想不出任何方法可以在未管理的代码中安全地做到这一点。

于 2010-02-12T01:31:13.577 回答
1

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

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

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

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

于 2010-02-12T00:56:37.320 回答