当前位置: 首页 > 知识库问答 >
问题:

为什么一些浮点

郁光熙
2023-03-14
>>> 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

更改比较运算符(例如,改用==>)不会以任何明显的方式影响时间。

这不仅仅与幅度有关,因为选择较大或较小的值会导致更快的比较,所以我怀疑这是由于位排列的某种不幸方式造成的。

共有1个答案

彭高畅
2023-03-14

float对象的Python源代码中的注释确认:

比较几乎是一场噩梦

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

  • 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既是浮点数的指数,也是整数中的位数。这两个数字都是正数,因此符合上述四个标准。

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

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

下面是该函数执行的检查的一步一步的描述。Python源代码中的注释在试图理解函数的功能时非常有用,因此我将它们留在了相关的地方。我还在答案后面的列表中总结了这些检查。

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的符号和w的符号(如果为零,则返回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];
    }

现在函数查看浮点的指数。回想一下,浮点数可以写成(忽略符号)有意义*2exponent,并且有意义表示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 ,因为有效值*2 exponent小于2 nbits

如果这两个检查失败,函数将查看指数是否大于w中的位数。这表明signand*2exponent大于2nbits并且因此v>w:

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

如果这个检查没有成功,我们知道浮点数v的指数与整数w的位数相同。

现在可以比较这两个值的唯一方法是从vw构造两个新的Python整数。其思想是丢弃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 double。现在,如果w也是浮点:

>

  • 检查wnan还是inf。如果是,则根据w的类型单独处理此特殊情况。

    如果不是,则直接将vw的表示形式作为C doubles进行比较。

    如果w是整数:

    >

  • 提取vw的符号。如果它们不同,那么我们就知道vw是不同的,哪个值更大。

    考虑浮点数v的指数。如果指数为负数,则V小于1,因此小于任何正整数。否则,如果指数小于w中的位数,那么它必须小于w

    如果v的指数大于w中的位数,则v大于w

    (指数与w中的位数相同。)

    最后的检查。将v拆分为整数部分和小数部分。将整数部分加倍,再加1,以补偿小数部分。现在将整数w加倍。比较这两个新的整数以得到结果。

  •  类似资料:
    • 这样一个看似简单的数字怎么会“太大”而无法在64位内存中表达呢?

    • null 他们解释“如何”。我想知道为什么这些语言之间的差异。我期望在相同的输入下得到相似的结果。 test.js test.java 结果: 要旨:https://gist.github.com/reklis/6694AD5FB01991A79A1A

    • 问题内容: 考虑以下测试用例,其中一个float元素和一个inline元素放置在a 与a之内: 呈现时,字段集的高度为200像素(它们清除浮点数了吗?),而div的高度仅与内联元素一样高。造成这种现象的原因是什么,是否有一种解决方法可以使这些字段集像div一样起作用? 问题答案: 显然,元素应该为其内容生成块格式上下文: 期望该元素建立新的块格式化上下文。 这就是为什么浮动元素不会从其中浮动出来的

    • 问题内容: 输出: 为什么这样的输出?我期望作为第一种情况的结果。 问题答案: 区别在于6.5可以完全以float和double表示,而3.2不能完全以两种类型表示。并且两个最接近的近似是不同的。 float和double之间的相等比较首先将float转换为double,然后将两者进行比较。因此数据丢失。 您不应该比较浮点数或双精度数是否相等。因为您不能真正保证分配给float或double的数字

    • 问题内容: 看一下这个文件: 它不编译;我需要。现在考虑这一点: 完全一样,只是用代替,所以不应该编译,对吧?错误。 好的,所以不必导入是众所周知的,但是 为什么 呢?“明显”的答案是因为它的使用频率远高于,但是不应该“隐式导入”吗?原始包装器类,例如和呢?用 远远 往往比和,但我必须进口,并不需要进口。 什么是落后不需要对进口的原因,,,和其他几个班? 问题答案: 明显的答案是因为它的使用频率远

    • 这里的CPython文档中指出: 当前的实现为-5到256之间的所有整数保留了一个整数对象数组,当您在这个范围内创建一个int时,实际上只需要返回一个对现有对象的引用。 这使得这个比较是正确的: 我想知道这背后的原因是什么,为什么有些号码是预先分配的,为什么特别是那些号码?