浮点数
我们先看下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.010110111012\^(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来声明,如果其中一个有符号一个无符号,则会统一按照无符号来运算:
参: