在比较浮点数与整数时,需要更长的时间要比其他类似规模的值计算某些成对的数值。

例如︰

>>> import timeit
>>> timeit.timeit("562949953420000.7 < 562949953421000") # run 1 million times
0.5387085462592742

但如果一定量小或更大,由浮点数或整数,比较运行速度更快︰

>>> timeit.timeit("562949953420000.7 < 562949953422000") # integer increased by 1000
0.1481498428446173
>>> timeit.timeit("562949953423001.8 < 562949953421000") # float increased by 3001.1
0.1459577925548956

更改比较运算符 (例如使用==>相反) 在任何显而易见的方式不会影响时间。

这不是仅仅因为挑选更大或更小的值会导致更快的比较,所以我怀疑它是某种不幸的方式位对齐到相关量。

显然,对这些值进行比较是多足够快的速度对于大多数使用情况。我只是好奇,为什么 Python 似乎更苦恼某些成对的值与其他人比。

2015-05-07 12:11:08
问题评论:

很 2.7 和 3.x 中的相同?

上面的计时从 Python 3.4-我运行 2.7 的 Linux 计算机上发生类似的差异 (3,4-和-a-位时间较慢) 之间的计时。

感谢您的有趣的撰写。我很好奇什么提出了问题-是您随便计时比较或有它背后的故事吗?

@Veedrac︰ 谢谢您。没有太多的故事︰ 我想知道 absently-mindedly 是浮点数和整数多快的速度进行比较、 定时几个值和注意到一些 smallish 的差异。然后我 realised 我必须绝对不知道 Python 的管理方式要精确地比较浮点数和较大的整数。我花了一段时间试图了解源、 学到什么是最坏的情况。

@YvesDaoust︰ 没有这些特定的值,则不 (,本应该是令人难以置信的幸运 !)。已尝试使用不同的值对,并注意到较小的差异 (例如比较类似的整数,而不是很大的整数与小型规模的浮点数) 的排练时间。我学习了 2 ^ 查看的源来了解如何比较工作之后才 49 例。因为它们在最令人信服的方式展示主题问题中选择值。

回答:

浮动对象的 Python 源代码中的注释承认:

比较是非常痛苦

因为与浮点数,在 Python 中的整数可以任意大,并且总是精确比较的浮点数,整数,为时更是如此。尝试强制转换为浮点值的整数可能会丢失精度,进行比较不准确。尝试强制转换为整数的浮点数不会起任何作用,因为任何小数部分都将丢失。

若要避免此问题,Python 执行一系列检查,如果检查成功,则返回结果。它比较两个值的符号整数是否"太大",然后为浮点数,然后将浮点长度的整数的指数进行比较。如果所有这些检查失败,则必须构造两个新的 Python 对象进行比较以获得结果。

在比较浮点数vw整数/long 类型的值,最坏的情况是︰

  • vw具有相同的符号 (正或同时为负),
  • w的整数具有一些可以足够位保留在size_t类型 (通常 32 或 64 位)
  • w的整数已至少 49 位,
  • 浮点型v的指数就是w中的比特数.

这也正是我们所拥有的问题中的值︰

>>> import math
>>> math.frexp(562949953420000.7) # gives the float's (significand, exponent) pair
(0.9999999999976706, 49)
>>> (562949953421000).bit_length()
49

我们发现 49 浮点数和整数位数的指数。这两个数字均为正值,因此满足上面的四个条件。

选择一个要大 (或更小) 的值可以更改位的整数或指数的值的数量再也 Python 可以不执行昂贵的最终检查确定比较的结果。

这是特定于语言的 CPython 实现。


更详细地比较

float_richcompare函数处理两个值vw之间的比较.

以下是该函数执行检查的分步说明。Python 源代码中的注释试图了解函数做什么,所以我让他们在相关时实际上非常有帮助。我已经将这些答案的支脚在列表中检查其进行了汇总。

主要思想是将 Python 对象vw映射到两个相应的 C 双精度、 ij,然后可很容易地比较提供正确的结果。Python 2 和 Python 3 使用同一个想法要这样做 (前者只处理intlong类型分开)。

第一个办法就是检查v无疑是 Python 浮点数,并将其映射到 C 双i下一步该函数查看是否w也是浮点数,并将其映射到 C 双j这是该函数的最佳用例方案,可以跳过所有其他检查。该函数还将检查vinfnan:

