理解浮点数及其二进制以及定点数的转化

浮点数

我们先看下2进制下的浮点数的表达形式,并不复杂,所以我直接举个例子,一目了然:

十进制173.8125 转换成二进制小数

  • 首先取出整数部分:173
  • 173->10101101
  • 再取出小数部分:0.8125
  • 将小数部分乘以2,一直到为整数为1,或者达到指定位数的精度:
0.8125*2 = 1.625
0.625*2 = 1.25
0.25*2 = 0.5
0.5*2 = 1

将整数结果和小数结果结合:

173.8125->10101101.1101

反过来的时候,就是整数部分正常二进制转换十进制,小数部分则是乘以2^-1

如小数部分1101:

  • 1*2^-1 + 1*2^-2 +0*2^-3 + 1*2^-4=0.8125

二进制小数的科学计数法:

  • 10101101.1101=1.010110111012^7
    乘以2^7表示表示向右移动7位小数点,和十进制里意思是一致的。也可以表示成:1.01011011101
    2\^(0111)

好,有了上面的基础,我们再看下浮点数在10进制下的存储形式,根据IEEE 754标准,浮点数是这样表示的:

其中规定

  • 1<=M<2
  • E>0

在32位float下 E的为8位,取值范围为0-255 ,因为在科学计数法中,指数是可以出现负值的,所以,加了个规定,127为偏置 注意double的偏置是1023。即假设正常指数为3,则存的时候指数位为130,就是有127的偏移。同理,取真实指数位的时候,要减去127。如果存储的指数是50,则实际的指数就是50-127=-77。上面我们提到指数的范围是0-255,但是需要注意的是0和255这两个是特殊值,其中255表示无穷大,0则是表示一个非常接近0的一个数。这俩在IEEE里都有专门的定义,并且有专门的特殊值:

  • NaN错误:
    • 实数范围内对负数开平方产生此错误,此时指数全为1
  • 正负无穷大:
    • 无穷大的一个值,比如很大的数在相乘产生溢出,此时,指数全为1,尾数全为0
  • 正负0
    • 标准格式中,小数点左侧的1是隐藏的,因此0无法用这种方式表达所以规定指数和尾数全为0时候表示0

所以,综上,指数范围0-255,偏置127 则实际是-127到128,除了全1和全0两种规定的特殊情况,则范围是-126-127

  • max=1.111111111111*2^127
  • 正数的最小值是:1.000000000000*2^-126

由于尾数是23位,尾数部分的分辨率为1/2^23 约为0.000000119,也就是说小数部分可以保证到第6位,即精度可以保证6位,最大到7位。

好了,我们再说下浮点数的存储形式,还是以173.8125为例:

  • 二进制为10101101.1101 -> 1.01011011101*2^7
  • 指数为7,加上偏置127,为134,二进制为:1000 0110
  • 尾数部分:01011011101
  • 最后将符号,指数,尾数,拼在一起,注意默认的小数点前的1是不存的:
    • 0 1000 0110 010110111010000...

有个工具可以帮我们快捷验证下:

如图,和我们算的一致,没有问题。

定点数

好,现在可以讲定点数的部分了。定点数看起来是个整数值,但是它是有固定位数的整数部分和小数部分,常见的表达形式就是Qm.n,m是整数的位宽,n就是小数位宽。一般默认是有符号的,就是默认有个1位符号位,整体位宽就是m+n+1。之前也有同事习惯称mPn,m就是整数位宽,整体位宽就是m+n。Qm.n能表示的范围是[-2^m,2^m - 1/2^n]。我们以Qm.n为例,比如Q1.2,就是说整数部分有1位,小数部分有2位,加上符号位,总共就是4位,范围就是[-2,1.75]。假设有Q1.2的有符号定点数为4'b10.11,则它的十进制就是这样算的:

  • 4'b10.11 = 1*(-2^1)+0*(2^0)+1*(2^-1)+1*(2^-2) = -2 + 0 + 0.5 + 0.25 = -1.25

