现代计算机中数字的表示与浮点数、定点数
导读:浮点数运算是一个非常有技术含量的话题,不太容易掌握。许多程序员都不清楚使用==操作符比较float/double类型的话到底出现什么问题。这篇文章讲述了浮点数的来龙去脉,所有的软件开发人员都应该读一下。而FPGA开发人员则需要知道定点数,首先要明确的是,定点数的说法是相对浮点数来说的,这篇文章同样讲述了定点数的理解。随着R&D经验的增长,自然而然会想去深入了解一些常见的东西的细节,浮点数与定点数运算就是其中之一。
-
浮点数规则与其运算
1.1什么是浮点数?
在计算机系统的发展过程中,曾经提出过多种方法表达实数。
【1】典型的比如相对于浮点数的定点数(Fixed Point Number)。在这种表达方式中,小数点固定的位于实数所有数字中间的某个位置。货币的表达就可以使用这种方式,比如 99.00 或者 00.99 可以用于表达具有四位精度(Precision),小数点后有两位的货币值。由于小数点位置固定,所以可以直接用四位数值来表达相应的数值。SQL 中的 NUMBER 数据类型就是利用定点数来定义的。
【2】浮点数表达方式, 这种表达方式利用科学计数法来表达实数,即用一个尾数(Mantissa ),一个基数(Base),一个指数(Exponent)以及一个表示正负的符号来表达实数。比如 123.45 用十进制科学计数法可以表达为 1.2345 × 102 ,其中 1.2345 为尾数,10 为基数,2 为指数。浮点数利用指数达到了浮动小数点的效果,从而可以灵活地表达更大范围的实数。尾数有时也称为有效数字(Significand),尾数实际上是有效数字的非正式说法。
同样的数值可以有多种浮点数表达方式,比如上面例子中的 123.45 可以表达为 12.345 × 101,0.12345 × 103 或者 1.2345 × 102。因为存在这种多样性,有必要对其加以规范化以达到统一表达的目标。规范的(Normalized)浮点数表达方式具有如下形式:
d.dd...d × β^e , (0 ≤ di < β)
其中 d.dd...d 即尾数,β 为基数,e 为指数。尾数中数字的个数称为精度,在本文中用 p(presion) 来表示。每个数字 d 介于 0 和基数β之间,包括 0。计算机内部的数值表达式是基于二进制的。从上面的表达式可以知道,二进制数同样可以有小数点,也同样具有类似于十进制的表达方式。特点是β 等于 2,而每个数字 d 只能在 0 和 1 之间取值。
1.2IEEE 754浮点数
计算机中是用有限的连续字节保存浮点数的。IEEE (美国电气和电子工程师学会)定义了多种浮点格式,但最常见的是三种类型:单精度、双精度、扩展双精度,分别适用于不同的计算要求。一般而言,单精度适合一般计算,双精度适合科学计算,扩展双精度适合高精度计算。一个遵循IEEE 754标准的系统必须支持单精度类型(强制类型)、最好也支持双精度类型(推荐类型),至于扩展双精度类型可以随意。单精度(Single Precision)浮点数是32位(即4字节)的,双精度(Double Precision)浮点数是64位(即8字节)的。比如Java 平台上的浮点数类型 float 和 double 采纳了 IEEE 754 标准中所定义的单精度 32 位浮点数和双精度 64 位浮点数的格式。
在 IEEE 标准中,浮点数是将特定长度的连续字节的所有二进制位分割为特定宽度的符号域,指数域和尾数域三个域,其中保存的值分别用于表示给定二进制浮点数中的符号,指数和尾数。通过尾数和可以调节的指数(所以称为"浮点")就可以表达给定的数值了。
长度 | 符号S | 指数P | 尾数M | 有效位数/隐含位 | 指数偏移 | |
单精度 | 32bit | 1b | 8b | 23b | 24b/1b | 127 |
双精度 | 64bit | 1b | 11b | 52b | 53b/1b | 1023 |
拓展双精度 | 80bit | 1b | 15b | 64b | 64b/0b | 16383 |
需要特别注意的是,扩展双精度类型没有隐含位,因此它的有效位数与尾数位数一致,而单精度类型和双精度类型均有一个隐含位,因此它的有效位数比位数位数多一个。
为了强制定义一些特殊值,IEEE标准通过指数E将表示空间划分成了三大块:
- 最小值指数(所有位全置0)用于定义0和弱规范数,E=0,M=0,数值表示0;
- 最大指数(所有位全值1)用于定义±∞和NaN(Not a Number),E=255,M=0时,表示无穷, 当P=255,M≠0时,表示NaN ;
- 其他指数用于表示常规的数。
现代计算机中的符号数有三种表示方法,即原码、反码和补码。
如补码的求取:
① 正数(符号位为0的数)补码与原码相同.
② 负数(符号位为1的数)变为补码时符号位不变,其余各项取反,最后在末尾+1;即求负数的反码不包括符号位。
在计算机系统中,数值一律用补码来表示和存储。原因在于:①使用补码,可以将符号位和数值域统一处理;②同时,加法和减法也可以统一处理。此外,③补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
1.3浮点数的误差
很多小数根本无法在二进制计算机中精确表示(比如最简单的 0.1)由于浮点数尾数域的位数是有限的,为此,浮点数的处理办法是持续该过程直到由此得到的尾数足以填满尾数域,之后对多余的位进行舍入。
换句话说,除了我们之前讲到的精度问题之外,十进制到二进制的变换也并不能保证总是精确的,而只能是近似值。
事实上,只有很少一部分十进制小数具有精确的二进制浮点数表达。再加上浮点数运算过程中的误差累积,结果是很多我们看来非常简单的十进制运算在计算机上却往往出人意料。这就是最常见的浮点运算的"不准确"问题。
讲完了浮点数,如果能理解浮点数的话,那么定点数就相当好理解了。
-
定点数规则与其运算
2.1什么是定点数?
首先要明确的是,「定点数」的说法是相对「浮点数」来说的。定点数,即fixed-point number, 在常用的货币10进制中,比如12.50, 就表示十二元5角0分,在货币交易中默认的都是2位小数点,也就是所谓的定点数。约定计算机中小数点的位置,且这个位置固定不变,用这种方式表示的数字就叫做定点数。
2、定点数如何表示数字?
(1)无符号数
无符号数的最高位不表示符号,仅表示数值。比如我们想表示一个10.125的8bit数字,小数定点4bit,整数也是4bit,那么计算机中的数字即为1010.0010。
(2)有符号数
有符号数的最高位表示符号,0表示正数,1表示负数。那么实际上我们想表示一个+10.125的8bit数字,小数定点4bit,整数则为3bit,符号1bit,那么计算机中的数字即为0111.1111(7.9375),因为仅能表示最大示值。10.125存在溢出,溢出的处理各个计算机系统不同,有些系统中是选取最大值,有些则以若直接截取,还是以+10.125为例,01010.0010,假设截取低8bit,则为1010.0010(补码,-5.875),示值完全错误!这就涉及到了定点数的数值范围。
2.2定点数的数值范围
如果想表示更大范围、更高精度的值,怎么办?
- 扩大整体位宽:比如使用 16位、32位来表示, 这样相应地整数部分和小数部分的宽度都可以增加,自然表示范围也就变大了。但是位宽的增加,也会带来更多的硬件资源开销;
- 对固定bit位宽而言,小数点向后移动,整个数字范围就会扩大,但是小数部分的精度则会降低。小数点向前移,则精度会变高,但是数字的表示范围又会降低。所以说定点数小数点位置的确定是一个范围和精度的trade off(权衡)
- 其实在算法部署的过程中,从算法到硬件如FPGA中最重要的一步也就是定点化。所谓的定点化,就是在不损失算法性能的前提下,利用最少的硬件资源去实现我们想要的算法,简单的比如FIR滤波器等,具体可以参见我的博客《DSP48E2使用以及FIR滤波器定点设计实现与优化》。
-
总结
相对于浮点数而言,定点数的精度与数值范围均不能与之比较,定点数不仅数值的范围表示有限,而且其精度也很低。但好处就在于定点数的位宽可以设计,那么对于特定的系统而言,其资源就是最优的。定点数在硬件上比较容易实现,在实际的数字信号处理算法,如射频算法与基带算法中,定点数运算效率比浮点数的运算效率高很多,且资源开销少。因此,定点数被广泛地应用在数字信号处理的各种应用场景中。常见的定点数系统为FPGA,ASIC,MCU,部分DSP,浮点机为CPU,GPU,部分DSP等,此外当前的FPGA的底层资源甚至包含了FPU浮点运算单元。