static PyObject*
float_richcompare(PyObject *v, PyObject *w, int op)
{
    double i, j;
    int r = 0;
    assert(PyFloat_Check(v));       
    i = PyFloat_AS_DOUBLE(v);       

    if (PyFloat_Check(w))           
        j = PyFloat_AS_DOUBLE(w);   

    else if (!Py_IS_FINITE(i)) {
        if (PyLong_Check(w))
            j = 0.0;
        else
            goto Unimplemented;
    }

现在我们知道, w失败,这些检查,是否它不是 Python 浮点数。现在该函数检查是一个 Python 的整数。如果出现这种情况,最简单的测试是 v 的提取符号vw (返回0 ,如果零、 -1如果负1正数) 的符号。如果有迹象不同,这是比较结果返回所需的所有信息︰

    else if (PyLong_Check(w)) {
        int vsign = i == 0.0 ? 0 : i < 0.0 ? -1 : 1;
        int wsign = _PyLong_Sign(w);
        size_t nbits;
        int exponent;

        if (vsign != wsign) {
            /* Magnitudes are irrelevant -- the signs alone
             * determine the outcome.
             */
            i = (double)vsign;
            j = (double)wsign;
            goto Compare;
        }
    }   

如果此检查失败,然后, vw具有相同的符号。

下一次检查计数的整数w位。如果有太多的位然后可能不能保存为浮点数,并因此必须是浮点型v幅度大于:

    nbits = _PyLong_NumBits(w);
    if (nbits == (size_t)-1 && PyErr_Occurred()) {
        /* This long is so large that size_t isn't big enough
         * to hold the # of bits.  Replace with little doubles
         * that give the same outcome -- w is so large that
         * its magnitude must exceed the magnitude of any
         * finite float.
         */
        PyErr_Clear();
        i = (double)vsign;
        assert(wsign != 0);
        j = wsign * 2.0;
        goto Compare;
    }

在另一只手,如果w的整数有 48 (或更少) 位,它可以安全地打开 C 双j中,比较︰

    if (nbits <= 48) {
        j = PyLong_AsDouble(w);
        /* It's impossible that <= 48 bits overflowed. */
        assert(j != -1.0 || ! PyErr_Occurred());
        goto Compare;
    }

从此以后,我们知道该w有 49 或更多位数。它将方便w视为一个正整数值,因此更改符号和必要时的比较运算符︰

    if (nbits <= 48) {
        /* "Multiply both sides" by -1; this also swaps the
         * comparator.
         */
        i = -i;
        op = _Py_SwappedOp[op];
    }

现在该函数看起来在浮点数的指数。为有效数撤回可以 (忽略符号) 编写浮点数 * 2指数和有效位数表示 0.5 到 1 之间的数字︰

    (void) frexp(i, &exponent);
    if (exponent < 0 || (size_t)exponent < nbits) {
        i = 1.0;
        j = 2.0;
        goto Compare;
    }

这将检查以下两种情况。如果指数小于 0 浮点型则小于 1 (和因此比任何整数值量级小)。或者,如果指数小于w中的比特数,然后我们有的v < |w|以来有效数 * 2指数是少于 2nbits.

失败的两个检查,函数会检查是否指数大于w中的位的数目。这将显示该有效数 * 2指数大于 2nbits ,因此v > |w|:

    if ((size_t)exponent > nbits) {
        i = 2.0;
        j = 1.0;
        goto Compare;
    }

如果此检查失败,这是我们知道的浮点型v的指数是整数w中的比特数相同.

现在可以比较两个值的唯一方法是构造两个新的 Python 整数,从vw这一想法是放弃v,双倍的整数部分,小数部分,然后添加一个。w也翻了一番,并可以比较这两个新的 Python 对象提供正确的返回值。使用较小的值,例如4.65 < 4将通过比较来确定(2*4)+1 == 9 < 8 == (2*4) (返回 false)。

    {
        double fracpart;
        double intpart;
        PyObject *result = NULL;
        PyObject *one = NULL;
        PyObject *vv = NULL;
        PyObject *ww = w;

        // snip

        fracpart = modf(i, &intpart); // split i (the double that v mapped to)
        vv = PyLong_FromDouble(intpart);

        // snip

        if (fracpart != 0.0) {
            /* Shift left, and or a 1 bit into vv
             * to represent the lost fraction.
             */
            PyObject *temp;

            one = PyLong_FromLong(1);

            temp = PyNumber_Lshift(ww, one); // left-shift doubles an integer
            ww = temp;

            temp = PyNumber_Lshift(vv, one);
            vv = temp;

            temp = PyNumber_Or(vv, one); // a doubled integer is even, so this adds 1
            vv = temp;
        }
        // snip
    }
}

为了简便起见,我所遗漏的其他错误检查和垃圾跟踪 Python 有它创建这些新对象时执行的操作。毋庸赘言,这会增加系统开销,并解释为什么在这个问题中突出显示的值会显著降低的速度比其他比较。


下面是检查所执行的比较函数的摘要。

v是浮点型,然后将其转换为 C 双。现在,如果w也是浮点数︰

  • 检查wnaninf如果是这样,处理此种特殊情况分别根据w的类型.

  • 如果不是, vw直接比较其作为 C 双精度型值的表示形式。

如果w是一个整数︰

  • 提取vw的迹象。如果它们不同然后我们知道vw是不同的这是更大的价值。

  • (号是相同的。)检查是否w具有太多位数是浮点型 (超过size_t)。如果是这样, w具有比v更大.

  • 检查是否w有 48 (或更少) 位。如果是这样,它可以安全地强制转换为 C 双而不会丢失其精度和v与比较.

  • (w拥有超过 48 位。我们现在将w视为已更改相应比较 op 的正整数。)

  • 考虑浮点数v的指数。如果指数为负值,则v是小于1 ,因此任何正整数小于。否则,如果指数小于w中的比特数然后它必须小于w.

  • 如果v的指数大于w中的比特数则大于w v.

  • (指数就是w中的比特数.)

  • 最后的检查。拆分为其整数部分和小数部分的v两倍的整数部分并添加 1 来弥补它的小数部分。现在双w的整数。改为比较以下两个新的整数,得到的结果。

这样做确实变得是一个纯粹的公告。

gmpy2使用任意精度浮点数和整数就可能获得更高的统一比较性能︰

~ $ ptipython
Python 3.5.1 |Anaconda 4.0.0 (64-bit)| (default, Dec  7 2015, 11:16:01) 
Type "copyright", "credits" or "license" for more information.

IPython 4.1.2 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: import gmpy2

In [2]: from gmpy2 import mpfr

In [3]: from gmpy2 import mpz

In [4]: gmpy2.get_context().precision=200

In [5]: i1=562949953421000

In [6]: i2=562949953422000

In [7]: f=562949953420000.7

In [8]: i11=mpz('562949953421000')

In [9]: i12=mpz('562949953422000')

In [10]: f1=mpfr('562949953420000.7')

In [11]: f<i1
Out[11]: True

In [12]: f<i2
Out[12]: True

In [13]: f1<i11
Out[13]: True

In [14]: f1<i12
Out[14]: True

In [15]: %timeit f<i1
The slowest run took 10.15 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 441 ns per loop

In [16]: %timeit f<i2
The slowest run took 12.55 times longer than the fastest. This could mean that an intermediate result is being cached.
10000000 loops, best of 3: 152 ns per loop

In [17]: %timeit f1<i11
The slowest run took 32.04 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 269 ns per loop

In [18]: %timeit f1<i12
The slowest run took 36.81 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 231 ns per loop

In [19]: %timeit f<i11
The slowest run took 78.26 times longer than the fastest. This could mean that an intermediate result is being cached.
10000000 loops, best of 3: 156 ns per loop

In [20]: %timeit f<i12
The slowest run took 21.24 times longer than the fastest. This could mean that an intermediate result is being cached.
10000000 loops, best of 3: 194 ns per loop

In [21]: %timeit f1<i1
The slowest run took 37.61 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 275 ns per loop

In [22]: %timeit f1<i2
The slowest run took 39.03 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 259 ns per loop

我还没有使用过这个库中,但它看起来可能会非常有用。谢谢 !

它使用 sympy 和 mpmath

CPython 还具有decimal的标准库

请输入您的翻译

Why are some float < integer comparisons four times slower than others?

确认取消