取值范围及精度

可以表示的范围为±3.40282 * 10^38(1.1111…1×2^127)即:

0-11111110-11111111111111111111111(23个1)

深度学习 单精度和半精度 单精度范围_深度学习 单精度和半精度

单精度浮点数可以表示1.175 * 10-38(1.00…0×2^-126)的数据而不损失精度。

0-00000001-00000000000000000000001(22个0,最后一位是1)

深度学习 单精度和半精度 单精度范围_深度学习 单精度和半精度_02

浮点数最小能表示的是当阶码都是0时,表示2^-126*0.fractionbits

深度学习 单精度和半精度 单精度范围_深度学习 单精度和半精度_03

ps:以上图片是从 这个网址 截取。

表示方式

  1. 如果指数位全零,尾数位是全零,那就表示0
  2. 如果指数位全零,尾数位是非零,就表示一个很小的数(subnormal),计算方式 (−1)^signbit × 2^−126 × 0.fractionbits(注意这里是0.fractionbits,应该是为了和阶码是-126的时候做出区分,其实也就是比-126的时候能表示的数更小了)
  3. 如果指数位全是1,尾数位是全零,表示正负无穷
  4. 如果指数位全是1,尾数位是非零,表示不是一个数NAN
  5. 剩下的计算方式为 (−1)^signbit × 2^(exponentbits−127) × 1.fractionbits

补码

首先,第一个问题,补码是用来做什么的?

补码是用来方便ALU做减法运算的,因为补码是没有符号的,减一个数相当于加这个数的补码。

所以,第二个问题就是,为什么减一个数相当于加一个数的补码呢?

在回答这个问题之前,首先问一下,如果补码就是简单的反码加一为什么要叫补码,为什么不直接叫反码加一呢,这里就要提出一个概念,叫补数

钟表的例子

这个例子就是补数的直观理解,先假定表盘就表示0-11点,然后现在指针指向2点,如果我想将指针拨到3点有两个办法,第一个是顺时针拨1个格,第二个是逆时针拨11个格,在一个满刻度是12的表盘上,这两个的效果是一样的。抽象来讲,2 - 1 = 2+11

这是为什么呢,这就有个的概念,模,当然数学上有抽象解释,我们这里就可以理解成数的表示极限大小,这里的模就是12,而对于十进制的两位数来说,模就是100。

继续回到 2 - 1 = 2+11 这个问题,因为模是12,所以在这个模下,每个数有其补数,补数的意思就是模减其本身。这里1的补数就是 12 - 1 = 11.而减去一个数,就相当于加上这个数的补数,所以我们得到2 - 1 = 2+11。

再以十进制两位数为例,90 - 10 = 80.10的补数是100-10=90,所以 90 -10 = 90 + 90(忽略百位,因为这里只有两位)。到这里总结一下,一个数的补数 = 模 - 这个数,一个数 - 另一个数 = 一个数 + 这个数的补数

但是如果是10 - 90 怎么办,难道10 - 90 = 10 + 10,-80难道等于20?没错!我们想用数表示-80,却不让加负号,那就直接让-80 = 20。所以,一个负数就用它绝对值的补数来表示

那么,现在的问题是,一个数既可以表示正数又可以表示负数,比如20既可以表示-80也可以表示20,那咋整,就规定0~99,0~49表示整数,50~99表示负数,90就代表-10这种。

实际上

对于浮点数的阶码是8位二进制数,其表示的极限是256(11111111表示255),所以模就是256,根据上面讲过的,将表示范围一分为二:00000001~01111111表示正数,10000000~11111110表示负数(全0和全1有特殊含义)。这样结合上面讲的知识就显而易见了,以10000000为例,256 - |x| = 128.所以表示的x=-128

移码

虽然补码解决了负数的问题,但是补码还是有一定的缺陷,就是比较大小不方便,而进行浮点数运算的时候,有一步是对阶,也就是比较阶码的大小然后再获得浮点数实际大小。为了方便比较大小,浮点数使用移码表示阶码。

移码,顾名思义,就是当前码通过(在坐标轴上)移动之后获得的码,而移动的距离称为偏置(bias)。

为什么移动之后就方便比较大小呢,具体如下图:

不用移码的时候数轴是这样的:

深度学习 单精度和半精度 单精度范围_补码_04

上面是实际的数,而下面是这些数代表的数,也就是上面讲的,254代表-2之类的。

如果我们使得下面的数向右移动一格:

深度学习 单精度和半精度 单精度范围_数位_05

可以看到,现在254代表的是-3了,再移动一格呢:

深度学习 单精度和半精度 单精度范围_数位_06

依次类推,可以一直推到255代表的是最大的正数,这样,就可以直接通过比较码的大小来判断实际值的大小了,是不是很方便呢

不过这里没有考虑全0和全1的情况,但是大概原理就是这样了。

半精度与单精度的转换

主要是最近在研究f16和f32的转换才看了上面一堆东西,正题是f16和f32是怎么转换的。

这个就简单了,由上面的知识可以推知,half的表示范围最大也就到65535,而float则很大,因此当half往float转换时,就是指数位转换到指数位,小数位低13位补零。当然考虑到阶码是移码,因此要-15+127才是最终的阶码。同理,从float转换到half也是,阶码-127+15,然后砍掉小数位后13位即可。

这里要注意,如果float原本表示的数超过了half的表示范围,那么转换成的half就是阶码全是1的NaN。

cite:Van Der Zijp J. Fast half float conversions[R]. Working paper, 2012ftp://www. fox-toolkit.org/pub/fasthalffloatconversion. pdf, 2008.