关于python:Numpy的float32和float比较

Numpy's float32 and float comparisons

继续Python float和numpy float32之间的区别:

1
2
3
4
5
6
7
import numpy as np

a = 58682.7578125
print(type(a), a)
float_32 = np.float32(a)
print(type(float_32), float_32)
print(float_32 == a)

打印:

1
2
3
<class 'float'> 58682.7578125
<class 'numpy.float32'> 58682.8
True

我完全理解,比较浮点数是否相等不是一个好主意,但仍然不应该是False(我们在谈论的是第一个十进制数字的差异,而不是0.000000001)?是否依赖系统?是否在某处记录了这种行为?

编辑:好吧,这是第三位小数:

1
2
print(repr(float_32), repr(a))
# 58682.758 58682.7578125

但是我可以信任repr吗?那些如何最终存储在内部?

EDIT2:人们坚持以更高的精度打印float_32会给我它的表示形式。但是,正如我已经根据nympy的文档评论的那样:

the % formatting operator requires its arguments to be converted to standard python types

和:

1
print(repr(float(float_32)))

版画

58682.7578125

@MarkDickinson在这里给出了一个有趣的见解,显然repr应该是忠实的(然后他说对np.float32不忠实)。

因此,让我重申如下问题:

  • 在示例中,如何获得float_32a的确切内部表示形式?如果这些相同,那么如果不解决,
  • 在python的floatnp.float32之间进行比较时,上/下转换的确切规则是什么?我猜想它会将float_32转换为float,尽管@WillemVanOnsem在评论中建议相反

我的python版本:

Python 3.5.2 (v3.5.2:4def2a2901a5, Jun 25 2016, 22:18:55) [MSC v.1900 64 bit (AMD64)] on win32


数字之所以相等,是因为可以在32位和64位浮点数中精确表示58682.7578125。让我们仔细看一下二进制表示形式:

1
2
3
4
5
6
7
8
9
32 bit:  01000111011001010011101011000010
sign    :  0
exponent:  10001110
fraction:  11001010011101011000010

64 bit:  0100000011101100101001110101100001000000000000000000000000000000
sign    :  0
exponent:  10000001110
fraction:  1100101001110101100001000000000000000000000000000000

它们具有相同的符号,相同的指数和相同的分数-64位表示形式中的多余位用零填充。

无论采用哪种方式进行铸造,它们都将比较相等。如果您尝试使用其他数字,例如58682.7578124,您会发现表示形式在二进制级别上有所不同。 32位失去了更高的精度,它们将无法相等。

(在二进制表示中也很容易看出,可以将float32转换为float64而不会丢失任何信息。这是numpy在比较两者之前应该做的。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import numpy as np

a = 58682.7578125
f32 = np.float32(a)
f64 = np.float64(a)

u32 = np.array(a, dtype=np.float32).view(dtype=np.uint32)
u64 = np.array(a, dtype=np.float64).view(dtype=np.uint64)

b32 = bin(u32)[2:]
b32 = '0' * (32-len(b32)) + b32  # add leading 0s
print('32 bit: ', b32)
print('sign    : ', b32[0])
print('exponent: ', b32[1:9])
print('fraction: ', b32[9:])
print()

b64 = bin(u64)[2:]
b64 = '0' * (64-len(b64)) + b64  # add leading 0s
print('64 bit: ', b64)
print('sign    : ', b64[0])
print('exponent: ', b64[1:12])
print('fraction: ', b64[12:])


他们是平等的。它们只是打印不同,因为它们使用不同的打印逻辑。

How can I get at the exact internal representation of float_32 and a in the example ?

好吧,这取决于您所说的"精确的内部表示形式"。如果确实需要一个位值数组,则可以得到:

1
2
3
4
>>> b = numpy.float32(a)
>>> numpy.unpackbits(numpy.array([b]).view(numpy.uint8))
array([1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0,
       1, 0, 1, 0, 0, 0, 1, 1, 1], dtype=uint8)

它与"精确的内部表示形式"非常接近,但这并不是最有用的东西。 (此外,结果将取决于字节序,因为它实际上是基于原始内部表示形式的。)

如果您想要C级浮点数,这就是NumPy如何在C级上表示float32值的方法……那就是C。除非要编写自己的C扩展模块,否则不能直接使用C级值。您可以得到的最接近的是C浮点数周围的某种包装器,嘿!您已经有一个!但是,您似乎对此并不满意,因此这并不是您真正想要的。

如果要使用以人类可读的十进制表示的确切值,请使用str.format或通过将其转换为常规浮点数然后再使用decimal.Decimal进行更高精度的打印。

1
2
3
4
>>> b
58682.758
>>> decimal.Decimal(float(b))
Decimal('58682.7578125')

您选择的58682.7578125值恰好可以表示为浮点数,因此,十进制表示形式恰好与您输入的值相同,但通常情况并非如此。您键入的确切十进制表示形式将被丢弃并且无法恢复。

What are the exact rules for up/downcasting in a comparison between python's float and np.float32 ?

float32被无损地转换为float64。


小数点58682.7578125是精确的分数(7511393/128)。

分母为2(2**7)的幂,分子为23位。因此,该十进制值可以在float32(有效位数为24位)和float64中精确表示。

因此,Victor T的答案是正确的:在内部表示中,它的值相同。

IMO对相同的值(即使对于不同的类型)都回答正确的事实,这对IMO是件好事,您对(2 == 2.0)有何期待?


相同的值在内部存储,只显示不带print的所有数字

尝试:

1
 print"%0.8f" % float_32

请参阅相关的全精度打印numpy.float64


58682.8

我的机器在这条线上显示58682.758。

I fully understand that comparing floats for equality is not a good idea

如果它们是独立计算的,则"不是一个好主意"。另一方面,如果您获得相同的数字并检查其转换,则是一个好主意。

Is it system dependent ? Is this behavior somewhere documented ?

它完全取决于转换为文本。根据评论,float32是必不可少的。如果是这样,则float32的保证精度为7个十进制数字,这与Python的内部float为float64(至少在x86上)不同。这就是为什么该值在打印中被截断的原因。建议使用十进制格式打印浮点值的方法是在输出形式转换回相同的内部值时停止。因此它将58682.7578125减少为58682.758:差异小于ULP。

与内部" float"或numpy float64打印的相同值将具有更高的有效数字,因为它们的省略将导致另一个内部值:

1
2
3
4
5
6
7
8
9
10
11
12
>>> 58682.758 == 58682.7578125
False
>>> numpy.float32(58682.758) == numpy.float32(58682.7578125)
True
>>> print(repr(numpy.float32(58682.758).data[0:4]))
'\xc2:eG'
>>> print(repr(numpy.float32(58682.7578125).data[0:4]))
'\xc2:eG'
>>> numpy.float64(58682.758) == numpy.float64(58682.7578125)
False
>>> print(numpy.float64(58682.758).hex(), numpy.float64(58682.7578125).hex())
('0x1.ca7584189374cp+15', '0x1.ca75840000000p+15')

您很幸运,这两个值在float32中与此具体值相等(是故意的吗?),但可能与其他值不同。