首页 > 解决方案 > 为什么 float.__repr__ 与等效的格式化选项相比返回不同的表示?

问题描述

为了了解repr(x)CPython 中 float 的工作原理,我检查了以下源代码float_repr

buf = PyOS_double_to_string(PyFloat_AS_DOUBLE(v),
                            'r', 0,
                            Py_DTSF_ADD_DOT_0,
                            NULL);

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

precision = 17;
format_code = 'g';

所以我希望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'

我知道repr只需要返回尽可能多的数字来重建与内存中表示的完全相同的对象,因此'1.1'显然足以返回1.1但我想知道这与(内部使用的)有何不同(或为什么) ).17g格式化选项。

(Python 3.7.3)

标签: pythonpython-3.xfloating-pointcpython

解决方案


似乎您正在寻找一种后备方法:

/* 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];

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

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

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

现在在第 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)

在那里你可以看到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;

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


推荐阅读