当我设置超出特定数据类型范围的值时会发生什么?

2 投票
4 回答
1187 浏览
提问于 2025-04-18 04:27

我一直都是这样创建numpy数组的:

>>> u = np.zeros( 10, int )
>>> v = np.zeros( 10, float )

之前我对最大允许值这回事完全没有注意过。我一直以为只要写了就能正常工作。如果不行,我会收到一个叫OverflowError的错误,然后再想办法,比如取对数之类的。

不过最近我开始使用其他的数据类型:

>>> v8 = np.zeros( 10, np.uint8 )
>>> v8[0] = 2 ** 8 - 1
>>> v8[1] = 2 ** 8
>>> v8
>>> array([255,   0,   0,   0,   0,   0,   0,   0,   0,   0], dtype=uint8)

好吧,当我给一个大于255的值时,并没有收到任何警告。这让我有点害怕。

所以我有几个问题:

  • 当我使用intfloat类型的数组时,有可能我设置了一个太大的值(导致计算结果完全错误)而我却不知道吗?
  • 如果我想使用uint8,我是不是必须手动检查所有赋值是否在[ 0, 255 ]这个范围内?

4 个回答

0

正如之前所说,numpy为了避免检查,会进行一些自动处理。

如果你不想让数据被截断,在转换数据类型之前,可以使用numpy.min_scalar_type来获取一个最小的数据类型,这样可以确保你的数据不会丢失。

另外,实际上使用uint8的唯一原因就是为了在处理非常大的数组时节省内存,因为计算速度通常差不多(有些操作甚至会自动转换成更大的类型)。如果你的数组不大,内存不是个大问题,那么你可以更安全地使用uint16或者uint32来进行中间计算。如果内存是个问题,你可以考虑使用外部存储,比如PyTables;如果你快要填满内存了,可能连uint8都不够用了,尤其是当数据集更大的时候。

1

正如其他回答中所解释的,数值如果太大就会“绕回去”,所以在转换之前,你需要手动把它们限制在允许的最小值和最大值之间。对于整数来说,这些限制可以通过 np.iinfo 来获取。你可以写一个自己的工具函数,以安全的方式进行这种转换,适用于特定的数据类型:

def safe_convert(x, new_dtype):
    info = np.iinfo(new_dtype)
    return x.clip(info.min, info.max).astype(new_dtype)

快速测试:

In [31]: safe_convert(np.array([-1,0,1,254,255,256]), np.uint8)
Out[31]: array([  0,   0,   1, 254, 255, 255], dtype=uint8)

In [32]: safe_convert(np.array([-129,-128,-127,126,127,128]), np.int8)
Out[32]: array([-128, -128, -127,  126,  127,  127], dtype=int8)
1

numpy在机器层面上工作得非常深入。测试过程比较耗时,因此测试的工作通常由开发者来完成。相比之下,Python的层次要高得多,很多测试都是自动进行的,尤其是对于整数来说,它们可以有非常大的值。在很多情况下,你需要在速度和安全性之间做出选择,而numpy更偏向于速度。

在需要测试数值范围的情况下,你需要自己来检查。

clip方法可能会对你有所帮助:

>>> u = np.array([124,-130, 213])
>>> u.astype('b')
array([124, 126, -43], dtype=int8)
>>> u.clip(-128,127).astype('b')
array([ 124, -128,  127], dtype=int8)
0

是的,uint8会对你的值进行处理,只保留最低的8位,所以你需要手动检查一下:

>>> a = numpy.uint8(256)
>>> a
0

而且,溢出可能会在你没有意识到的情况下发生。这在很多编程语言中是一个常见的错误来源。不过,在Python中,长整型的表现方式有点特别:它没有明确的限制。

我在这个回答中写过相关内容。

撰写回答