两个定点数相加减时,要做小数位对齐的操作,还要考虑溢出,比如5'b100.01(−3.75) + 4'b1.011(−0.625)

  • 先对齐小数点:5'b100.01 -> 6'b100.010
  • 作位宽扩展,扩展为7bit:
    • 6'b100.010 -> 7'b1100.010
    • 4'b1.011 -> 7'b1111.011
  • 最后相加:
    • 7'b1100.010 + 7'b1111.011 = 7'b1011.101(−4.375)

为什么相加只扩展1bit?考虑6'b100.010为Q2.3格式,Q2.3数据范围为-4 -> 4-2^3 两个Q2.3数据相加,范围为-8 -> 8-2^2

而Q3.3数据范围是 -8 -> 8-2^3,大于两个Q2.3范围的数相加,所以扩展1bit到7bit一定不会溢出。

接下来要再讲一下定点数运算的四舍五入,这个关乎到算法运算的精准度。对于二进制小数的四舍五入,可以参考10进制的思想,比如0.026保留两位小数,四舍五入到0.03。要去除的小数位在第3位,也就是0.006,规则是当第3位 >= 0.005时,就向第2位进位,也就是 >= 0.5*10^-2,要向上进位。类似的,当二进制小数,比如Q5.29 -> Q5.28 时候,就是要舍掉最后一位29位,则当第29位 >= 0.5*2^-28 == 2^-29,也就是说第29位是1的时候,就向28位进1。

所以,要丢掉某个位的值,则四舍五入的原则就是加上(或减去)这个位的一半的值,就是0.5 * val,比如2.346 保留两位小数,就是要舍掉千分位,则就加上 0.005 ,所以千分位的一半 就是 0.01 * 0.5 = 0.005 ,也就是要加上的值,用来作四舍五入。0.01其实百分位的最小单位,其实可以理解为百分位是千分位的权值。也可以类比10进制去思考,10进制下的四舍五入,想到的就是加5,因为是10进制,底数就是10,2进制的四舍五入,底数就是2,所以应该是加1。

所以2进制有个规则就是,要舍去的第几位小数位,就加上半个LSB(新精度下的),比如4p29变成4p28,要舍去第29位,原本的精度是2^-29,权值是2^-28,所以就加上2^-28 * 0.5 ,就是2^-29。在定点数下,其实就是加1。如果是变为4p27,则减了2个小数位,就是加上2^-27*0.5 等于 2^-28,则就是加上二进制10,再算术右移2位,所以原位宽和新位宽相差n位,就加上2^(n-1)。

还没完,还要考虑2进制小数的负数情况,我们以上提到四舍五入,默认入的时候都是远离0方向的,正数好理解就是向上,负数其实就是向下。比如-0.67入到-0.7。很多语言都有几种不同四舍五入的函数,matlab里也有。

在10进制情况下 -2.346 + -0.005 = -2.35 就是减去0.005

但是2进制小数不太一样,因为2进制小数的最高权是-2^m,小数点后面的是正数,就是加的关系,加的越多,越靠近0的方向,

比如9'b100.101101(−3.296875), 要舍去的是101,这个是大于等于100的,也就是加的数大于100,所以相对于直接抹去后三位小数,原本的值是更靠近0方向的,所以小数直接进位,向更靠近0的方向,所以其实是要加的2^-3 * 0.5 = 2^-4,也就是要加上.000100

对于定点数来说就是,就是加上000100,也就是加上2^2 = 4 = 100.110001(−3.234375),算术右移3位=100.110(−3.25),可以看到在Q2.6四舍五入到Q2.3,也就是小数位少3位,精度一下就下来了,在verilog里以上的运算基本就是右移的运算了。最后强调下,verilog里如果要做有符号数的运算,则两个变量都要以signed来声明,如果其中一个有符号一个无符号,则会统一按照无符号来运算:

参: