为什么float .__ repr__返回与等效格式选项相比不同的表示形式?

a_g*_*est 7 python floating-point cpython python-3.x

为了查看repr(x)CPython中float的工作方式,我检查了以下代码float_repr

buf = PyOS_double_to_string(PyFloat_AS_DOUBLE(v),
                            'r', 0,
                            Py_DTSF_ADD_DOT_0,
                            NULL);
Run Code Online (Sandbox Code Playgroud)

这会调用PyOS_double_to_string格式代码'r',该格式代码似乎已转换为'g'精度设置为17的格式代码:

precision = 17;
format_code = 'g';
Run Code Online (Sandbox Code Playgroud)

因此,我希望repr(x)f'{x:.17g}'返回相同的表示形式。但是,情况似乎并非如此:

>>> repr(1.1)
'1.1'
>>> f'{1.1:.17g}'
'1.1000000000000001'
>>> 
>>> repr(1.225)
'1.225'
>>> f'{1.225:.17g}'
'1.2250000000000001'
Run Code Online (Sandbox Code Playgroud)

我知道,repr仅需要返回与重构内存中所表示的相同对象相同数量的数字,因此'1.1'显然足以取回该数字,1.1但我想知道这与(内部使用)与(内部使用)有何不同(或为什么) ).17g格式选项。

(Python 3.7.3)

Jea*_*bre 5

似乎您正在查看备用方法:

/* The fallback code to use if _Py_dg_dtoa is not available. */

PyAPI_FUNC(char *) PyOS_double_to_string(double val,
                                         char format_code,
                                         int precision,
                                         int flags,
                                         int *type)
{
    char format[32];
Run Code Online (Sandbox Code Playgroud)

条件回退方法的预处理器变量是PY_NO_SHORT_FLOAT_REPR。如果设置,dtoa则将不会编译,因为它将失败

/ *如果定义了PY_NO_SHORT_FLOAT_REPR,那么甚至不要尝试编译以下代码* /

在大多数现代设置中可能并非如此。该问答解释了何时/为什么Python选择这两种方法:是什么导致Python的float_repr_style使用旧版?

现在在第947行,您可以使用_Py_dg_dtoa的版本

/* _Py_dg_dtoa is available. */


static char *
format_float_short(double d, char format_code,
                   int mode, int precision,
                   int always_add_sign, int add_dot_0_if_integer,
                   int use_alt_formatting, const char * const *float_strings,
                   int *type)
Run Code Online (Sandbox Code Playgroud)

在那里您可以看到gr有细微的差异(在注释中说明)

我们曾经在1e17进行转换,但是当用伪造的零填充16位“最短” repr时,对于某些值来说,结果看起来很奇怪。

case 'g':
    if (decpt <= -4 || decpt >
        (add_dot_0_if_integer ? precision-1 : precision))
        use_exp = 1;
    if (use_alt_formatting)
        vdigits_end = precision;
    break;
case 'r':
    /* convert to exponential format at 1e16.  We used to convert
       at 1e17, but that gives odd-looking results for some values
       when a 16-digit 'shortest' repr is padded with bogus zeros.
       For example, repr(2e16+8) would give 20000000000000010.0;
       the true value is 20000000000000008.0. */
    if (decpt <= -4 || decpt > 16)
        use_exp = 1;
    break;
Run Code Online (Sandbox Code Playgroud)

似乎与您描述的行为相符。注意"{:.16g}".format(1.225)产量1.225