4

这个问题更多是出于好奇。

我正在创建以下数组:

A = zeros((2,2))
for i in range(2):
    A[i,i] = 0.6
    A[(i+1)%2,i] = 0.4
print A

>>>
   [[ 0.6  0.4]
   [ 0.4  0.6]]

然后,打印它:

for i,c in enumerate(A):
    for j,d in enumerate(c):
        print j, d

但是,如果我删除 j,我得到:

>>>
0 0.6
1 0.4
0 0.4
1 0.6

但是,如果我从 for 中删除 j,我得到:

(0, 0.59999999999999998)
(1, 0.40000000000000002)
(0, 0.40000000000000002)
(1, 0.59999999999999998)

这是因为我使用 0.6 创建矩阵的方式?它如何代表内部的真实价值?

4

2 回答 2

16

这里发生了一些不同的事情。

首先,Python 有两种将对象转换为字符串的机制,称为reprstrrepr应该提供“忠实”的输出,(理想情况下)可以很容易地准确地重新创建该对象,同时str旨在获得更多人类可读的输出。对于 Python 3.1 及以下版本中repr的浮点数,提供足够的数字来完全确定浮点数的值(以便评估返回的字符串会准确地返回该浮点数),同时str 四舍五入到小数点后 12 位;这具有隐藏不准确性的效果,但意味着非常接近的两个不同的浮点数最终可能具有相同的str值 - 这是 . 不会发生的事情repr。当你打印一个对象时,你会得到str那个物体的。相反,当您只是在解释器提示符处计算表达式时,您会得到repr.

例如(这里使用 Python 2.7):

>>> x = 1.0 / 7.0
>>> str(x)
'0.142857142857'
>>> repr(x)
'0.14285714285714285'
>>> print x  # print uses 'str'
0.142857142857
>>> x  # the interpreter read-eval-print loop uses 'repr'
0.14285714285714285

但是,从您的角度来看,有点令人困惑,我们得到:

>>> x = 0.4
>>> str(x)
'0.4'
>>> repr(x)
'0.4'

这似乎与您在上面看到的内容不太吻合,但我们将在下面回到这一点。

要记住的第二件事是,在您的第一个示例中,您正在打印两个单独的项目,而在您的第二个示例(已j删除)中,您正在打印一个项目:长度为 2 的元组。有点令人惊讶的是,当用 转换一个元组以打印时str,Python 仍然使用repr来计算该元组元素的字符串表示:

>>> x = 1.0 / 7.0
>>> print x, x  # print x twice;  uses str(x)
0.142857142857 0.142857142857
>>> print(x, x)  # print a single tuple; uses repr(x)
(0.14285714285714285, 0.14285714285714285)

这就解释了为什么在这两种情况下你会看到不同的结果,即使底层的浮点数是相同的。

但还有最后一块拼图。在 Python >= 2.7 中,我们在上面看到对于特定的 float 0.4,该float 的strandrepr是相同的。那么0.40000000000000002从哪里来呢?好吧,这里没有 Python 浮点数:因为您从 NumPy 数组中获取这些值,它们实际上是 type numpy.float64

>>> from numpy import zeros
>>> A = zeros((2, 2))
>>> A[:] = [[0.6, 0.4], [0.4, 0.6]]
>>> A
array([[ 0.6,  0.4],
       [ 0.4,  0.6]])
>>> type(A[0, 0])
<type 'numpy.float64'>

该类型仍然存储一个双精度浮点数,就像 Python 的浮点数一样,但它有一些额外的好处,使其能够与 NumPy 的其余部分很好地交互。事实证明,NumPy 计算a 的算法与 Python计算repra 的算法略有不同。Python(在 >= 2.7 版本中)旨在提供仍然准确表示浮点数的最短字符串,而 NumPy 仅根据将基础值四舍五入为 17 位有效数字来输出字符串。回到上面那个例子,下面是 NumPy 的作用:numpy.float64reprfloat0.4

>>> from numpy import float64
>>> x = float64(1.0 / 7.0)
>>> str(x)
'0.142857142857'
>>> repr(x)
'0.14285714285714285'
>>> x = float64(0.4)
>>> str(x)
'0.4'
>>> repr(x)
'0.40000000000000002'

所以这三件事一起应该解释你所看到的结果。请放心,这完全是装饰性的:底层浮点值不会以任何方式改变;它只是被两种类型的str和的四种不同可能组合以不同的方式显示:和.reprfloatnumpy.float64

Python 教程提供了有关Python 浮点数如何存储和显示的更多详细信息,以及一些潜在的陷阱。这个 SO question的答案有更多关于str和之间区别的信息repr

于 2013-04-19T20:13:43.053 回答
3

编辑:

别介意我,我没有意识到这个问题是关于 NumPy 的。


奇怪0.59999999999999998的朋友是 Python 准确表示所有计算机如何存储浮点值的最佳尝试:根据 IEEE 754 标准,作为一堆位。值得注意的是,0.1是二进制的非终止十进制,因此无法准确存储。(所以,大概是0.60.4。)

您通常看到的原因0.6是大多数浮点打印功能会完善这些不精确存储的浮点数,以使我们人类更容易理解它们。这就是您的第一个打印示例正在做的事情。

在某些情况下(即,当打印功能不尝试人类可读时),0.59999999999999998将打印完整的、略微偏离的数字。这就是您的第二个打印示例正在做的事情。

tl;博士

这不是 Python 的错。这就是浮点数的存储方式。

于 2013-04-19T13:41:14.037 回答