其他分享
首页 > 其他分享> > IEEE754浮点数表示法

IEEE754浮点数表示法

作者:互联网

IEEE二进制浮点数算术标准(ANSI/IEEE Std 754-1985)是一套规定如何用二进制表示浮点数的标准。就像“补码规则”建立了二进制位和正负数的一一对应关系一样,IEEE754规则说明了一个从二进制状态到实数集的一一映射的规则(当然事实上状态有限而实数无限,叫做“单射”更为合适)。

IEEE754的初标准在1985年发布,也是现在广为流传的版本,被大多数语言所采用。事实上后来已经有了更新的标准了,不过两者间没有太大的区别。因此了解老标准就可以。

浮点数是如何存储的

标准提供了四种最常见的规范:

  1. 单精度(single)浮点(32bit)
  2. 双精度(double)浮点(64bit)
  3. 延伸单精度(extended single)浮点(43bit以上,很少用到)
  4. 延伸双精度(extended double)浮点(79bit以上)。

沿用C/C++习惯,可以用float代指32位单精度浮点、double代表64位双精度浮点。以下主要以较短的float进行说明。

一个32位float型数用科学计数法表示,由符号位1位(sign)、指数位8位(exponent)和小数位23位(fraction)组成,在图里从左到右排列。

一个64位double型数由符号位1位、指数位11位和小数位52位组成,在图里从左到右排列。

二进制里的科学计数法

十进制和二进制的互化大家都很熟悉,但是一般仅限于整数,许多计算器软件在二进制下甚至不能输入小数点。

不过小数的转化其实也是一个道理:对于整数位来说,第\(i\)位的1代表\(2^i\),而小数点后的第\(i\)位1则代表\(2^{-i}\)。比如\(110.101_{bit}=4+2+\frac 12 +\frac 18=6.625\)

将十进制数化为二进制数就反过来弄:小数部分大于0.5,则第一位为1,小数部分"模0.5"后大于0.25,则第二位为1。。。比如\(0.875=0.111_{bit}\)

在十进制中,如果用科学计数法表示数,最规范的表示就是让底数的小数点之前仅有一位非零整数,便于用指数表示数量级。在二进制中,我们也这样干,并且可以得到更特殊的性质:二进制中的"非零整数"只能是1。也就是任意一个不是太接近0的数都可以表示为\(1.xxxx \times 2^{exp}\)的形式。因此在表示小数位时,我们将这个首位1省略,只保存小数部分,显然对于一个不是太接近0的数,这样的表示都是益于节省空间提高精度的。

写出一个数的浮点表示

image

特殊的浮点位

IEEE754标准还提供了浮点数中一些特殊状态的表示。

非规约数 & 正零和负零

上述规则描述的是常规范围内的数如何表示,他们可以叫做规约数(normal number)。高位1的省略可以节省空间。这样最接近0的数(即0x00000000)值为\(\pm 2^{-127}\)

但是如果一个数太小,他的第一位有效数字(当然指二进制)在127位以后呢?即使小数点右移127位,最高位仍然是0,不能表示更小的数了。

为了表示更小的数,在指数位全为0时,我们丢掉最高位为1的束缚,将最高位规定为0,将"全0指数位"规定为-126而不是本来的-127,用于表示绝对值小于\(2^{-126}\)的数。

比如00000000000101000000000000000000,其值为\(0.00101\times 2^{-126}=1.01*\times 2^{-129}\),表示出了更小的数。在这样的规则下,最接近0的数(即0x00000001)值为\(\pm 2^{(-127-23)=-149}\),而全零位用来存储0。

这样的“全零位”,由于符号原因有两种(0x000000000x80000000),他们用于表示正零和负零。高级应用层面对于正零和负零的判定各不相同。在C++,正零和负零是相等的,并且都对应布尔值false(尽管负零的符号位)。我们不关心,我们只需要知道IEEE支持两种零的表示,并且在运算过程一个理论答案为零的结果既可能被计算为正零,也可能被计算为负零。

逐渐溢出

规格数的最小值为\(0(00000001)0..0_{bit}=2^{-126}\),非规格数的最大值为\(0(0..0)1..1_{bit}=(1-2^{-23})2^{-126}\),基本可以看做\(2^{-126}\)的开区间,从非规格数过渡到规格数时,相当于指数-126不变,底数进位到隐藏的高位。从而实现了平稳的值域过渡,刚好覆盖了实数轴,这种特性叫做逐渐溢出(gradual overflow)

更有意思的是,当二进制码从0x00000000不断递增时,他表示的浮点数值也是逐渐递增的。对于非规约数到规约数来说表现为"逐渐溢出";对于规约数来说,小数部分没有全满的情况显然;而每当小数位全为1时,再下一个数应该是"逢二进一"(小数位清零,指数位加一),就好像小数位像指数位进位了一样(比如0(0..01)11..11对应浮点数的下一个数是0(0..10)00..00,而0(0..01)11..11对应整数的下一个数也是0(0..10)00..00)!根据这个特性,我们也可以对浮点数进行基数排序(先划分正负,同号的数将后31位任意切割为多个关键字后分别排序)。

无穷

为了表示状态"无穷",同样只能从指数上动手脚。我们把指数全为1的状态"挖掉",用于表示无穷等状态,如果一个数指数位全为1,小数位全为0,那么这个数就表示无穷。

显然无穷有两种,\(0(1..1)0..0_{bit}\)对应正无穷0x7f800000,\(1(1..1)0..0_{bit}\)对应负无穷0xff800000。无穷支持一些数学意义上的运算:

C++用1/0.0或者1e1000或者1e10000000赋值就可以得到一个无穷,他们都是一样的无穷,本质上是表示"超过存储范围"。可以输出无穷,表示为inf-inf

