浮点算法 - 双类型的模算子

时间:2010-05-27 21:47:06

标签: c# floating-point precision

所以我试图找出为什么模运算符返回如此大的异常值。

如果我有代码:

double result = 1.0d % 0.1d;

它会得到0.09999999999999995的结果。我希望值为0

请注意,使用除法运算符不存在此问题 - double result = 1.0d / 0.1d;

会得到10.0的结果,这意味着余下的 应该是0

让我明确一点:我不会对错误存在感到惊讶,我很惊讶这个错误与游戏中的数字相比非常。 0.0999~ = 0.1和0.1与0.1d处于同一数量级,距离1.0d仅一个数量级。它不像你可以将它与double.epsilon进行比较,或者说“如果它的< 0.00001差异就等于它”。

我已经在StackOverflow上阅读了这个主题,在以下帖子中one two three等等。

任何人都可以建议解释为什么这个错误如此之大?任何建议,以避免将来遇到问题(我知道我可以使用十进制,但我担心它的性能)。

编辑:我应该特别指出,我知道0.1是infinitely repeating series of numbers in binary - 这与它有什么关系吗?

4 个答案:

答案 0 :(得分:14)

出现错误是因为double不能精确地表示0.1 - 它最接近的代表是0.100000000000000005551115123126。现在,当你将1.0除以它时,它会给你一个略小于10的数字,但是双倍不能完全代表它,所以它最终会向上舍入到10.但是当你执行mod时,它可以稍微给你一点少于0.1余数。

因为0 = 0.1 mod 0.1,mod中的实际误差为0.1 - 0.09999999 ...... - 非常小。

如果将%运算符的结果添加到9 * 0.1,它将再次给你1.0。

修改

关于舍入内容的更多细节 - 特别是因为这个问题是混合精度危险的一个很好的例子。

通常计算浮点数的a % b方式为a - (b * floor(a/b))。问题是它可以一次性完成,内部精度高于你在这些操作中获得的内部精度(并将结果四舍五入到每个阶段的fp数),因此它可能会给你一个不同的结果。很多人看到的一个例子是英特尔x86 / x87硬件,它使用80位精度进行中间计算,内存值只使用64位精度。所以上面等式中的b中的值来自内存,因此是一个64位的fp数,它不是0.1(感谢dan04的精确值),所以当它计算1.0 / 0.1时它得到9.9999999999999944488848768742172978818416595458984375(四舍五入到80位)。现在,如果你将它舍入到64位,它将是10.0,但如果你保持80位内部并在其上发言,它将截断为9.0,因此得到.0999999999999999500399638918679556809365749359130859375作为最终答案。

所以在这种情况下,你会看到一个很大的明显错误,因为你正在使用一个非连续的阶梯函数(floor),这意味着内部值的微小差异可以推动你超越这一步。但由于mod本身是一个非连续的阶跃函数,这是预期的,这里的实际误差是0.1-0.0999 ...因为0.1是mod函数范围内的不连续点。

答案 1 :(得分:2)

这不是计算中的“错误”,而是你从未真正开始过0.1的事实。

问题是1.0可以用二进制浮点精确表示但0.1不能,因为它不能完全由2的负幂构造。 (这是1/16 + 1/32 + ......)

所以你不是真的得到1.0%0.1,机器剩下计算1.0%0.1 + - 0.00 ......然后它会诚实地报告它得到的结果......

为了有一个大的余数,我想%的第二个操作数必须略微超过0.1,防止最后的除法,并导致几乎整个0.1都是操作的结果。

答案 2 :(得分:2)

0.1无法用二进制表示的事实与它有关。

如果0.1可以表示为double,那么您将获得可表示的双精度(假设“最接近”舍入模式)与您要计算的操作的实际结果。

因为它不能,所以你得到的是最接近一个操作的可表示的双精度,它与你试图计算的操作完全不同。

还要注意/是一个大多数连续函数(参数上的一个小差异通常意味着结果上的一个小差异,虽然导数可能很大但接近零但是在零的同一侧,至少是额外的精度参数帮助)。另一方面,%不是连续的:无论你选择何种精度,总会有一些参数,第一个参数上任意小的表示错误意味着结果出现大的错误。

指定IEEE 754的方式,假设参数完全符合您的要求,您只能得到一个浮点运算结果近似值的保证。如果参数不完全符合您的要求,则需要切换到其他解决方案,例如区间运算或分析程序的良好条件(如果它在浮点数上使用%,则可能不会很好-conditioned)。

答案 3 :(得分:0)

你看到的错误很小;乍一看它看起来很大。 你的结果(在显示四舍五入后)是  0.09999999999999995 == (0.1 - 5e-17)当你预期的时候是0  % 0.1操作。 但请记住,这几乎是0.1,0.1 % 0.1 == 0

所以这里你的实际错误是-5e-17。我会称之为小。

根据您需要的数字,最好写一下:

double result = 1.0 % 0.1; result = result >= 0.1/2 ? result - 0.1 : result;