python 如何处理非常小的浮点数?

How does python handle very small float numbers?

这与其说是技术问题,不如说是好奇。

我试图更好地理解 Python 中如何处理浮点数。特别是,我很好奇 sys.float_info.epsilon = 2.220446049250313e-16 返回的数字。 我可以看到,查看有关双精度浮点数的文档,这个数字也可以写成 1/pow(2, 52)。到目前为止,还不错。

我决定写一个小 python 脚本(见下文。免责声明:这段代码很丑,会灼伤你的眼睛)从 eps = 0.1 并进行比较 1.0 == 1.0 + eps。如果 False,则表示 eps 足够大,可以有所作为。然后我尝试通过从最后一位数字减去 1 并将数字 1 添加到最后一位数字的右侧并通过递增最后一位数字再次寻找 False 来找到一个较小的数字。

我非常有信心代码是正确的,因为在某些时候(32 小数位)我得到 eps = 0.00000000000000011102230246251567 = 1.1102230246251567e-16 非常接近 1/pow(2, 53) = 1.1102230246251565e-16(最后一位相差 2)。

我认为此后代码不会产生合理的数字。但是,该脚本一直在工作,始终将更准确的十进制数归零,直到达到 107 位小数。除此之外,代码没有找到 False 来测试。我对这个结果非常感兴趣,无法全神贯注。

这个107位小数的​​浮点数有什么意义吗?如果是阳性,它有什么特别之处? 如果不是,python 在小数点后 32 位 eps 之后做什么?当然有一些算法 python 是 c运行 获得 107 长浮点数的王。

脚本。

total = 520 # hard-coded after try-and-error max number of iterations.
dig = [1]
n = 1
for t in range(total):
    eps = '0.'+''.join(str(x) for x in dig)
    if(1.0 == 1.0 + float(eps)):
        if dig[-1] == 9:
            print(eps, n)
            n += 1
            dig.append(1)
        else:
            dig[-1] += 1
    else:
        print(eps, n)
        n += 1
        dig[-1] -= 1
        dig.append(1)

输出(部分)。值为 eps 小数位数

0.1 1
0.01 2
(...)
0.000000000000001 15
0.0000000000000002 16
0.00000000000000012 17
0.000000000000000112 18
0.0000000000000001111 19
0.00000000000000011103 20
(...)
0.0000000000000001110223024625157 31
0.00000000000000011102230246251567 32
0.000000000000000111022302462515667 33
(...)
0.000000000000000111022302462515666368314810887391490808258832543534838643850548578484449535608291625976563 105
0.0000000000000001110223024625156663683148108873914908082588325435348386438505485784844495356082916259765626 106
0.00000000000000011102230246251566636831481088739149080825883254353483864385054857848444953560829162597656251 107

I 运行 Python 3.8.3 中的此代码(tags/v3.8.3:6f8c832,2020 年 5 月 13 日,22:20:19)[MSC v.1925 32 位(英特尔)] 在 win32 上。

您的测试涉及双舍入,并找到数字 2−53+2−105.

许多 Python 实现使用 IEEE-754 binary64 格式。 (这不是 Python 文档所要求的。)在这种格式中,浮点数的有效数(小数部分)有 53 位。 (52 在主要有效数字字段中编码。1 通过指数字段编码。)对于区间 [1, 2) 中的数字,有效数字被缩放(通过浮点表示的指数部分),使其前导位对应于值 1 (20)。这意味着尾随位对应于值 2−52.

因此,1 和下一个可以用这种格式表示的数字之间的差是 2−52——这是可以对数字进行的最小变化,通过增加低位.

现在,假设x包含1,如果我们加上2−52,当然会得到1+2−52 ,因为该结果是可表示的。如果我们添加稍微小一些的东西,比如 ¾•2−52,会发生什么?在这种情况下,实数结果 1+¾•2−52 不可表示。它必须四舍五入到一个可表示的数字。常见的默认舍入方法是舍入到最接近的可表示数字。在这种情况下,即 1+2−52.

因此,将一些小于2−52的数加到1仍然得到1+2−52。我们可以与 1 相加得到这个结果的最小数字是多少?

在平局的情况下,实数结果恰好在两个可表示数字的中间,常见的默认舍入方法使用具有偶数低位的那个。因此,如果在 1(尾随位 0)和 1+2−52(尾随位 1)之间进行选择,它会选择 1。这意味着如果我们添加 ½•2−52到1,会产生1.

如果我们将任何大于½•2−52的数加到1,就不会出现平局;实数结果会更接近1+2−52,这就是结果

下一个问题是我们可以加上的大于½•2−52 (2−53)的最小数是多少到 1?如果数字必须采用 IEEE-754 binary64 格式,则受其有效位数限制。前导位按比例缩放表示 2−53,尾随位表示 2−53−52 = 2−105.

因此,2−53+2−105是我们可以与1相加得到1+2[的最小binary64值=31=]−52.

当您的程序测试值时,它使用十进制数字。该十进制数字转换为浮点格式,然后加 1。所以它是在浮点格式中找到产生大于 1 的和的最小数字,也就是上面描述的数字,2−53+2−105。它的十进制值为 1.110223024625156663683148108873914908082588325435348386438505485784844495356082916259765625•10−16.