浮点和字符串转换的奇怪行为

2024-04-28 23:31:17 发布

您现在位置:Python中文网/ 问答频道 /正文

我在python shell中输入了以下内容:

>>> 0.1*0.1
0.010000000000000002

我预期0.1*0.1不是0.01,因为我知道0.1在基数10中是周期性的,在基数2中是周期性的。在

^{pr2}$

我期望得到20个,因为我看到了上面的20个字符。为什么我得4分?在

>>> str(0.1*0.1)
'0.01'

好的,这解释了为什么我len给了我4,但是{}为什么返回{}?在

>>> repr(0.1*0.1)
'0.010000000000000002'

为什么str不圆而{}不圆?(我读过this answer,但我想知道他们是如何决定str取整浮点数和不取整浮点数的)

>>> str(0.01) == str(0.0100000000001)
False
>>> str(0.01) == str(0.01000000000001)
True

所以这似乎是一个浮动精度的问题。我以为Python会使用ieee754单精度浮点。所以我检查了一下:

#include <stdint.h>
#include <stdio.h> // printf

union myUnion {
    uint32_t i; // unsigned integer 32-bit type (on every machine)
    float f;    // a type you want to play with
};

int main() {
    union myUnion testVar;
    testVar.f = 0.01000000000001f;
    printf("%f\n", testVar.f);

    testVar.f = 0.01000000000000002f;
    printf("%f\n", testVar.f);

    testVar.f = 0.01f*0.01f;
    printf("%f\n", testVar.f);
}

我得到了:

0.010000
0.010000
0.000100

Python给了我:

>>> 0.01000000000001
0.010000000000009999
>>> 0.01000000000000002
0.010000000000000019
>>> 0.01*0.01
0.0001

为什么Python会给出这些结果?在

(我使用python2.6.5。如果您知道Python版本之间的差异,我也会对它们感兴趣。)


Tags: lenincludetypeshell基数union周期性浮点数
3条回答

from python tutorial

In versions prior to Python 2.7 and Python 3.1, Python rounded this value to 17 significant digits, giving ‘0.10000000000000001’. In current versions, Python displays a value based on the shortest decimal fraction that rounds correctly back to the true binary value, resulting simply in ‘0.1’.

关于repr的关键要求是它应该往返;也就是说,eval(repr(f)) == f在所有情况下都应该给出{}。在

在python2.x(2.7之前的版本)中,repr的工作方式是使用%.17g格式执行printf,并丢弃尾随的零。IEEE-754保证这是正确的(对于64位浮点)。从2.7和3.1开始,Python使用了一种更智能的算法,在某些情况下,%.17g给出了不必要的非零终端数字或终端9,这种算法可以找到更短的表示。请参见What's new in 3.1?issue 1580。在

即使在Python2.7中,repr(0.1 * 0.1)也给出了"0.010000000000000002"。这是因为根据IEEE-754解析和算术,0.1 * 0.1 == 0.01False;也就是说,与0.1最近的64位浮点值相乘时,将生成一个64位浮点值,该值不是最接近0.01的64位浮点值:

>>> 0.1.hex()
'0x1.999999999999ap-4'
>>> (0.1 * 0.1).hex()
'0x1.47ae147ae147cp-7'
>>> 0.01.hex()
'0x1.47ae147ae147bp-7'
                 ^ 1 ulp difference

repr和{}(2.7/3.1之前的版本)的区别在于{}格式有12位小数,而不是17位,这是不可舍入的,但在许多情况下会产生更具可读性的结果。在

我可以证实你的行为

ActivePython 2.6.4.10 (ActiveState Software Inc.) based on
Python 2.6.4 (r264:75706, Jan 22 2010, 17:24:21) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> repr(0.1)
'0.10000000000000001'
>>> repr(0.01)
'0.01'

现在,Python<;2.7中的文档claim

the value of repr(1.1) was computed as format(1.1, '.17g')

这只是一个小小的简化。在


<>请注意,这与字符串格式化< EME>内存中的代码有关,所有Python浮点都被存储为C++双打,所以它们之间永远不会有区别。在

另外,即使你知道有更好的浮点数,也有点不舒服。实际上,在现代Pythons中,一种新的算法被用于浮点格式,它以一种聪明的方式选择最短的表示。在


我花了一段时间在源代码中查找,所以我将在这里包括详细信息,以防您感兴趣。你可以跳过这一节。在

floatobject.c中,我们看到

^{pr2}$

这让我们来看看format_float。省略NaN/inf特殊情况,它是:

format_float(char *buf, size_t buflen, PyFloatObject *v, int precision)
{
    register char *cp;
    char format[32];
    int i;

    /* Subroutine for float_repr and float_print.
       We want float numbers to be recognizable as such,
       i.e., they should contain a decimal point or an exponent.
       However, %g may print the number as an integer;
       in such cases, we append ".0" to the string. */

    assert(PyFloat_Check(v));
    PyOS_snprintf(format, 32, "%%.%ig", precision);
    PyOS_ascii_formatd(buf, buflen, format, v->ob_fval);
    cp = buf;
    if (*cp == '-')
        cp++;
    for (; *cp != '\0'; cp++) {
        /* Any non-digit means it's not an integer;
           this takes care of NAN and INF as well. */
        if (!isdigit(Py_CHARMASK(*cp)))
            break;
    }
    if (*cp == '\0') {
        *cp++ = '.';
        *cp++ = '0';
        *cp++ = '\0';
        return;
    }

    <some NaN/inf stuff>
}

我们可以看到

因此,这首先初始化一些变量并检查v是否是格式良好的浮点。然后准备一个格式字符串:

PyOS_snprintf(format, 32, "%%.%ig", precision);

现在PREC_REPR在floatobject.c的其他地方定义为17,因此这将计算到"%.17g"。现在我们打电话来

PyOS_ascii_formatd(buf, buflen, format, v->ob_fval);

看到隧道的结尾后,我们查找^{},发现它在内部使用snprintf。在

相关问题 更多 >