如何在使用浮点数时避免浮点误差?

4
我正在尝试通过一些UI按钮来影响3D模型的翻译,使其可以通过0.1或-0.1进行位置移动。
我的模型位置是三维浮点数,所以简单地将0.1f添加到其中一个值会导致明显的舍入误差。虽然我可以使用类似BigDecimal的东西来保留精度,但最终仍然必须将其从float转换回float,结果总是产生一些看起来很乱的傻瓜数字,这让我的UI看起来像个混乱。
我可以美化显示的值,但是随着更多编辑,舍入误差只会变得更糟,它们使我的保存文件变得相当难以阅读。
那么,当我需要使用float时,我该如何避免这些错误呢?

1
多次加减0.1f不会产生巨大的误差。最明显的是单个数字的变化,例如期望结果为4,但在转换为整数时产生了非常微小的值并被截断为3。这可以通过在转换之前进行四舍五入来解决。如果您遇到其他错误,则可能有其他问题。请提供示例数据和代码以说明您遇到的问题。 - Eric Postpischil
最简单的:增加/减少 0.125。 - Thorsten S.
4个回答

2

2
问题报告使用浮点数。将其更改为双精度会比Kahan求和算法提供更多的误差减少,并且更加廉价(在大多数硬件上)。但是,无论哪种方法都无法解决通过截断将非常微小的错误结果转换为整数时产生的错误。 - Eric Postpischil

1
我会使用一个Rational类。有许多类可用 - 这个看起来应该可以工作。
一个显著的成本是当Rational转换为float时,另一个是当分母缩小到gcd时。我发布的一个保持分子和分母在完全缩减状态始终应该是相当有效的,如果你总是在加或减1/10。 此实现保持值规范化(即具有一致的符号),但未缩减。
您应该选择最适合您使用的实现。

0
一个简单的解决方案是使用固定精度,即整数10倍或100倍于所需精度。
float f = 10;
f += 0.1f;

变成

int i = 100;
i += 1;  // use an many times as you like
// use i / 10.0 as required.

在任何情况下,我都不会使用float,因为你会得到比double更多的舍入误差,而几乎没有好处(除非你有数百万个float值)。double提供了8位更高的精度,通过合理的取整,你将不会看到这些误差。


-1

如果你坚持使用浮点数: 避免错误的最简单方法是使用接近所需值的精确浮点数,即

round(2^n * value) * 1/2^n.

n 是位数,value 是要使用的数字(在你的情况下是0.1)

随着精度的提高:

n = 4 => 0.125
n = 8 (byte) => 0.9765625
n = 16 (short)=> 0.100006103516....

长数字链是二进制转换的副产品, 真实数字的位数要少得多。

由于浮点数是精确的,加法和减法不会产生偏移误差, 只要位数不超过浮点数可以保存的值,它们将始终是可预测的。

如果你担心这种解决方案会影响你的显示(因为它们是奇怪的浮点数),只使用整数(步进增加-1/1)并进行存储。 内部设置的最终值是

x = value * step.

由于步长按1的数量增加或减少,精度将保持不变。


2
你的意图是什么?虽然这可以消除加减法中的误差,但在一定范围内,它会增加总误差,因为所使用的步长(.100006…)与所需步长不同。这使得计算结果与期望结果不同。你打算如何获得期望的结果呢? - Eric Postpischil
因为计算的目标通常不在乎它是0.1还是0.100... 在这种情况下,一个3D模型会增加或减少特定的数量。我在积分区间中使用这个技巧,因为如果起始位置和增量间隔是精确的浮点数,那么添加操作会更快、更精确。实际上,我建议使用0.125,因为这意味着通过8个步骤后,我又得到了一个整数值,并且将1.0分成八个步骤并不是不自然的。 - Thorsten S.
这个答案没有详细解释自己。显然,意图是改变步长而不是修正原始建议的步长引起的错误,但是从问题陈述中并不清楚是否可以接受改变步长。显然,这会改变软件的行为:使用更改后的步长,从3到4不再恰好需要十步。也许提问者可以接受,也可能不行。但是答案肯定应该介绍这个提案,解释它正在改变观察到的行为,并解决这种情况的后果。 - Eric Postpischil

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