Python中的浮点相等

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,如果其中一个被分页而另一个留在寄存器中.

问题

  • 避免这样的问题的最佳方法是什么?...在​​Python或一般情况下.
  • 我做错了什么吗?

边注

这显然是一个被剥离的例子的一部分,但我想要做的是带有长度,体积等类,可以与同一类但具有不同单位的其他对象进行比较.

修辞问题

  • 如果这是一个潜在的危险问题,因为它使程序在一个非人类的问题上表现,编译器在发现你正在检查浮点数是否相等时会发出警告或错误
  • 编译器是否应支持用"足够接近"的函数替换所有浮点等式检查的选项?
  • 编译器是否已经这样做了,我找不到相关信息.

def*_*ode 7

如已经示出的,比较两个浮子(或双打等)可能是有问题的.通常,不应比较精确相等,而应根据错误限制对其进行检查.如果它们在误差范围内,则认为它们是相等的.

这说起来容易做起来难.浮点的性质使固定的错误束缚无价值.当值接近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样式浮点比较器.


Mat*_*hen 6

所不同的是,如果要更换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-gmpdecimal.想一想你是否确实需要这些,因为它们确实会对性能产生重大影响.

在常规寄存器和缓存之间移动没有问题.你可能会想到x86的80位扩展精度.


eri*_*ich 4

感谢您的回复。大多数都非常好并且提供了很好的链接,所以我就这么说并回答我自己的问题。

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)