我首先在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
这里发生了什么事情。我想念什么?
我首先在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
这里发生了什么事情。我想念什么?
这个结果是由于机器浮点表示。在你的方法中,你正在将float转换为int,并且没有这个问题。避免此类问题的“最佳”方法(尤其是mod
)是乘以一个足够大的int(在您的情况下只需要10个)并再次执行操作。
FMOD(1.0,0.1)
FMOD(10.0,1.0)= 0
我在哪里做“有点'铸造'”?如果'我的'方法给出正确答案为什么其他方法使用其他方法? – beemtee 2010-11-18 20:14:26
python中没有像C++中那样的“铸造”(因此也是如此)。当您将您的号码“发言”时,您实际上使浮点成为整数。斯蒂芬佳能有一个更详细的答案,告诉你它[我]确实给出了正确的答案,而不是你期望的答案... – g19fanatic 2010-11-18 20:19:30
因为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
的表示错误。
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
。
从man fmod
:
的FMOD()函数计算x除以y 的浮点余数。返回值是x - n * y,其中n是x/y, 向零整数的整数的商。
那么什么情况是:
fmod(1.0, 0.1)
,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
谢谢!这是明确而明确的答案。剩下的唯一问题就是为什么手工定义的功能不会遇到这个问题? – beemtee 2010-11-18 20:40:07
不,它不是FPU的怪癖,而是手写算法涉及四舍五入的结果,而'fmod'函数总是提供确切的答案。查看我的答案了解更多详情。还要注意''fmod'的具体实现是针对你的平台的数学库而不是硬件的(尽管许多x86数学库使用'fprem'指令,而不是全部)。 – 2010-11-18 22:14:05
@Stephen佳能:当然你是对的。我不知何故错过了额外的'fdiv'步骤导致更多的四舍五入。 – adw 2010-11-18 22:49:16
这给正确的答案:
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
我认为它的工作原理,因为它使用提供了更准确的答案的两个浮点数的合理等价物。
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
顺便说一句,Wolfram Alpha也可以得到这个答案,如果你用精确的分数而不是浮点数来表达它:http://www.wolframalpha.com/input/?i=1+mod+1%2F10 – 2010-11-18 22:09:49
这里返回0.09999999999999995(OS X,Python 2.5.4)。答案取决于你的平台的C库。 – geoffspear 2010-11-18 20:02:23
好的,抱歉,我做了舍入。无论如何,我的理解应该是0,因为1.0/0.1 = 10这是一个整数。 – beemtee 2010-11-18 20:05:31