所以我试图找出为什么模运算符返回如此大的异常值。
如果我有代码:
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 - 这与它有什么关系吗?
答案 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;