Python中的浮点相等

时间:2010-06-15 21:15:41

标签: 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

首先我要说的是,我很确定这里发生了什么。我以前在C中看过它,从来没有在Python中看过,但是因为Python在C中实现了,我们才看到它。

我知道浮点数会改变从CPU寄存器到缓存和返回的值。我知道比较两个相等的变量将返回false,如果其中一个被分页而另一个留在寄存器中。

问题

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

旁注

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

修辞问题

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

8 个答案:

答案 0 :(得分:7)

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

说起来容易做起来难。浮点的性质使固定的错误束缚无价值。当值接近0.0时,小的误差界限(如2 * float_epsilon)效果很好,但如果值接近1000则会失败。对于接近0.0的值,大小为1,000,000.0的值的误差范围将过于宽松。

最好的解决方案是了解你的数学领域并根据具体情况选择一个合适的错误。

当这是不切实际的或你是懒惰时,最后位置的单位(ULPs)是一个非常新颖和强大的解决方案。完整的细节非常复杂,您可以阅读更多here

基本思想是这个,浮点数有两个部分,尾数和指数。通常,舍入误差仅会将尾数改为几个步骤。当值接近0.0时,那些步骤正好是float_epsilon。当浮点值接近1,000,000时,步数将接近1。

Google test使用ULP compare floating point numbers。他们为两个浮点数选择了4个ULP的默认值进行比较。您也可以使用他们的代码作为参考来构建您自己的ULP样式浮点比较器。

答案 1 :(得分:6)

不同之处在于,如果您将factors[units_to ]替换为1.0 / 2.54,那么您正在做:

(base_value * 1.0) / 2.54

使用字典,你正在做:

base_value * (1.0 / 2.54)

四舍五入的顺序很重要。如果您这样做,这将更容易看到:

>>> print (((25.4 / 10.0) * 1.0) / 2.54).__repr__()
1.0
>>> print ((25.4 / 10.0) * (1.0 / 2.54)).__repr__()
0.99999999999999989

请注意,没有非确定性或未定义的行为。有一个标准,IEEE-754,实现必须符合(不要声称他们总是)。

我认为应该有一个自动足够接近的替代品。这通常是处理问题的有效方法,但应由程序员决定是否以及如何使用它。

最后,当然有任意精度算术的选项,包括python-gmpdecimal。想想你是否真的需要这些,因为它们确实会对性能产生重大影响。

在常规寄存器和缓存之间移动没有问题。你可能会想到x86的80位extended precision

答案 2 :(得分:4)

首先我要回答你应该阅读David Goldberg的经典What Every Computer Scientist Should Know About Floating-Point Arithmetic

正如其他一些评论员所说,你注意到的差异本质上是由浮点模型引起的,与寄存器,缓存或内存无关。

根据浮点模型,2.54实际上表示为

>>> 2859785763380265 * 2 ** -50
2.54

但这种表述并不确切:

>>> from fractions import Fraction
>>> float(Fraction(2859785763380265, 2 ** 50) - Fraction(254, 100))
3.552713678800501e-17

现在,您正在评估的表达式是:

>>> 25.4 / 10 * (1/2.54)
0.99999999999999989

问题出在1 / 2.54:

>>> Fraction.from_float(1/2.54)
Fraction(1773070719437203, 4503599627370496)

但你期望的是

>>> 1/Fraction.from_float(2.54)
Fraction(1125899906842624, 2859785763380265)

回答你的问题:

  • 是一个难题,但显然是确定性的,没有什么神秘的。
  • 您无法使用 close-enough 比较自动替换相等性。后者要求您指定公差,该公差取决于手头的问题,即您对结果的期望精度。还有很多情况下你真的想要平等而不是足够接近的比较。

答案 3 :(得分:3)

感谢您的回复。大多数都非常好,并提供了良好的链接,所以我只会这样说并回答我自己的问题。

Caspin发布了此link

他还提到Google Tests使用了ULP比较,当我查看谷歌代码时,我看到他们提到了与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

同样有趣的是,当其中一个被写成字符串并重新读回时,相等数字之间有多少个浮点。

>>> # 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

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 equivilent 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.
'''

答案 4 :(得分:2)

如果我运行

x = 0.3+0.3+0.3
if (x != 0.9): print "not equal"
if (x == 0.9): print "equal"

它打印“不相等”这是错误的但是

x-0.9

将浮动错误视为-1.11022302e-16我只做这样的事情:

if (x - 0.9 < 10**-8): print "equal (almost)"

否则你可以将两者都转换成字符串,我想:

if (str(x) == str(0.9)): print "equal (strings)"

答案 5 :(得分:1)

  

避免问题的最佳方法是什么   像这样?...在​​Python或一般情况下。

有什么问题?你正在进行物理测量。除非你有一些真正复杂的设备,否则测量中的误差将比浮点epsilon高几个数量级。那么为什么要编写依赖于数字精确到16位有效数字的代码呢?

  

编译器是否应该支持选项   用。替换所有浮点等式检查   一个'足够接近'的功能?

如果确实如此,你会得到一些奇怪的结果:

>>> float.tolerance = 1e-8    # hypothetical "close enough" definition
>>> a = 1.23456789
>>> b = 1.23456790
>>> c = 1.23456791
>>> a == b
True
>>> b == c
True
>>> a == c
False

如果您认为现在很难将浮点数存储在字典中,请使用非传递==运算符进行尝试!性能会很糟糕,因为保证x == yhash(x) == hash(y)的唯一方法是让每个浮点数具有相同的哈希码。这与整数不一致。

答案 6 :(得分:0)

为了比较浮点数,通常将浮点数的差值的绝对值与选择的delta值进行比较,该值小到足以满足您的需求。

修辞问题

  • 这个**是一个危险的问题**,因为它可能会隐藏错误或产生无限循环,如果这样的比较被用作停止标准。
  • 现代C / C ++编译器警告比较浮点数是否相等
  • 我知道的所有静态代码检查器都会为我使用的语言输出错误

我认为python是一样的,因为用于比较的delta可能会有所不同,必须由实现者来选择它。这意味着无法完全自动提供良好的默认转换。

答案 7 :(得分:0)

  

同样有趣的是那里有多少个浮点   当其中一个是时,它们之间是相等的数字   写成一个字符串并回读。

这可以说是一个Python错误。这个号码只写了十二位数。两个唯一标识64位双精度(Python的浮点类型),你需要十七位数的尾数。如果Python以17位精度打印出数字,那么你可以保证得到完全相同的值。

精确度问题在以下讨论: http://randomascii.wordpress.com/2012/03/08/float-precisionfrom-zero-to-100-digits-2/

焦点在于32位浮点数(需要9位尾数来唯一标识每个数字),但它简单地提到了双重,以及需要17位尾数的事实。