ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

IEEE754浮点数表示法

2022-01-24 16:32:46  阅读:434  来源: 互联网

标签:表示 .. 浮点数 IEEE754 NaN 表示法 无穷 精度


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位组成,在图里从左到右排列。

  • 符号位:1位,0表示正数,1表示负数
  • 指数位:8/11位表示指数。可以表示256/2048种状态。
    然而指数是可正可负的。在标准里,我们没有选择用"补码规则"表示负数,而是选择直接向左平移(又叫阶码)。8位范围是\([0,255]\),我们将它向左平移一半(取127),就变成了\([-127,128]\),也就是说指数位减去127才是真实的指数(比如12(00001100)代表-125次方)。这里减去的数叫偏移量(biase),对单精度来说是127,对双精度来说是1023。
  • 小数位:23/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的数,这样的表示都是益于节省空间提高精度的。

写出一个数的浮点表示

  • 实战演练:将\(78.625\)转化为浮点数形式。
    \(78.625\)的二进制形式是\(1001110.101\),即\(1.001110101\times 2^6\),而指数位\(6+127=133=10000101_{bit}\),将底数的小数位后面补0到23位,得答案01000010100111010100000000000000

  • 这是一个在线网站,可以验证答案

  • 在C++中,浮点数是不能给二进制位赋值的。但是我们可以将32位整数赋值为对应的数,再用float指针来解析它,验证结果(后文也会写成16进制,节省空间)。

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++中,NaN与任何数的算数比较将返回false。即使是自身之间(实际上NaN==NaNNaN<NaNNaN>NaN均为假,只有NaN!=NaN为真)。NaN自身转化为bool值后为true

  • 任何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\)一个简单的数却只能写成无限循环。在之前的例子里,二进制有限小数化为十进制当然有限(显然),但十进制有限在二进制下不一定有限,因为二进制无法把数五等分。

  • 0.2,按照开篇的规则,应该对应\(0.0011\ 0011\ 0011..._{bit}\),在某一位后截断在存储,实际值不是0.2,是值域中最接近0.2的数。

  • 0.99993896484375,尽管远大于7位,但它可以在二进制下完全表示\(1-2^{-14}=0.11111111111111_{bit}\),因此完全储存在了float中, 体现出了超乎寻常的精度。

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

其他类型的浮点数

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

  • 半精度(Half)(16bit)
    image

  • 四精度(Quadruple)(128bit)
    image

  • 八精度(Half)(256bit)
    image

  • 延伸精度与上面的又不太一样。(就好像32位整数相乘时,要取到64位一样)延伸精度可以视为"精度运算的中间变量"。延伸双精度定为79位以上,便于执行比双精度更精确的计算。一些储存标准中为扩展精度提供了专门的最高位。按照维基百科最高位的存在使延伸精度可以表示更多"额外状态",比如运算中的精度损失。C++里long double可以实现延伸双精度,长度为80/96/128位。

image

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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有