非数值

实数范围里,有一些计算是没有结果,无法进行的。在标准里同样规定了一类数,用于保存这类结果,他们叫做非数值(not a number)。非数值与无穷一样使用全为1的指数位表示,为了区分开来,小数位全为0时表示无穷,其他所有情况表示非数值情况。

显然很多状态都可以表示非数值,但是他们不被加以区分,也不分+NaN或者-NaN,同时也不能参与运算。

C++中用sqrt(-1)0.0/0.0或者inf-inf都将得到NaN,可以将其输出,表示为nan
image

浮点数的范围和精度

image

对于32位规约数来说,指数位包括\([-127,128]\),但是左右端点用来表示特殊数了,因此实际指数位\([-126,127]\)

首先是范围,这个很好计算。不妨只考虑正数,前面已经计算过最小的规约数为\(2^{-126}\),而最大的规约数应该是\(0(11111110)1..1_{bit}\approx 2\times 2^{127}=2^{128}\),因此极限范围就是\([2^{-126},2^{128})\),转化为十进制就约是\([1.175\times 10^{-38},3.403\times 10^{38}]\)。如果算上非规约数0x00000001,下界可以达到\(2^{-149} \approx 1.401\times 10^{-45}\)。

而关于精度也不难计算,精度即底数有效数字的位数,底数有23位,那么可以表示\(2^{23}\approx 10^{6.92}\)种有效数字,即两个形如\(1.xxxxx\)的23位数大致可以和十进制下的7位小数一一对应,7位以后不同的数字只能对应到同一个二进制数上。

浮点数是离散不均匀储存的

对于整数来说,32位二进制码与\([0,2^{32})\)的数一一对应,是多少就是多少。\([0,2^{32})\)里的全体整数可以看作对应关系的"值域"(一张数表)。如果赋值int a=3.4呢,值域里没有这个数!于是只能将它存为值域里最相邻的两个点之一(C++中浮点阶段为整数的规则是向0取整(做图时写错了应该是3),但是你也可以处理为向上取整,向下取整,或者四舍五入)。

image

而对于浮点数来说(仍以float为例),32位二进制码最多只能对应\(2^{32}\)个数,但是实数是无穷无尽的!因此,按照上面规则,除去无穷和非数值,每个状态计算出一个实数组成值域,float只能表示这些有限多的实数,对于不在"值域"内的数,只能选择将他存储为相邻的两个点之一(8388607.2在float范围里,但float的数表里没有这个数)。

image

显然,相邻两个数越近,误差越小,精度越高,小数部分越长,越能支持更大精度。如果只考虑同一个类型,float的精度是多少呢?

我们从1开始计算,1的表示为\(1*2^0\),1的下一个数是\((1+2^{-23})*2^0\),再下一个数是\((1+2^{-22})*2^0\),由于实际指数为0,因此小数位每移动1,值就移动\(2^{-23}\approx 1.19\times 10^{-7}\)。

但是在2048附近呢?2048的表示为\(1*2^{11}\),下一个数\((1+2^{-23})*2^{11}\),再下一个数是\((1+2^{-22})*2^{11}\),误差增大到了\(2^{-12}\approx 2.4\times 10^{-4}\)。

规律已经很显然了,和整数完全不同,浮点数的间隔是变化的,离0越远,间隔越大,并且每通过一个\(2^i\),指数位就增大1,间隔增大一倍。用刚刚有效数字来理解,有效数字只有大约7位,随着整数部分越来越大,小数部分的位数会越来越短,在上图,间隔已经达到0.5,只能储存整数和"整数.5"。在数据达到\(2^{23}=8388608\)以后,间隔达到1,小数部分消失,小数都会舍入到整数。数据达到\(2^{24}=16777216\)以后.间隔变为2,已经不能精确存储整数了。
image

image

这也可以说明为什么float的范围看起来如此夸张,因为这不算真的可用范围,只是表示无穷以下的最大值而已。这个数可以表示为\(2^{60}\),却不能表示\(2^{60}+1\),也不能表示\(2^{60}+1e9\),他的下一个数是\(2^{60}+2^{37}\),这是完全不可用的。

冷知识:《我的世界》中当水平坐标超过一千万时,图像扭曲,加载异常,默认情况不能出现的5格跳,以及最终出现的边境之地等都被认为是浮点误差太大引起的

image

进制影响真实的储存位数

为什么0.1+0.2=0.300000000004?,一位有效数字也不能精确储存了?这是因为0.1和0.2知识看上去的一位,实际上是无限小数。

我们知道任何\(\frac pq\)只有在分母的因子都能被进制整除才能写成有限小数。比如10的因子只有2和5。所以\(\frac p{2^n5^m}\)无论分母多大也能除得尽。但\(\frac 13\)一个简单的数却只能写成无限循环。在之前的例子里,二进制有限小数化为十进制当然有限(显然),但十进制有限在二进制下不一定有限,因为二进制无法把数五等分。

所以之前提到的精度只是约值,而且其意义应该是相邻两数的间隔值,即"存储值和实际值相减后大约第7位才有明显误差"。绝不是说"7位以下的数字能精确存储,7位以上的就截断到7位"。

其他类型的浮点数

以上说的都是32位,其实对于64位来说也是一样的,更进一步来说,现在的标准还指定各种位数浮点数的存储标准,一般来讲,位数越长,小数位越多,有效数字越多;同时指数位也越多,最大范围更大。虽然范围不一样,但他们的标准是一样的。

image

标签:表示,..,浮点数,IEEE754,NaN,表示法,无穷,精度
来源: https://www.cnblogs.com/ofnoname/p/15839927.html