Python numpy float16 数据类型操作,和 float8?

Python numpy float16 datatype operations, and float8?

对float16 Numpy 数字进行数学运算时,结果也是float16 类型数字。 我的问题是结果是如何计算出来的? 假设我 multiplying/adding 两个 float16 数字, python 生成结果在 float32 中然后 truncate/round 结果到 float16 吗?还是一直在'16bitmultiplexer/adder硬件'中进行计算?

另一个问题 - 有 float8 类型吗?我找不到这个……如果找不到,那为什么呢?谢谢大家!

第一个问题:在典型的处理器上(至少在 GPU 之外)没有对 float16 的硬件支持。 NumPy 完全按照您的建议进行操作:将 float16 操作数转换为 float32,对 float32 值执行标量运算,然后将 float32 结果舍入为 float16.可以证明结果仍然正确舍入:float32 的精度足够大(相对于 float16 的精度)双舍入在这里不是问题,至少对于四个基本算术运算和平方根。

在当前的 NumPy 源代码中,float16 标量运算的四种基本算术运算的定义如下所示。

#define half_ctype_add(a, b, outp) *(outp) = \
        npy_float_to_half(npy_half_to_float(a) + npy_half_to_float(b))
#define half_ctype_subtract(a, b, outp) *(outp) = \
        npy_float_to_half(npy_half_to_float(a) - npy_half_to_float(b))
#define half_ctype_multiply(a, b, outp) *(outp) = \
        npy_float_to_half(npy_half_to_float(a) * npy_half_to_float(b))
#define half_ctype_divide(a, b, outp) *(outp) = \
        npy_float_to_half(npy_half_to_float(a) / npy_half_to_float(b))

以上代码取自 scalarmath.c.src in the NumPy source. You can also take a look at loops.c.src for the corresponding code for array ufuncs. The supporting npy_half_to_float and npy_float_to_half functions are defined in halffloat.c,以及 float16 类型的各种其他支持函数。

对于第二个问题:不,NumPy 中没有 float8 类型。 float16 是一种标准化类型(在 IEEE 754 标准中进行了描述),已在某些情况下(尤其是 GPU)广泛使用。没有 IEEE 754 float8 类型,并且似乎没有 "standard" float8 类型的明显候选者。我还猜测对 NumPy 中 float8 支持的需求并不多。

此答案基于问题的 float8 方面。已接受的答案涵盖了其余 well.One 没有被广泛接受的 float8 类型的主要原因,除了缺乏标准之外,它实际上不是很有用。

浮点入门

在标准表示法中,float[n] 数据类型使用 n 位存储在内存中。这意味着最多只能表示 2^n 个唯一值。在 IEEE 754 中,其中一些可能的值,如 nan,本身并不是偶数。这意味着所有浮点表示(即使你去 float256)在它们能够表示的有理数集合中都有间隙,如果你试图在这个差距。通常 n 越高,这些差距越小。

如果您使用 struct 包来获取一些 float32 数字的二进制表示,您可以看到实际的差距。一开始进入 运行 有点吃惊,但是在整数 space:

中有一个 32 的差距
import struct

billion_as_float32 = struct.pack('f', 1000000000 + i)
for i in range(32):
    billion_as_float32 == struct.pack('f', 1000000001 + i) // True

通常,浮点数最适合仅跟踪最高有效位,这样如果您的数字具有相同的小数位数,重要的差异就会被保留下来。浮点标准通常仅在它们在基数和指数之间分配可用位的方式上有所不同。例如,IEEE 754 float32 使用 24 位作为基数,8 位作为指数。

返回float8

根据上述逻辑,一个 float8 值只能取 256 个不同的值,无论您在基数和指数之间划分位时多么聪明。除非您热衷于将数字四舍五入到聚集在零附近的 256 个任意数字之一,否则仅跟踪 int8 中的 256 种可能性可能更有效。

例如,如果您想以粗精度跟踪一个非常小的范围,您可以将您想要的范围分成 256 个点,然后存储您的数字最接近 256 个点中的哪一个。如果你想变得非常花哨,你可以根据对你最重要的事情,将值的非线性分布聚集在中心或边缘。

其他人(甚至以后你自己)需要这个确切方案的可能性 非常 并且大多数时候你支付的额外字节或 3 作为惩罚使用 float16float32 太小而无法产生有意义的差异。因此......几乎没有人愿意写一个 float8 实现。