您的当前位置:首页正文

浮点数的存储(Normalized Denormalized 特殊值)(f64/32/16 + bf16)

2024-11-11 来源:个人技术集锦

在正常的浮点数中, 有效数部分开头的不是0,0被移动到指数部分去了,比如0.0123会被表示为1.23*2-2。Denormal numbers(非规格数)就是指数部分非常小的数字。这些数字的有效数部分首位用0来表示。

IEEE浮点标准使用 $ V=(-1)s×M×2E$ 的形式来表示一个数。

符号位(sign),与补码类似,s为1代表这个数为负,s为0代表这个数为正

阶码(exponent)E的作用是对浮点数加权,权重是2的E次幂。

尾数(significand)M是一个二进制小数,范围是1到2−ϵ1到2−ϵ(规格化)或者0到1−ϵ0到1−ϵ(非规格化)

规格化的值(Normalized Values)
在 exp≠000…0 和 exp≠111…1 时(阶码部分不全为0或全为1),表示的值就叫规格化值。到底规格化是什么意思呢?对于规格化数来说,用二进制数来表示时,原本连续的值会被规范到有限的定值上。如果把规格化的数放到数轴上表示,那他们之间的距离是不同的。(后面会举例子解释,现在不懂不用太担心)

对于规格化值,阶码字段(exp这部分)的编码区域的无符号值为e,但阶码E的值是E=e−Bias,注意这里不要把大小写e和E搞混。

Bias是指偏移量,值为$ 2^{k-1}-1 $,k是阶码字段的位数(32bit:127, 64bit:1023)。

(无符号的值怎么表示负数呢?比如32bit的浮点数,阶码取值0-255,由于0和255有特殊表示,于是范围缩小为1-254,为了表示负数,所以用 “指数”-”中间值“的办法,这个中间值就是前面提到的Bias(127), 比如想1.xxx * 2^-4, 则最终阶码e = -4+127)

非规格化的值(Denormalized Values)
当阶码域exp全为0时,所表示的数就是非规格化形式。这里的意思是,原本用二进制表示的连续值,它们之间的间距是一样的。

在这种情况下有两点不同。对于阶码字段,阶码值E改成了E = 1 - Bias。也就是说

单精度非规格化数的E是-126

双精度非规格化数的E是-1022

(为什么不是E = - Bias,这样不是更小吗?还拿32bit举例,因为前面说过阶码的取值为1-254( - 127),而规格化的尾数是1.xxx,所以为了数据的连续性,这里阶码域exp全为0时,还以规格化时的最小阶数1-127来表示阶数,但尾数是以0.xxx表示了)

对于尾数字段,尾数的值是 M = f。也就是说,M不再是一个以1开头的小数,而是以0开头的小数。这所以这样设置是有原因的。首先,非规格化数定义了一种表示零的方法,如果使用规格化数,由于总有 M ≥ 1,那么我们就不能表示0。实际上,零的表示就是exp和frac字段全为0,因此,对于浮点数来说,还有正零和负零的区别。非规格化数的另一个用途就是表示非常接近零的数。这种机制实现了由最大非规格化数到最小规格化数的平滑转换。

特殊值
第一类是当exp全为1,frac全为0时,表示的值为无穷。当符号位 s = 1 代表负无穷,s = 0 代表正无穷。当小数域不是0,那就代表NaN(Not a Number),不是一个数,表示出错。例如计算-1开根号的值就是NaN。

(下面这张图显示了各种float数在数轴上的相对位置)

举例如何将一个浮点数转换为计算机内存储的内码形式

例如十进制数:25.5 对应的二进制形式为:11001.1 ,改写成相应的指数形式为:1.10011*2^4

25.5在float的精度范围内表示绰绰有余,假设我们用单精度浮点数来存储它,符号位S=0(正数),负数的符号位为1,8位指数位为127+4=131(其中127为指数的偏移量),至于为什么要加上偏移,且偏移为何偏偏为127的原因,我们后面会讨论,我们暂且按规定来,指数位131转换为二进制位:1000 0011,接下来小数位M=10011,原始的小数位应该是1.10011,为什么可以省略小数点前面的1呢?因为我们用来存储小数位的二进制bit位是有限的,而我们希望利用有限的二进制bit位来尽可能大存储浮点数的精度范围,因此我们可以将小数点前面的默认的1可以省略,只要我们在还原时记得加上小数点前面的1就可以。

因此25.5用float类型来存储的编码为:0 1000 0011 0000 0000 0000 0000 0010 011

如果是负浮点数只需要将最高位的符号位改为1,其他步骤一样。

FP16

FP16也叫做 float16,两种叫法是完全一样的,全称是Half-precision floating-point(半精度浮点数),在IEEE 754标准中是叫做binary16,简单来说是用16位二进制来表示的浮点数,来看一下是怎么表示的(以下图都来源于维基百科[2])

Sign(符号位): 1 位,0表示整数;1表示负数。
Exponent(指数位):5位,简单地来说就是表示整数部分,范围为00001(1)到11110(30),正常来说整数范围就是
,但其实为了指数位能够表示负数,引入了一个偏置值,偏置值是一个固定的数,它被加到实际的指数上,在二进制16位浮点数中,偏置值是 15。这个偏置值确保了指数位可以表示从-14到+15的范围即
,而不是1到30,注:当指数位都为00000和11111时,它表示的是一种特殊情况,在IEEE 754标准中叫做非规范化情况,后面可以看到这种特殊情况怎么表示的。
Fraction(尾数位):10位,简单地来说就是表示小数部分,存储的尾数位数为10位,但其隐含了首位的1,实际的尾数精度为11位,这里的隐含位可能有点难以理解,简单通俗来说,假设尾数部分为1001000000,为默认在其前面加一个1,最后变成1.1001000000然后换成10进制就是:

# 第一种计算方式
1.1001000000 = 1 * 2^0 + 1 * 2^(-1) + 0 * 2^(-2) + 0 * 2^(-3) + 1 * 2^(-4) + 0 * 2^(-5) + 0 * 2^(-6) + 0 * 2^(-7) + 0 * 2^(-8) + 0 * 2^(-9) = 1.5625
# 第二种计算方式
1.1001000000 = 1 + 576(1001000000变成10进制)/1024 = 1.5625

贴一个FP16(float16)特殊数值的情况:

BF16

BF16也叫做bfloat16(这是最常叫法),其实叫“BF16”不知道是否准确,全称brain floating point,也是用16位二进制来表示的,是由Google Brain开发的,所以这个brain应该是Google Brain的第二个单词。和上述FP16不一样的地方就是指数位和尾数位不一样,看图

(申明:本文基于 https://blog.51cto.com/u_6760421/3618364 和 https:///tsqer1987/article/details/38591295 修改)

显示全文