9

当我在 Python 中对小数进行算术运算时,我得到以下意外结果:

>>> sys.float_info
sys.float_info(max=1.7976931348623157e+308, max_exp=1024, max_10_exp=308, min=2.2250738585072014e-308, min_exp=-1021, min_10_exp=-307, dig=15, mant_dig=53, epsilon=2.220446049250313e-16, radix=2, rounds=1)
>>> (1. - (1.e-17) ) < 1.
False

我知道浮点数没有无限精度,但它应该能够处理像 1e-17 这样的“大”小数,不是吗?

4

7 回答 7

12

浮点数接近 0 的精度高于接近 1 的精度。

  • 在 0.0 和 0.1 之间有 4591870180066957722 个浮点数。
  • 在 0.9 和 1.0 之间有 900719925474099 个浮点数,到目前为止更少。

浮动密度定期减半,看起来像这样:

浮子密度的可视化

这是 之后的下一个浮点数1.,方向为0.

>>> import math
>>> math.nextafter(1., 0.)
0.9999999999999999
>>> format(math.nextafter(1., 0.), ".32f")  # let's see more decimal places
'0.99999999999999988897769753748435'

1 - 10 -17的数学正确值是0.99999999999999999(有 17 个 9),我将这个数字称为n。像几乎所有数字一样,n不能用浮点数精确表示。

0.99999999999999999                    # n
0.00000000000000001                    # distance between n and 1, i.e. 10^-17
0.00000000000000010102230246251565...  # distance between n and nextafter(1., 0.)

所以你看,1 - 10 -17大约是 10nextafter(1., 0.)1.。当1. - 1.e-17解释器评估表达式时,它会返回最接近的可能结果,这1.正是。返回任何其他浮点数是没有意义的,这将与“真实”结果相距甚远(请原谅双关语)。

注意: math.nextafter在 Python 3.9+ 中可用。在早期版本中,您可以numpy.nextafter类似地使用。

相关问题 ->将 Python 浮点值增加最小可能量

于 2012-05-11T17:10:05.837 回答
7

首先,让我们回顾一下epsilon的返回值到底是什么sys.float_info

Epsilon (or ) 是满足0.5 + ≠ 0.5 AND 0.5 - ≠ 0.5的最小数

Python 告诉您,将导致0.5重复递增或递减的最小数字是epsilon=2.220446049250313e-16-但这仅适用于值 0.5。您正试图1.0增加1.0e-17. 这是一个较大的值(1.0 对 0.5),其增量小于 0.5 的值(1.0e-17 对 2.2e-16)。由于 1.0e-17 的增量值比 1.0 的相对 epsilon 小一个数量级,因此您大致偏离了一个数量级。

你可以在这里看到这个:

这些改变了0.5

>>> 0.5+sys.float_info.epsilon
0.5000000000000002
>>> 0.5-sys.float_info.epsilon
0.4999999999999998

这些值不会:

>>> 0.5+sys.float_info.epsilon/10.0
0.5
>>> 0.5-sys.float_info.epsilon/10.0
0.5
>>> 5.0+sys.float_info.epsilon
5.0
>>> 5.0-sys.float_info.epsilon
5.0

解释:

IEEE 754定义了当今大多数标准计算机上使用的浮点格式(专业计算机或库可能使用不同的格式。) IEEE 754 的64 位格式使用 53 位精度来计算和 52 位存储到浮点数的尾数点值。由于您有一个固定的 52/53 位可以使用,尾数的大小和精度会随着更大/更小的值而变化。那么随着浮点数的相对大小的变化而变化。0.5 的值不同于 1.0 和 100.0 的值。

由于各种非常好的和特定于平台的原因(存储和表示、舍入等),即使您可以使用较小的数字,epsilon 也被定义为对 64 位浮点格式使用 52 位精度。由于大多数 Python 实现使用 C 双浮点数来表示浮点数,因此可以证明这一点:

>>> 2**-52==sys.float_info.epsilon
True

查看您的平台将执行多少位:

>>> 0.5 + 2.0**-53
0.5000000000000001
>>> 0.5 - 2.0**-53
0.4999999999999999
>>> 0.5 + 2.0**-54
0.5                           # fail for 0.5 + 54 bits...
>>> 0.5 - 2.0**-54
0.49999999999999994           # OK for minus
>>> 0.5 - 2.0**-55
0.5                           # fail  for 0.5 minus 55 bits...

您的问题有几种解决方法:

  1. 您可以使用nextafter的C99 概念来计算适当的epsilon 值。对于 Python,使用 numpy 或 Decimal 类来计算nextafter. 更多关于nextafter我之前的回答这里
  2. 使用整数。64 位整数将清楚地处理 17 数量级的 epsilon 值,无​​需四舍五入。
  3. 使用任意精度数学库。十进制在标准 Python 发行版中。

重要的概念是 的值是相对于值的(如果你是递增或递减)。

这可以在这里看到:

>>> numpy.nextafter(0.0,1.0)-0.0
4.9406564584124654e-324                 # a relative epsilon value of 4.94e-324
>>> numpy.nextafter(0.01,1.0)-0.01
1.7347234759768071e-18                  # 1e-17 would still work...
>>> numpy.nextafter(0.1,1.0)-0.1
1.3877787807814457e-17                  # 1e-17 would >>barely<< work...
>>> numpy.nextafter(0.5,1.0)-0.5
1.1102230246251565e-16                  # a relative epsilon value of 1.1e-16
>>> numpy.nextafter(500.0,501.0)-500.0
5.6843418860808015e-14                  # relative epsilon of 5.6e-14
>>> numpy.nextafter(1e17,1e18)-1e17
16.0                                    # the other end of the spectrum...

因此,您可以看到 1e-17 可以轻松地增加 0.0 和 0.1 之间的值,但不会有比这更多的值。如上所示,1e17 的相对值是 16。

于 2012-05-11T19:50:08.570 回答
3

它应该能够处理像 1e-17 这样的“大”小数字,不是吗?

不一定(这取决于数字)。Afloat不能准确地表示1e-171-(1e-17)。在后者的情况下,它可以表示的最接近的数字是1

我建议您阅读每个计算机科学家应该了解的关于浮点运算的知识

于 2012-05-11T17:06:14.560 回答
3

如果您需要这种级别的精度,请考虑使用Decimal 模块

>>> decimal.Decimal(1.0)-decimal.Decimal('1.0e-17')
Decimal('0.999999999999999990')
>>> decimal.Decimal(1.0)-decimal.Decimal('1.0e-17')<decimal.Decimal(1.0)
True

和:

>>> decimal.Decimal(1.0)-decimal.Decimal('1.0e-17')<1.0
True

不过要小心最后一个,因为您可能会遇到转换错误。

其他人建议每个计算机科学家都应该了解浮点运算。我还建议不要将其存储在浮动中

于 2012-05-11T17:09:41.843 回答
2

你可以处理这些。注意

>>> 1.e-17 == 0
False

>>> 1.e-17 + 1.e-18
1.1e-17

您根本无法处理 1-1e-17,因为尾数不适合有限精度

于 2012-05-11T17:07:52.047 回答
0
>>> from decimal import Decimal
>>> Decimal('1')-Decimal(str(10**-17)) < Decimal('1')
True

使用十进制模块,以获得那种精度!

于 2012-05-11T17:12:41.727 回答
0

你有epsilon=2.220446049250313e-16所以这是正常的,(1. - (1.e-17) ) = 1因为1.e-17 < epsilon

于 2012-05-11T17:38:08.993 回答