2010-11-18 82 views
4

我首先在Python中经历了这种现象,但事实证明这是常见的答案,例如MS Excel给出了这个结论。 Wolfram Alpha给出了一个有趣的分裂的答案,它指出零的有理逼近是1/5。 (1.0 mod 0.1为什么fmod(1.0,0.1)== .1?

另一方面,如果我手工实现定义,它给了我'正确的'答案(0)。

def myFmod(a,n): 
    return a - floor(a/n) * n 

这里发生了什么事情。我想念什么?

+3

这里返回0.09999999999999995(OS X,Python 2.5.4)。答案取决于你的平台的C库。 – geoffspear 2010-11-18 20:02:23

+0

好的,抱歉,我做了舍入。无论如何,我的理解应该是0,因为1.0/0.1 = 10这是一个整数。 – beemtee 2010-11-18 20:05:31

回答

1

这个结果是由于机器浮点表示。在你的方法中,你正在将float转换为int,并且没有这个问题。避免此类问题的“最佳”方法(尤其是mod)是乘以一个足够大的int(在您的情况下只需要10个)并再次执行操作。

FMOD(1.0,0.1)

FMOD(10.0,1.0)= 0

+0

我在哪里做“有点'铸造'”?如果'我的'方法给出正确答案为什么其他方法使用其他方法? – beemtee 2010-11-18 20:14:26

+0

python中没有像C++中那样的“铸造”(因此也是如此)。当您将您的号码“发言”时,您实际上使浮点成为整数。斯蒂芬佳能有一个更详细的答案,告诉你它[我]确实给出了正确的答案,而不是你期望的答案... – g19fanatic 2010-11-18 20:19:30

22

因为0.1不是0.1;该值是不是在双精度所能表述的,所以它被四舍五入到最接近的双精度数,而这正是:

0.1000000000000000055511151231257827021181583404541015625 

当你调用fmod,您可以通过上面列出的数值得到的余数,这正是:

0.0999999999999999500399638918679556809365749359130859375 

这轮以0.1(或者0.09999999999999995)当您打印。

换句话说,fmod完美地工作,但你没有给你它认为你的输入。


编辑:自己的实现为您提供了正确的答案,因为它是不太准确,信不信由你。首先,请注意fmod计算余数时没有任何舍入误差;唯一的不准确来源是使用值0.1引入的表示错误。现在,我们来看看您的实现,看看它发生的舍入误差是如何消除表示误差的。

评估a - floor(a/n) * n一个步骤的时间,保持跟踪在每一个阶段中计算的精确值的:

首先,我们评估1.0/n,其中n是最接近双精度近似0.1如上所示。这种划分的结果大约是:

9.999999999999999444888487687421760603063276150363492645647081359... 

注意,此值不是一个表示的双精度数 - 所以它得到四舍五入。看到这个圆是如何发生的,让我们来看看数字的二进制,而不是十进制的:

1001.1111111111111111111111111111111111111111111111111 10110000000... 

空间指示了舍入到双精度发生。由于圆点之后的部分大于确切的中途点,因此该值完全凑到10

floor(10.0)可以预见的是10.0。所以剩下的就是计算1.0 - 10.0*0.1

在二进制的10.0 * 0.1的确切值是:

1.0000000000000000000000000000000000000000000000000000 0100 

再次,该值不表示为一个双,所以在由一个空间表示的位置倒圆。这一次它下降到正确的1.0,所以最终的计算是1.0 - 1.0,这当然是0.0

您的实现包含两个舍入错误,在这种情况下恰好抵消了值0.1的表示错误。 fmod,相反,是总是确切(至少在具有良好数字库的平台上),并且暴露0.1的表示错误。

+0

这并不能解释为什么'1.0 - floor(1.0/0.1) * 0.1'虽然给零。 – kennytm 2010-11-18 20:27:51

+0

是的,我明白了。 1.0可以完全由double表示,而0.1不能。由于最接近的可表示数大于原始数,所以除法结果将类似9.99999999999,在截断后变为9。 – beemtee 2010-11-18 20:36:23

+0

@KennyTM:为您解答后续问题。 – 2010-11-18 22:00:41

0

fmod返回x-i * y,它小于y,并且i是整数。 0.09 ....是因为浮点精度。尝试fmod(0.3, 0.1) -> 0.09...fmod(0.4, 0.1) -> 0.0因为0.3是0.2999999 ...作为浮点数。

fmod(1/(2.**n), 1/(2.**m)对于整数n> = m将不会产生任何内容,但0.0

1

man fmod

的FMOD()函数计算x除以y 的浮点余数。返回值是x - n * y,其中n是x/y, 向零整数的整数的商。

那么什么情况是:

  1. fmod(1.0, 0.1),0.1实际上比0.1稍大,因为该值不能精确表示为浮动。
  2. 因此n = X/Y = 1.0/0.1000something = 9.9999something
  3. 当向0舍入,正实际上变成9
  4. X - N * Y = 1.0 - 9 * 0.1 = 0.1

编辑:至于它为什么与floor(x/y)一起工作,据我所知这似乎是一个FPU怪癖。在x86上,fmod使用fprem指令,而x/y将使用fdiv。奇怪的是1.0/0.1似乎正是10.0返回:

>>> struct.pack('d', 1.0/0.1) == struct.pack('d', 10.0) 
True 

我想fdiv采用了更精确的算法比fprem。有些讨论可以在这里找到:http://www.rapideuphoria.com/cgi-bin/esearch.exu?thread=1&fromMonth=A&fromYear=8&toMonth=C&toYear=8&keywords=%22Remainder%22

+0

谢谢!这是明确而明确的答案。剩下的唯一问题就是为什么手工定义的功能不会遇到这个问题? – beemtee 2010-11-18 20:40:07

+0

不,它不是FPU的怪癖,而是手写算法涉及四舍五入的结果,而'fmod'函数总是提供确切的答案。查看我的答案了解更多详情。还要注意''fmod'的具体实现是针对你的平台的数学库而不是硬件的(尽管许多x86数学库使用'fprem'指令,而不是全部)。 – 2010-11-18 22:14:05

+0

@Stephen佳能:当然你是对的。我不知何故错过了额外的'fdiv'步骤导致更多的四舍五入。 – adw 2010-11-18 22:49:16

0

这给正确的答案:

a = 1.0 
b = 0.1 

a1,a2 = a.as_integer_ratio() 
b1,b2 = b.as_integer_ratio() 
div = float(a1*b2)/float(a2*b1) 
mod = a - b*div 
print mod 
# 0.0 

我认为它的工作原理,因为它使用提供了更准确的答案的两个浮点数的合理等价物。

0

Python divmod函数在这里很有指导意义。它会告诉你一个除法操作的商和余数。

$ python 
>>> 0.1 
0.10000000000000001 
>>> divmod(1.0, 0.1) 
(9.0, 0.09999999999999995) 

当键入0.1时,计算机不能代表二进制浮点算术,准确的值,所以它选择,它可以代表,0.10000000000000001最接近数。然后当你执行除法运算时,由于0.10000000000000001 * 10大于1.0,所以浮点运算决定商必须为9。这会使余数略小于0.1。

我想使用新的Python fractions模块来得到确切的答案。

>>> from fractions import Fraction 
>>> Fraction(1, 1) % Fraction(1, 10) 
Fraction(0, 1) 

IOW,(1/1) mod (1/10) = (0/1),这相当于1 mod 0.1 = 0

另一种选择是自己实现模数运算符,允许您指定自己的策略。

>>> x = 1.0 
>>> y = 0.1 
>>> x/y - math.floor(x/y) 
0.0 
+0

顺便说一句,Wolfram Alpha也可以得到这个答案,如果你用精确的分数而不是浮点数来表达它:http://www.wolframalpha.com/input/?i=1+mod+1%2F10 – 2010-11-18 22:09:49