(.NET)引入半类型

2020-09-01 15:09:47

IEEE754规范定义了许多浮点类型,包括:二进制16、二进制32、二进制64和二进制128。大多数开发人员熟悉binary32(相当于C#中的Float)和binary64(相当于C#中的Double)。它们提供了一种标准格式来以许多应用程序可以接受的精度表示大范围的值。.NET一直有Float和Double两种类型,在.NET5Preview 7中,我们添加了一个新的Half类型(相当于binary16)!

一半是占用16位的二进制浮点数。如果位数的一半为浮点数,则半个数字可以表示±65504范围内的值。更正式地说,Half类型被定义为一种基数为2的16位交换格式,旨在支持实现之间的浮点数据交换。半类型的主要用例之一是节省计算结果不需要以全精度存储的存储空间。许多计算工作负载已经利用了半类型:机器学习、显卡、最新处理器、本机SIMD库等。使用新的半类型,我们希望在这些工作负载中解锁许多应用程序。

尽管有效数由10位组成,但总精度实际上是11位。假定该格式具有值为1的隐式前导位(除非指数字段全为零,在这种情况下,前导位具有值0)。要以半格式表示数字1,我们将使用以下位:

前导位(我们的符号位)为0,表示正数。指数位为01111,或十进制为15。但是,指数位并不直接表示指数。取而代之的是,定义了指数偏差,让格式既表示正指数又表示负指数。对于半类型,指数偏差是15。真实指数是从存储的指数中减去15得出的。因此,01111表示指数e=01111(二进制)-15(指数偏差)=0。有效位是0000000000,在我们的例子中,它可以解释为基数为2,0的数字.有效位(以2为基数)。例如,如果有效位是0000011010(十进制为26),我们可以将其十进制值26除以10位(1<;10)可表示的值数:因此,有效位0000011010(二进制)为26/(1;lt;<;10)=26/1024=0.025390625。最后,因为我们存储的指数位(01111)不全是0,所以我们具有隐式前导位1。因此,

一般情况下,半个半值的前16位数被解释为:1-1^(符号位);**2^(StoredExponent*-1 5);*(隐含位数)+1(有效位/1 0 2 4)。*存在一种特殊情况,即存储的指数为00000。在这种情况下,2位数被解释为1-1^(符号位)**2^。在这种情况下,2位数被解释为0-1^(符号位)**2^。

(注:此处隐含的数字位数为0,因为所有存储的数字指数和位数都为0)。

任何半值,因为一半只使用16位,所以可以表示为浮点/双精度,而不会损失精度。然而,反之亦然。当从浮点/双精度变为一半时,某些精度可能会丢失。在.NET5.0中,Half类型主要是没有定义算术运算符的交换类型。它只支持解析、格式化和比较运算符。所有算术运算都需要显式转换为浮点/双精度。未来的版本将考虑直接在一半上添加算术运算符。

作为库作者,需要考虑的一点是语言将来可以添加对类型的支持。可以想象,未来C#会增加半个类型。语言支持将启用诸如f16这样的标识符(类似于目前存在的f)和隐式/显式转换。因此,库定义的类型Half需要以这样的方式定义:如果Half成为现实,则不会导致任何破坏性更改。具体地说,我们在向Half类型添加运算符时需要小心。如果添加语言支持,到浮点/双精度的隐式转换可能会导致潜在的破坏性更改。另一方面,在Half类型上具有浮点/双精度属性感觉不太理想。最后,我们决定添加显式运算符来转换浮点/双精度浮点数。如果C#确实添加了对一半的支持,则没有用户代码会中断,因为所有类型转换都是显式的。

我们预计有一半会进入许多代码库。半类型填补了.NET生态系统中的一个空白,我们希望许多数值库都能利用它。在开源领域,ML.NET预计将开始使用一半,Apache Arrow项目的C#实现有一个公开问题,DataFrame库在这里跟踪相关问题。随着x86和ARM处理器的.NET中更多的内部功能被解锁,我们预计计算性能可以减半,并产生更高效的代码!