eri*_*ich 16 python floating-point equality
我有一段代码的行为不同,这取决于我是否通过字典获取转换因子或我是否直接使用它们.
将打印以下代码 1.0 == 1.0 -> False
但是,如果你更换factors[units_from]用10.0,并factors[units_to ]用1.0 / 2.54它将打印1.0 == 1.0 -> True
#!/usr/bin/env python
base = 'cm'
factors = {
'cm' : 1.0,
'mm' : 10.0,
'm' : 0.01,
'km' : 1.0e-5,
'in' : 1.0 / 2.54,
'ft' : 1.0 / 2.54 / 12.0,
'yd' : 1.0 / 2.54 / 12.0 / 3.0,
'mile' : 1.0 / 2.54 / 12.0 / 5280,
'lightyear' : 1.0 / 2.54 / 12.0 / 5280 / 5.87849981e12,
}
# convert 25.4 mm to inches
val = 25.4
units_from = 'mm'
units_to = 'in'
base_value = val / factors[units_from]
ret = base_value * factors[units_to ]
print ret, '==', 1.0, '->', ret == 1.0
Run Code Online (Sandbox Code Playgroud)
我先说我很确定这里发生了什么.我以前在C中看过它,从来没有在Python中看到它,但是因为Python在C中实现,我们才看到它.
我知道浮点数会改变从CPU寄存器到缓存和返回的值.我知道比较两个相等的变量将返回false,如果其中一个被分页而另一个留在寄存器中.
问题
边注
这显然是一个被剥离的例子的一部分,但我想要做的是带有长度,体积等类,可以与同一类但具有不同单位的其他对象进行比较.
修辞问题
如已经示出的,比较两个浮子(或双打等)可能是有问题的.通常,不应比较精确相等,而应根据错误限制对其进行检查.如果它们在误差范围内,则认为它们是相等的.
这说起来容易做起来难.浮点的性质使固定的错误束缚无价值.当值接近0.0时,小的误差界限(如2*float_epsilon)效果很好,但如果值接近1000则会失败.对于接近0.0的值,大小为1,000,000.0的值的误差范围太大.
最好的解决方案是了解您的数学领域,并根据具体情况选择一个合适的错误.
当这是不切实际的或你是懒惰的时候,最后的位置(ULP)是一个非常新颖和强大的解决方案.完整的细节非常复杂,你可以在这里阅读更多.
基本思路是这样,浮点数有两个部分,尾数和指数.通常,舍入误差仅将尾数改变几步.当值接近0.0时,这些步骤正好是float_epsilon.当浮点值接近1,000,000时,步数将接近1.
Google测试使用ULP来比较浮点数.他们为两个浮点数选择了4个ULP的默认值进行比较.您也可以使用他们的代码作为参考来构建您自己的ULP样式浮点比较器.
所不同的是,如果要更换factors[units_to ]有1.0 / 2.54,你正在做的:
(base_value * 1.0) / 2.54
Run Code Online (Sandbox Code Playgroud)
有了字典,你正在做:
base_value * (1.0 / 2.54)
Run Code Online (Sandbox Code Playgroud)
四舍五入的顺序很重要.如果您这样做,这将更容易看到:
>>> print (((25.4 / 10.0) * 1.0) / 2.54).__repr__()
1.0
>>> print ((25.4 / 10.0) * (1.0 / 2.54)).__repr__()
0.99999999999999989
Run Code Online (Sandbox Code Playgroud)
请注意,没有非确定性或未定义的行为.有一个标准,IEEE-754,实现必须符合(不要声称他们总是这样做).
我不认为应该有一个自动足够接近的替代品.这通常是解决问题的有效方法,但应该由程序员决定是否以及如何使用它.
最后,当然有任意精度算术的选项,包括python-gmp和decimal.想一想你是否确实需要这些,因为它们确实会对性能产生重大影响.
在常规寄存器和缓存之间移动没有问题.你可能会想到x86的80位扩展精度.
感谢您的回复。大多数都非常好并且提供了很好的链接,所以我就这么说并回答我自己的问题。
Caspin 发布了此链接。
他还提到 Google 测试使用了 ULP 比较,当我查看 google 代码时,我看到他们提到了与 Cygnus 软件相同的链接。
我最终用 C 实现了一些算法作为 Python 扩展,后来发现我也可以用纯 Python 来实现。代码发布在下面。
最后,我可能最终会将 ULP 差异添加到我的技巧包中。
有趣的是,看看两个永远不会离开内存的相等数字之间有多少浮点。我读过的一篇文章或谷歌代码说 4 是一个很好的数字......但在这里我能够达到 10。
>>> f1 = 25.4
>>> f2 = f1
>>>
>>> for i in xrange(1, 11):
... f2 /= 10.0 # To cm
... f2 *= (1.0 / 2.54) # To in
... f2 *= 25.4 # Back to mm
... print 'after %2d loops there are %2d doubles between them' % (i, dulpdiff(f1, f2))
...
after 1 loops there are 1 doubles between them
after 2 loops there are 2 doubles between them
after 3 loops there are 3 doubles between them
after 4 loops there are 4 doubles between them
after 5 loops there are 6 doubles between them
after 6 loops there are 7 doubles between them
after 7 loops there are 8 doubles between them
after 8 loops there are 10 doubles between them
after 9 loops there are 10 doubles between them
after 10 loops there are 10 doubles between them
Run Code Online (Sandbox Code Playgroud)
同样有趣的是,当其中一个作为字符串写出并读回时,相等的数字之间有多少个浮点。
>>> # 0 degrees Fahrenheit is -32 / 1.8 degrees Celsius
... f = -32 / 1.8
>>> s = str(f)
>>> s
'-17.7777777778'
>>> # Floats between them...
... fulpdiff(f, float(s))
0
>>> # Doubles between them...
... dulpdiff(f, float(s))
6255L
Run Code Online (Sandbox Code Playgroud)
import struct
from functools import partial
# (c) 2010 Eric L. Frederich
#
# Python implementation of algorithms detailed here...
# From http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
def c_mem_cast(x, f=None, t=None):
'''
Do a c-style memory cast
In Python...
x = 12.34
y = c_mem_cast(x, 'd', 'l')
... should be equivalent to the following in c...
double x = 12.34;
long y = *(long*)&x;
'''
return struct.unpack(t, struct.pack(f, x))[0]
dbl_to_lng = partial(c_mem_cast, f='d', t='l')
lng_to_dbl = partial(c_mem_cast, f='l', t='d')
flt_to_int = partial(c_mem_cast, f='f', t='i')
int_to_flt = partial(c_mem_cast, f='i', t='f')
def ulp_diff_maker(converter, negative_zero):
'''
Getting the ULP difference of floats and doubles is similar.
Only difference if the offset and converter.
'''
def the_diff(a, b):
# Make a integer lexicographically ordered as a twos-complement int
ai = converter(a)
if ai < 0:
ai = negative_zero - ai
# Make b integer lexicographically ordered as a twos-complement int
bi = converter(b)
if bi < 0:
bi = negative_zero - bi
return abs(ai - bi)
return the_diff
# Double ULP difference
dulpdiff = ulp_diff_maker(dbl_to_lng, 0x8000000000000000)
# Float ULP difference
fulpdiff = ulp_diff_maker(flt_to_int, 0x80000000 )
# Default to double ULP difference
ulpdiff = dulpdiff
ulpdiff.__doc__ = '''
Get the number of doubles between two doubles.
'''
Run Code Online (Sandbox Code Playgroud)