V2.0
C++基础教程<br />——<br />C语言部分卷1C语言基础语法<br/>补充IEEE754标准<br/><br/>最新版本V2.0
<br>王道C++团队<br/>COPYRIGHT ⓒ 2021-2024. 王道版权所有前言进制十进制和二进制的移位十进制的科学计数法二进制的科学计数法IEEE754标准的浮点数定点数概念区别定点数如何表示数字?定点数表示纯整数和小数总结浮点数具体案例说明正数案例将十进制整数转换成浮点数将十进制小数转换成浮点数将浮点数恢复成十进制数负数案例将十进制负整数转换成浮点数总结浮点数表示步骤浮点数的精度和表示范围精度float的精度double的精度表示范围移码偏移值非规格化浮点数单精度浮点数的各种极值float的表示范围小结后记The End <br /><br / >如有发现错误,欢迎联系老师更新!
Gn!
在绝大多数编程语言当中,IEEE754标准的浮点数都是常见的数据类型,C语言也不例外。本文我们来详细分析一下浮点数。
Gn!
提到计算机当中数的表示,二进制是跑不掉的,现在做以下约定:
一个数的下标表示它的进制,无下标的数默认就是十进制,比如1、123等等都是十进制的数。
有下标的数,如102 表示二进制的10,即十进制的数字2。
一个数的上标和正常的数学公式一样,表示它的幂运算,比如102表示10的2次方,即100。
一般不会出现一个数既有上标也有下标,如有会括号说明它的含义。
除了以上设定外,本文中还会涉及进制转换的问题,如果不会请自行学习一下,或者直接使用Windows程序员计算器转换进制。
在Windows操作系统下,按:”Win + R“,输入”calc“,然后运行,即打开计算器功能:
(Mac也有类似的功能,不知道的百度即可调出来!)
Gn!
下面看一个推导过程:
12.34 = 1 × 101 + 2 × 100 + 3 × 10-1 + 4 × 10-2 = 12.34
一个十进制数可以做以上分解,那么对于一个二进制数,也可以做以下分解:
101.112 = 1 × 22 + 0 × 21 + 1 × 20 + 1 × 2-1 + 1 × 2-2 = 4 + 0 + 1 + 1/2 + 1/4 = 5.75
上式反过来:
5.75 = 5 + 3/4 = 4 + 1 + 1/2 + 1/4 = 1 × 22 + 0 × 21 + 1 × 20 + 1 × 2-1 + 1 × 2-2 = 101.112
通过上述式子,不难看出:
十进制的数字每左移一位,就表示将该数乘以10,每右移一位就表示该数除以10
二进制的数字每左移一位,就表示将该数乘以2,每右移一位就表示该数除以2
这两个结论虽然不那么起眼,但是对我们而言却是十分重要的,下面我们要用到。
Gn!
科学计数法是一种数字的表示方法,普通的数字用科学计数法表示并无多大优势,但是一个极大或者极小的数使用科学计数法来表示就会很有优势。
例如:
0.0000000000000000000000112332 = 1.12332 × 10-23
112332000000000000000000 = 1.12332 × 1023
等号右边就是科学计数法的表示形式,即 a * 10n,在计算机当中经常写为aen或者aEn,其中:
|a| >= 1 且 |a| < 10
n一定是整数
e或E表示乘以10的多少(n)次方
在计算机领域,如果在计算中需要存储极大或者极小的数时,使用科学计数法是比较有优势的:
长度显著变短(这很重要,意味着在计算机中可以节省内存空间)
可以直观的比较大小(数量级、精度等等一目了然)
当然十进制的科学计数法,大家早已经十分熟悉,我们今天就推导一下二进制的科学计数法。
Gn!
根据十进制的科学计数法推导,如果用科学计数法表示二进制,格式为:
a2 × 2n
其中:
基数从原先的10变为2
底数a,变成了一个二进制数
|a| >= 1 且 |a| < 2,也就说a应该是(1.xxx)2的二进制数
n是一个整数
例如:
5.75 = 101.112 = 1.01112 × 22 (右移几位,乘几个2)
0.1875 = 0.00112 = 1.12 × 2-3 (左移几位,除以几个2)
明白了二进制的科学计数法表示方式后,浮点数的IEEE754标准表示方式就基本可以理解了。
Gn!
IEEE754标准下表示的浮点数,可以简单的理解成二进制的科学计数法表示形式。IEEE754标准,规定了以下主要两种浮点数的表示形式:
单精度浮点数(内存空间占用32位)
双精度浮点数(内存空间占用64位)
下图就是IEEE754表示的浮点数的具体形式(精度不同体现在位数的差别)我们结合二进制的科学计数法表示范式,来说明一下浮点数的三个部分。
对于二进制的科学计数法表示:a2 × 2n
IEEE754标准浮点数可表示为:
符号位,计算机有符号数中,0表示正数,1表示负数。
阶码,规定为实际指数值(也就是上述式子中的n)加上一个偏移值常量,偏移值为2m-1 - 1,其中m为该阶码的位长。
阶码就是n + 2m-1 - 1 这个数的二进制表示。
单精度浮点数的阶码位长是8,即m = 8,偏移值为127
双精度浮点数的阶码位长是11,即m = 11,偏移值为1023
阶码,是通过某个二进制数的科学计数法的,指数位n计算出来的,所以阶码也常常被称为指数位(我也喜欢这么叫)
尾数,用于存储科学计数法中的有效数字(也就是上述式子中的a2),它是用原码表示的!
尾数常常被称之为小数位,因为它就是a2的二进制表示,如果长度不够后面补0。
很容易发现 a2 总是1.xxx,总是1.开头的。所以IEEE754标准当中,尾数总是默认隐含1.,计算的时候不要忘记这个隐含1.
Rd!
单精度和双精度浮点数,具有不同的表示方式,主要是位数的差异带来的不同:
单精度浮点数,长度为32位:
最高的一位是符号位
符号位后面8位是阶码(指数位),偏移值常量是127
最后的23位是尾数(小数位)
双精度浮点数,长度为64位:
最高一位是符号位
符号位后面11位是阶码(指数位),偏移值常量是1023
最后的52位是尾数(小数位)
Gn!
实际上当你了解了上述浮点数的表示形式后,那么就可以解释一个非常重要的问题了:浮点数为什么叫浮点数?既然有浮动的点数,那么有没有不动的点数?
当然有,那就是计算机当中的定点数。
Gn!
对于用a2 × 2n形式表示的二进制数:
a2决定了这个数的有效数字是什么,而它的大小(也就是小数点的位置)是由指数位n来决定的。
如果这个n是一个固定的数,也就意味着该数的小数点位置是不变的
这种表示方式的数字就被称之为"定点数"。
定点数的表示中,都会直接约定小数点的位置。
如果这个n不是固定的数,也就意味着该数的小数点位置是可变的
这种表示方式的数字就被称之为"浮点数"。
IEEE754标准下表示出来的数字的小数点位置显然是可变的,它是一种浮点数的表示方式。
Gn!
浮点数和定点数是计算机中数字的两种不同的存储表示方式:
定点数(Fixed-point Number)
浮点数(Floating-point Number)
下面来看一下定点数的表示数值的方式。
Gn!
定点数可以存储小数和整数,由于小数点位置固定不变,所以存储时小数点不进行存储,只需要约定位置即可。
定点数约定小数点位置,可以分为以下三种情况:
纯整数:例如整数100,小数点约定在最低位即可。
纯小数:例如:0.123,小数点约定在最高位即可。
整数 + 小数:例如1.23、12.3,需要指定小数点在某个位置
现在对定点数的表示做以下定义:
假设以机器字长 n 位表示定点数,从右至左,从高位到低位分别为 Xn、Xn-1...X3、X2、X1 其中 Xn是符号位,取值 0 和 1 分别表示正号和负号。
如此,对于任意一个定点数 X = XnXn-1…X2X1,在定点机器中可表示为:
如果 X 表示的是纯小数,那么小数点位于 Xn 与 Xn-1 之间。
如果 X 表示的是纯整数,那么小数点位于 X1 的右边。
如果X表示的是整数 + 小数,那么小数点可以约定在有效数字的任意一位。
最后还需要注意的是,计算机中存储整数都是以补码的形式存储的。
Gn!
纯整数:
对于纯整数100,由于小数点固定在最低位,假定我们以 1 个字节(8 bit)表示,用定点数表示如下:
100 = 0 11001002
-53 = 1 10010112(补码)
其中,最高位是符号位,小数点约定在最后一位,实际上计算机中的整数就是以定点数形式存储表示的
纯小数:
对于纯小数 0.125,由于小数点固定在最高位,同样以 1 个字节(8 bit)表示,用定点数表示小数0.125。结果如下:
0.125 = 0.0012 = 0 00100002
最高位的0表示小数是一个正数,后面的则是
001 0000
则是0.125的二进制小数表示。
整数 + 小数:
这种情况下,我们需要先约定小数点的位置
以 1 个字节(8 bit)为例,第一位为符号位,约定之后 5 位表示整数部分,最后 2 位表示小数部分
对于数字 1.5 用定点数表示就是这样:
1.5 = 0 00001 102
对于数字 26.5 用定点数表示就是这样:
26.5 = 0 11010 102
Gn!
以上就是用定点数表示一个小数的过程,其过程可以做以下总结:
在有限的 bit 位数限制下,先约定小数点的位置
整数部分和小数部分,分别转换为二进制表示
两部分二进制组合起来,即是结果
显然使用定点数去表示一个数是非常简单的,但是却并不是没有代价的,例如以下问题:
如上文所说,我们约定第一位为符号位,之后 5 位表示整数部分,最后 2 位表示小数部分后,此时
整数部分的二进制最大值只能是 11111,即十进制的31
小数部分的二进制最大只能表示 0.11,即十进制的 0.75
这样表示的数值很有限,而且因为位数限制,能够表示的小数是离散的,不是连续的!
于是可以做一下总结:
对于有限位数的定点数来说:
小数位越短,表示的数精度就越低,无法表示更多小数点后面的位数,比如2位小数显然表示不了X.00001
同时整数位就越大,表示的数值就越大。
小数位越长,表示的数精度就越大,就能够表示更多小数点后面的位数
同时整数位就越短,能够表示的数值就越小。
如果想表示数的范围更大或者精度越高,可以考虑:
扩大 bit 位数:例如使用 2 个字节、4 个字节,这样整数部分和小数部分宽度增加,表示范围和精度都提升了
改变小数点的位置:小数点向后移动,整个数字范围就会扩大,但是小数部分的精度就会越来越低
由此我们发现,不管如何约定小数点的位置,都会存在以下问题:
数值的表示范围有限(小数点越靠左,整个数值范围越小)
数值的精度范围有限(小数点越靠右,数值精度越低)
总的来说,如果使用定点数表示小数,不仅数值的范围表示有限,而且其精度也很低。
所以在计算机中,普遍使用定点数表示纯整数,不使用它表示小数,而使用浮点数表示小数。
Gn!
以下案例都以32位单精度浮点数为准(64位和32位只有位数的差距,可以套用)
怎么知道我们手动转换出来的浮点数是否正确呢?为了验证这一点这里提供两种方案:
最简单的,找一个在线转换浮点数和十进制的网站
也可以找一个IEEE754标准转换器(百度云自取)
链接:https://pan.baidu.com/s/1nj--X0OY8MopJtExK4v1Ow 提取码:b0tf
Gn!
正数是最常见的情况,比较容易理解。
Gn!
78 = 010011102 = 1.001112 × 26
分析:
这个数是正数,显然符号位应该是0
二进制科学计数法的指数是6,那么阶码应该是 6 + 127 = 133 = 1000 01012(8位)
尾数是最简单的,就是00111,但是单精度浮点数的尾数应该是23位,后面的位要用0补满
最终得到的32位单精度浮点数表示为(分段表示):
0 -- 1000 0101 -- 00111000000000000000000
Gn!
360.75 = 101101000.112 = 1.01101000112 × 28
分析:
这个数是正数,显然符号位就是0
二进制科学计数法的指数是8,那么阶码应该是 8 + 127 = 135 = 100001112(8位)
尾数就是 0110100011,不满32位,用0补满
最终得到的32位单精度浮点数表示为(分段表示):
0 -- 1000 0111 -- 01101000110000000000000
Gn!
将下列单精度浮点数表示,恢复成十进制数
0 -- 01111101 -- 10000000000000000000000
分析:
符号位是0,显然是一个正数
阶码是01111101 = 125 ,显然实际的指数是125 - 127 = -2
尾数实际上就是1,加上隐含的1,那么有效数字a就是1.12
因此,该浮点数的二进制科学计数法表示形式为:1.12 × 2-2 = 0.0112 = 0.375
Gn!
负数可能具有一定的理解难度,所以仅给出一个案例。
Gn!
-16 = -100002 = -1.0 × 24(原码表示)
分析:
是一个负数,显然符号位是1
阶码是 4 + 127 = 131 = 100000112
尾数是0,全部都是0
最终得到的32位单精度浮点数表示为(分段表示):
1 -- 1000 0011 -- 00000000000000000000000
Gn!
虽然我们不知道为什么这么做,但是现在我们已经可以对十进制转换成IEEE754浮点数的步骤做一个总结:
将十进制数值转换为二进制数值。
将二进制数值转换为它的科学记数法表示形式,注意直接使用原码形式(正负数都一样)
确定符号位:如果是正数,符号位为0,如果是负数,符号位为1
计算阶码:将科学计数法的指数值加上偏移值(单精度为127,双进度为1023),再把这个数转换成8位二进制
计算尾数:忽略有效数字的整数部分1(有效数字整数部分总是1,约定忽略掉),将有效数字的小数部分作为尾数,如果不足位数(单精度23位,双精度52位)则用0在右边补满
这样,前面5步,我们就得到了一个符合IEEE754标准的二进制浮点数表示。
Gn!
学到这里,实际上你还有很多问题没有解决,还有很多困惑不明白,但是我们先放下困惑,用现有的知识来解决两个最重要的疑惑:
浮点数的表示范围有多大?
浮点数的精度有限制吗?
为了搞清楚这两个问题,我们需要首先研究一下浮点数的精度问题。
Gn!
显然,浮点数肯定是有精度的,因为浮点数能够表示的有效数字(尾数)是有限的,超过的部分就无法表示
Gn!
尾数都是用原码直接表示的,隐含整数位1,不带符号,其中float的尾数是23位
于是:
float的尾数最大值是:1111111111111111111111112 (23个1),也即是0~223
即范围是 0~8388608(十进制数)
这个数是一个比较大的7位数,所以float的有效小数位是6~7位的,其中6位是绝对正确的,7位多数情况正确
而如果直接看有效数字(因为隐含1),则应该是7~8位,其中7位是绝对正确的,8位多数正确
注意以上说的精度都是针对十进制数来的,下面的double也是一样。
Gn!
比个葫芦画个葫芦,double和float差不多的,只不过double的尾数是52位的。
于是:
double的取值范围是0~252 ,即0~4503599627370500
这个数是一个16位数,则有效小数位是15~16,其中15绝对准确,16一般都准确。
如果加上隐含的1,则有效位数是16~17,其中16绝对准确,17一般准确。
Gn!
尾数位用于决定浮点数的精度,那么指数位(阶码)就用来决定浮点数的表示范围。要想学习这个东西,咱们要先看几个问题和相关概念。
Gn!
为什么阶码的计算要加上一个偏移量?
阶码在存储的时候要加上偏移量,恢复的时候还要再减回来,来回折腾有必要吗?答案是肯定的,凡事都有两面性,有利有弊,这么设计的好处是什么呢?
阶码,实际上是二进制科学计数法表示数字的指数,这个指数在正常情况下可正可负。这样的话,当我们比较两个浮点数的大小时,不仅要考虑数值位(尾数)本身的大小和符号,还需要考虑指数的符号然后才能比较大小,逻辑上显然是比较麻烦的。
说到这里,可能有些同学会觉得并没有什么麻烦,-123和124我一眼就能看出谁大谁小,但是不要忘了我们说的比较是计算机比较大小,而不是人类。所以为了更好的比较阶码大小,最理想的情况下,我不希望有负号。
在深入探究这个问题之前,我们可以再探讨一个问题:计算机中存储负数为什么要使用补码而不是原码?
存当然是可以存的,但是如果直接存储负数原码会给计算带来困难。计算机为了设计简单,对于数值的加减运算都是带符号位的,也即减去一个正数相当于加上一个负数如果使用原码直接进行运算,得到的结果可能是不正确的,例如:
1 - 2 = 0000 00012 + 1000 00102 = 1000 00112(原) = -3
以上运算结果显然是荒谬的,但是如果使用补码存储负数,就不会出现这个问题,例如:
1 - 2 = 0000 00012 + 1111 11102 = 1111 11112(补) = 1000 00012(原) = -1
综上,使用补码存储负数会让人更难以去计算思考,但是:
对计算机而言,使用补码,能够仅使用加法指令就实现数值的加减运算
就可以简化CPU的指令集,简化CPU设计,提升运算的效率
以上两点,充分说明了我们学习计算机知识要站在计算机的角度,而不是人,这在很多场景中都是适用的。
再回到,我们阶码的问题上。 想一下:如果指数并不是直接存储而是加上一个偏移值,保证它一定是正数是不是就更有利于计算机比较它们的大小了呢?答案是肯定的,指数加上一个偏移值之后,无论指数部分是正是负,都可以转换成非负数。 这样的,将真值映射到正数域的数值(真值在数轴上正向平移一个偏移量),称为移码。
使用移码来比较两个真值的大小,只要高位对齐后逐位比较即可,不用考虑符号位问题。
Gn!
在明白为什么要有偏移值后,我们还有一个问题:为什么偏移值是2m-1 - 1而不是2m-1又或者别的值呢 ?
8位二进制有符号数的取值范围是[-128,127],也是32位浮点数指数可能的取值范围
8位二进制无符号数的取值范围是[0,255],也是32位浮点数阶码可能的取值范围
要使指数有符号数[-128,127],变为阶码的无符号非负数[0,255]需要加上偏移量128,为什么实际却加127呢?
Gn!
为了解释这一问题,引用一段教材的原文:
"当阶码E为全0且尾数M也为全0时,表示的真值X为零,结合符号位S为0或1,有正零和负零之分。当阶码E全1且尾数M为全0时,表示的真值X为无穷大,结合符号位S为0或1,也有+∞和-∞之分。
这样在32位浮点数表示中,要除去E用全0和全1(255)表示零和无穷大的特殊情况,指数的偏移值不选128(10000000),而选127(01111111)。对于规格化浮点数,E的范围变为1到254,真正的指数值e则为-126到+127。因此32位浮点数表示的绝对值的范围是10-38~1038(以10的幂表示)。" ——引自白中英《计算机组成原理》
Gn!
当然以上一段话,你可以完全不理解。这里要先引入一个不是很新的概念:规格化浮点数。
我们上面提到的浮点数表示方式,实际上指的都是规格化浮点数。而浮点数一共有5种表示形式,如下表所示:
对于以下浮点数表示形式:
注:NaN(Not a Number,非数)是计算机科学中数值数据类型的一类值,表示未定义或不可表示的值。常在浮点数运算中使用,首次引入NaN的是1985年的IEEE 754浮点数标准。
IEEE754浮点数的五种表示方式
浮点数形式 阶码 尾数 描述(m是阶码的位长数,float时m=8) 零 0 0 阶码是0,尾数表示的小数部分也是0,那么这个数是 ±0(正负取决于符号位) 非规格化 0 非0 阶码是0,尾数表示的小数部分非0时,此时有效数字的整数部分为固定值0,这就是浮点数的非规格化表示形式,它用于表示一个非常小非常接近0的数(指数偏移值为2m - 2) 规格化 [1,2m - 2] 任意 阶码不包括全0和全1,尾数表示的小数部分为任意数值,此时有效数字的整数部分为固定值1,这就是浮点数的规格化表示形式,它是浮点数的主要表示形式,日常使用的绝大多数数值都可以用它来表示(指数偏移值为2m - 1) 无穷 2m - 1 0 阶码是2m - 1(全1),尾数表示的小数部分为0,那么这个数是 ±∞(正负取决于符号位) NaN 2m - 1 非0 阶码是2m - 1(全1),尾数表示的小数部分为非0,那么这个数是NaN(非数) 当你了解上述表格知识后,我们就可以来掰扯掰扯为什么32位规格化浮点数阶码的偏移值是127而不是别的了。
由于没有找到明确的官方解释,这里提供几个猜想的原因:
由于阶码的范围从[0,255]变为了[1,254],如果偏移值为128的话,那么实际指数范围就是[-127,126],这样不是不可以。但是而如果偏移值改为127的话,该浮点数就能表示更大的范围。
阶码是127所表示的浮点数取值范围更加对称,比128更合适。
以上两点可以查验表格数据去验证。
Gn!
当然,以上解释可能都不太完美,也许这只是设计者的一个随手之举。总之无论如何请记住,以下规定(m是浮点数阶码位长):
float情况下,m = 8
规格化浮点数阶码的取值范围是[1,2m - 2],对于单精度浮点数阶码的取值范围是[1,254]
偏移值是2m-1 - 1,对于单精度浮点数来说,这个偏移值是127
单精度浮点数float的实际指数范围是[-126,127]
Gn!
既然提到了规格化浮点数,那这里我们也简单了解一下非规格化浮点数:
一般是某个数字相当接近零时,才需要使用非规格化形式来表示,这时它的阶码是0,尾数为非0
IEEE754标准规定:
非规格化浮点数的指数偏移值,比规格化浮点数的小1,也就是偏移值为2m - 2
拿单精度浮点数为例,偏移值为126,由于阶码固定为0,所以实际指数固定为-126
非规格化浮点数解决填补了绝对值意义下最小规格数与零的距离,避免了突然式下溢出(abrupt underflow)
下溢出:指当要表示的数据的绝对值小于计算机所能表示的最小绝对值时,出现无法表示数值的情况
关于突然式下溢出,我们在后面还会论述。
实际上,当你学习到这里,对于浮点数的表示范围已经有了认识,接下来我们就以单精度为例,抛砖引玉。
Gn!
表格说明:
表格上半部分为负数,下半部分为正数,以0为间隔
表格上方这个数值越小,表格下方这个数值越大(不是绝对值,NaN除外)
为了描述方便,最值指的都是绝对值大小而不是数值本身大小
该表格完全根据上面浮点数表示形式的表格推导而来
单精度浮点数的位数是8位
单精度浮点数的各种极值
浮点数表示形式 符号位 实际指数(阶码) 有效数字 (含隐藏位) 表示数值 负无穷 1 128(255) 小数位是0 -∞ 规格化最大值 1 127(254) 2 - 2-23 -(2-2-23) × 2127 = -3.4E+38 规格化最小值 1 -126(1) 1.0 -2-126 = -1.18E-38 (-1.17549435E-38) 非规格化最大值 1 -126(0) 1 - 2-23 -(1-2-23) × 2-126 = -1.18E-38 (-1.17549421E-38) 非规格化最小值 1 -126(0) 2-23 -2-23 × 2-126 = -1.4E-45 负零 1 -127(0) 0 -0.0 正零 0 -127(0) 0 +0.0 非规格化最小值 0 -126(0) 2-23 2-23 × 2-126 = 1.4E-45 非规格化最大值 0 -126(0) 1 - 2-23 (1-2-23) × 2-126 = 1.18E-38 (1.17549421E-38) 规格化最小值 0 -126(1) 1.0 2-126= 1.18E-38 (1.17549435E-38) 规格化最大值 0 127(254) 2 - 2-23 (2-2-23) × 2127 = 3.4E+38 正无穷 0 128(255) 小数位是0 +∞ NaN 0或1 128(255) 小数位非0 非数 注:
非规格化最大值和规格化最小值非常接近,但是它们并不是一个值
E表示十进制科学计数法,即乘以十的多少次方
双精度浮点数和单精度浮点数表示方式是一致的,可以类推一下,这里不再给出
最后可能有些同学还会有疑惑:有效数字当中的2-23是怎么来的呢?
举个例子:
规格化最大值的有效数字是:1.111...12(小数点后23个1)
这个数可以转换成:22 - 0.000...12(小数点后22个0)
稍做转换:22 - 12 × 2-23 (转换成了科学计数法)
整体转换成十进制:2 - 1 × 2-23
规格化浮点数最大值的有效数字为:2 - 2-23
Gn!
当你看到这里,应当已经明白:浮点数的取值范围已经不能够简单的,去使用一个区间描述了,以float为例子:
float的规格化形式包含两个区间:[-3.4E+38,-1.18E-38] 和 [1.18E-38,3.4E+38]
规格化形式,无法表示相当接近0的两个区间[0,±1.18E-38)
float的非规格化形式也包含两个区间:[-1.18E-38,-1.4E-45] 和 [1.4E-45,1.18E-38]
非规格化形式用于表示相当接近0的数值,但是它仍然有不能表示的区间[0,±1.4E-45)
我们日常使用float几乎只会使用规格化浮点数,所以很多书籍里也会直接指出float的表示范围为:[-3.4E+38,3.4E+38]
现在你已经知道上述说法是具有一定的瑕疵的,但是它在多数情况下是正确的。
当然,double和float的表示形式是一样的,无非位数不同,可以类推一下,这里不再赘述。
Gn!
以上,我们了解了IEEE754标准下浮点数的表示范围和精度两大问题,这里做一些总结和额外的说明
浮点数集合当中的所有元素都是有序的(NaN除外)
浮点数的零有正负之分,但两者是相等的。但是在实操上有些许差距,比如
1.0 ÷ (+0.0) = +∞
1.0 ÷ (-0.0) = -∞
我们常使用的规格化浮点数不能表示一个很小的,接近0的数,这会产生一些问题
在使用规格化浮点数的情况下,正数当中的第一小,第二小,第三小的数字分别是:
2-126
( 1 + 2-23 ) × 2-126
( 1 + 2 × 2-23 ) × 2-126
这些数字显然不是连续的,而是间隔2-126 × 2-23 = 2-149,从第一个开始,以后每一个都是
但是你会发现规格化浮点数的最小值和0的差距不是2-149,而是2-126,这个数要大223倍
这样浮点数的区间就不是连续的了:
规格化表示时,正数往0靠拢,逐个减小2-149,但是到了最小值后,突然减小了2-126变成了0
以上情况,就是我们上面提到的突然式下溢出(abrupt underflow)
为了解决突然式下溢出的问题,IEEE采用了Intel公司力荐的渐进式下溢出(gradual underflow)
实际上就是在规格化的最小值和0之间,加入了浮点数的非规格化表示
在使用非规格化浮点数的情况下,正数当中的第一小,第二小,第三小的数字分别是:
2-23 × 2-126 = 2-149
(2 × 2-23) × 2-126
(3 × 2-23) × 2-126
这样设计后:
0到非规格化浮点数最小值的间隔就变成了2-149,并且非规格浮点数逐个之间的间隔也是2-149
而非规格浮点数的最大值为(1-2-23) × 2-126,这个数和规格化浮点数的最小值2-126之间的间隔也是2-149
我们在这里反复提到了浮点数的间隔问题,它的实质就是:浮点数在数轴上是规格分布的吗?
如果你已经掌握了本节上述的所有内容,那么这个问题不难回答:
非规格化浮点数只能表示接近0的特别小的数,在[-1.18E-38,1.18E-38 ]这个区间内数值是连续的
间隔为2-149
非规格浮点数的阶码是相同的,都是0
规格化浮点数在阶码相同的一组数区间内是连续的
但是不同阶码的浮点数之间显然是不连续,而且随着阶码越来越大,数值越来越远离原点(0)
每个阶码区间内连续的间隔越来越小,精度越来越低
xxxxxxxxxx
21- 阶码为1,数的间隔为2<sup>-149</sup>,精度也是
2- 阶码为2,数的间隔就变成了2<sup>-148</sup>,精度也降低了
而不同阶码区间的最大值和最小值的间隔以阶码小的为准
xxxxxxxxxx
11- 阶码为1的最大值和阶码为2的最小值,间隔2<sup>-149</sup>
显然非规格化数是均匀分布的,精度为2-149,且自然过渡到规格化数,
规格化数不是均匀分布的,规格化数的阶码每加一,精度就减半,但是阶码越大表示的数(绝对值)会更大
无穷大在两头,0与无穷大之间大部分是规格化数,只有非常接近0的少部分是非规格化数。
而且,越是靠近0的地方,数与数之间的间隔越小,到了非规格化数时,间隔是2-149。
相反,越是往无穷大的地方,数与数之间的间隔越大。
当越过非规格式化最小值时,下溢到0。当越过规格式化最大值时,上溢到无穷大。
Gn!
这篇文章就进入尾声了,我们再回头看一个问题:为什么偏移值是2m-1 - 1而不是2m-1又或者别的值呢 ?
无需长篇大论,思考一下为什么规格化浮点数和非规格化数之间能够平滑过渡?
这实际上和偏移值设置为127也是有关系的,127的偏移值保证了规格化最小值和非规格化最大值的平滑过渡。
Gn!
坦白来说,这篇文章写到这里已经非常长了,作为IEEE754的入门学习文章它甚至有点过长了。如果你能够学到这里,不得不说你非常有毅力,非常有钻研精神,很不错。
看完IEEE754标准的设计规范,我相信你也会感概它的设计是这么优美,这么巧妙。关于它的一些其它问题,就不在文章中继续啰嗦了,如果感兴趣可以自行学习查找。
这篇文章是花费业余时间书写的,甚至不是连续时间写完的,相信出现错误也完全是可能的,欢迎指正!