为什么这个numpy元素在赋值后与原始值不同?

时间:2017-10-26 16:23:28

标签: python numpy

我已经在numpy中创建了一个结构化数组并设置了这样的值:

>>> array_structured = np.zeros(200000, dtype=[('index', np.int32),
                                        ('price', np.float32)])

>>> array_structured
array([(0,  0.), (0,  0.), (0,  0.), ..., (0,  0.), (0,  0.), (0,  0.)],
    dtype=[('index', '<i4'), ('price', '<f4')])

>>> array_structured['price'][0] = 100.12

>>> array_structured
array([(0,  100.12000275), (0,    0.        ), (0,    0.        ), ...,
    (0,    0.        ), (0,    0.        ), (0,    0.        )],
    dtype=[('index', '<i4'), ('price', '<f4')])

但实际值不是100.12,而是100.12000275。即使我检查它们是否相同,它也说错了:

>>> array_structured['price'][0] == 100.12
False

如何为元素指定精确的100.12?

2 个答案:

答案 0 :(得分:2)

>>> array_structured['price'][0] == 100.12
False

100.12是一个python float,它是一个float64,但你的数组包含float32

执行作业时,float64向下转换为float32,您将失去精确度。

进行比较时,float32会向上转换为float64,但精度未恢复,因此值不相等。

@cᴏʟᴅsᴘᴇᴇᴅ暗示float(100.12)只是10012/100的近似值,并且它们并不完全相等

答案 1 :(得分:2)

这里发生了一些事情,而不是一切都是它出现的,所以让我们把所有东西分开。

浮点近似

正如@COLDSPEED和@Eric都提到的那样,当你有一个浮点数时,它只是你想要存储的“真实”值的近似值。这个事实的原因是因为计算机以二进制 - 基数2表示存储数字 - 而100.12因此是100和12/100。整数部分很容易用2的正幂表示,但小数部分在基数2中没有精确表示(你可以通过使用Wolfram Alpha并运行查询“12/100 base 2”来看到这一点)。因此,要存储100.12,计算机必须使用2(容易)和12/100的幂使用2的幂(不可能精确地),大约100,并且它使用32或64位(32或64个插槽对应功率) 2)做到这一点。除此之外的所有内容都会被截断,因此存储的12/100的近似值并不准确。表示中的位越多,近似越接近,并且你可以使用任意更多的位来任意接近,但你永远不会得到它。

Float32 vs Float64近似

根据用于存储每个浮点数(32或64)的位数,您将获得更好或更差的近似值。您可以通过要求Python打印出每个数字的50位数来看到这一点(50太多了,但只是为了说明 - 我们接下来要打印):

In [2]: print("%.50f"%(np.array([100.12],dtype=np.float32)[0]))
100.12000274658203125000000000000000000000000000000000

In [3]: print("%.50f"%(np.array([100.12],dtype=np.float64)[0]))
100.12000000000000454747350886464118957519531250000000

中级计算

进行中间计算也可以改变最终的二进制表示。这是一个例子,比较100.12到100.02 + 0.10:

In [4]: print("%.50f"%(100.12))
100.12000000000000454747350886464118957519531250000000

In [5]: print("%.50f"%(100.02+0.10))
100.11999999999999033661879366263747215270996093750000

在第一种情况下,Python使用2的幂创建12/100的近似值。在第二种情况下,Python使用2的幂创建2/100的近似值,然后使用幂的另一近似值1/10两个,然后结合这两个表示,导致不同的近似。

打印表示

在这里进行的另一层近似是,当Numpy / Python打印100.12000275时,它只打印作为Numpy数组的字符串表示的一部分,而Numpy数组又使用数组中每个元素的字符串表示。因此,不要假设打印数组会为您提供计算机在该数组中看到的“绝对”版本。如果您拉出该特定值并使用打印格式字符串将其打印出来,您会看到还有更多小数点:

In [7]: array_structured
Out[7]:
array([(0,  100.12000275), (0,    0.        )],
      dtype=[('index', '<i4'), ('price', '<f4')])

In [8]: print("%.50f"%(array_structured['price'][0]))
100.12000274658203125000000000000000000000000000000000

但是,我应该指出,由于基数为2的表示,打印50个小数位对应于打印比Python实际存储的精度更高的位数,所以在某些时候甚至上面的数字只是近似值近似值。

将此与您使用np.float64的情况进行比较:再次,您会看到表示层。打印阵列使得它看起来像现在“完全”100.12,但使用打印格式显示您只有更接近的近似值:

In [10]: array_structured = np.zeros(2, dtype=[('index', np.int32),('price', np.float64)])

In [11]: array_structured['price'][0] = 100.12

In [12]: array_structured
Out[12]:
array([(0,  100.12), (0,    0.  )],
      dtype=[('index', '<i4'), ('price', '<f8')])

In [13]: print("%.50f"%(array_structured['price'][0]))
100.12000000000000454747350886464118957519531250000000

再次,这只是近似的近似值 - Python将基数为10的数字“100.12”转换为基数2表示来存储其值,当您将其打印出来时,它将基数2表示转换为基数10表示。

平等检查

由于==运算符的性质,您的等式检查不考虑这些多层表示。您将其解释为数学等于运算符,如2 + 2 = 4.但是,由于浮点数的二进制表示,这不会像您期望的那样工作。 (我的意思是,即使它做了工作,它也行不通,因为它实际上是在检查“100.12”是否等于数组中100.12的值,它正在检查计算机是否代表左边的东西等于右边东西的计算机表示。)

要检查两个数字是否相等,请不要使用==,使用math.isclose或比较两个数字的绝对差值:

In [18]: a = 100.12

In [19]: b = 100.02 + 0.10

In [20]: import math

In [21]: a==b
Out[21]: False

In [22]: math.isclose(a,b)
Out[22]: True

In [25]: abs(a-b)<1e-10
Out[25]: True

你的问题肯定是Python内脏的一个洞穴......希望你喜欢这个小小的游览。

相关问题