如何避免一些四舍五入误差?

3

我在.NET中有一个处理地理坐标的方法,还有一个结构体用于存储坐标对,以便如果一个坐标参数传入256,则变为0。然而,在一个特定的实例中,计算出一个值约为255.99999998,并因此存储在结构体中。当它在ToString()中打印时,它变成了256,这不应该发生 - 256应该是0。如果它打印255.9999998,我也无所谓,但调试器显示255.99999998时它打印256就是个问题。既存储又显示为0会更好。

具体来说,比较存在问题。255.99999998足够接近256,以至于它应该等于256。当比较双精度浮点数时,应该怎么办?使用某种ε值吗?


编辑:具体来说,我的问题是,我取一个值,执行一些计算,然后对该数字执行相反的计算,并且需要精确地返回原始值。


使用epsilon是比较浮点数值的唯一合法方法。 abs(a - b) <= epsilon - Andrey
使用“==”比较两个浮点数值是完全合法的,因此几乎没有编译器会报错。然而,即使是微小的舍入误差也可能导致这两个值在数学上应该相等但实际不相等,因此如果你重视自己的理智,不建议使用这种方法。 - cHao
如果您真的只需要恢复原始值,难道不能将其存储下来,或将一些关于原始计算过程的信息与数字一起存储吗? - Victor Liu
4个回答

3
这似乎是数字打印的问题,而不是存储问题。一个双精度变量有大约15个有效数字,因此可以轻松区分255.99999998和256的差别。

1

您可以选择格式字符串,以便根据需要显示数字的任意部分。

比较双精度浮点数相等的常规方法是将它们相减并查看绝对值是否小于某个预定义的 epsilon 值,例如 0.000001。


使用 R 格式说明符而不是默认的 G 使它显示正确的值。 - Jake Petroules

1

你可以使用epsilon方法,但epsilon通常是一种权宜之计,用于解决浮点运算的精度损失问题。

你可以考虑完全避免使用二进制浮点数,而是使用一个好的有理数类。

如果你使用有理数类型,上面的计算可能本来应该是256,因为你会得到无损算术。

有理数类型也可以被称为比率或分数类,并且编写起来相当简单。

这里有一个例子。 这里还有另一个


编辑....

为了理解您的问题,请考虑当十进制值0.01转换为二进制表示时,它无法在有限的内存中精确存储。这个值的十六进制表示是0.028F5C28F5C,其中“28F5C”无限重复。因此,即使在进行任何计算之前,仅通过将0.01存储为二进制格式,您就会失去精确性。

有理数和小数类用于克服这个问题,尽管存在性能成本。有理数类型通过存储分子和分母来表示您的值,避免了这个问题。小数类型使用二进制编码的十进制格式,可以在除法方面出现损失,但可以精确地存储常见的小数值。

对于您的目的,我仍然建议使用有理数类型。


0

你必须自己决定两个值相等的阈值。这相当于使用所谓的定点数(与浮点数相对)。然后,您必须手动执行舍入操作。

如果有可用的话,我会选择一些已知大小的无符号类型(例如uint32或uint64,我不知道.NET),并将其视为模256的定点数类型。

例如:

typedef uint32 fixed;

inline fixed to_fixed(double d)
{
    return (fixed)(fmod(d, 256.) * (double)(1 << 24))
}

inline double to_double(fixed f)
{
    return (double)f / (double)(1 << 24);
}

或者更详细地说,以适应舍入约定(最近,向下,向上,奇数,偶数)。 固定点数的最高8位保留整数部分,24个较低位保留小数部分。 绝对精度为2^{-24}。

请注意,这些数字的加减自然地在256处循环。 对于乘法,您应该小心。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接