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

时间:2012-11-12 14:18:17

标签: python floating-point python-2.x

我已将其输入python shell:

>>> 0.1*0.1
0.010000000000000002

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

>>> len(str(0.1*0.1))
4

我预计会得到20分,因为我看过20个字符。为什么我会得到4?

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

好的,这解释了为什么我len给了我4,但为什么str会返回'0.01'

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

为什么str轮,但repr没有? (我已阅读this answer,但我想知道他们在str轮次浮动时以及何时没有决定

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

所以它似乎是浮子的准确性问题。我以为Python会使用IEEE 754单精度浮点数。所以我已经这样检查过了:

#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会给我这些结果?

(我使用Python 2.6.5。如果你知道Python版本的差异,我也会对它们感兴趣。)

3 个答案:

答案 0 :(得分:14)

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

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

即使在Python 2.7下,%.17g也会提供repr(0.1 * 0.1)。这是因为"0.010000000000000002"在IEEE-754解析和算术下是0.1 * 0.1 == 0.01;也就是说,当与False相乘时,最接近的64位浮点值与自身相乘,产生一个64位浮点值,该值不是最接近{{1}的64位浮点值}:

0.1

0.01>>> 0.1.hex() '0x1.999999999999ap-4' >>> (0.1 * 0.1).hex() '0x1.47ae147ae147cp-7' >>> 0.01.hex() '0x1.47ae147ae147bp-7' ^ 1 ulp difference (2.7 / 3.1之前)之间的差异是repr格式有12位小数而不是17位,这是非圆形的,但是产生在许多情况下,结果更具可读性。

答案 1 :(得分:5)

我可以确认你的行为

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中的文档claim&lt; 2.7

  

repr(1.1)的值计算为format(1.1, '.17g')

这是一个小小的简化。


请注意,这与字符串格式化代码有关 - 在内存中,所有Python浮点数都只是存储为C ++双精度数,因此它们之间永远不会有区别。 / p>

此外,即使您知道有一个更好的字符串,使用浮动的全长字符串也是一种令人不愉快的事情。实际上,在现代Pythons中,一种新算法用于浮点格式化,以智能方式选择最短的表示。


我花了一些时间在源代码中查找,所以我会在这里包含详细信息,以防您感兴趣。您可以跳过此部分。

floatobject.c中,我们看到了

static PyObject *
float_repr(PyFloatObject *v)
{
    char buf[100];
    format_float(buf, sizeof(buf), v, PREC_REPR);

    return PyString_FromString(buf);
}

让我们看看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);

随着隧道尽头的结束,我们会查找PyOS_ascii_formatd并发现它在内部使用snprintf

答案 2 :(得分:0)

from python tutorial

  

在Python 2.7和Python 3.1之前的版本中,Python将此值四舍五入为17位有效数字,得到‘0.10000000000000001’。在当前版本中,Python显示一个基于最小小数的值,该小数正确地回滚到真正的二进制值,仅在‘0.1’中生成。