- 我又来复习了
- 上篇二进制那么多阅读量,,,小心脏怦怦跳,,,
- 后来发现我最后的复习计划也被列到目录中了,,,大概是这个原因吧
浮点数与科学记数法
- 之前讲过的定点整数,只是对整数的一种存储,诸如3.14这种只能存成314,之后那就要想办法存下“小数点在最高位3和次高位1之间”这个信息
- 放心吧,计算机怎么也不会真正的保存小数点
- 科学记数法就发挥了很大的作用
- 十进制下科学记数法,就是将一个数表示成
- number=a?10n
- 这种形式,并且对a做限定
- 1<=|a|<10
- 比如
- 3.14=3.14?100
314=3.14?102
- 如此一来,相当于默认了小数点位于a的最高位和次高位之间,那么只要存下了a*10^n也就不需要存小数点了
- 问题?为什么要乘10?
- 比如一个数字3.14,
3.14?10=31.431.4?10=314
314?10?1=31.4
- 看看看,小数点在运动哦~就酱
- 二进制下有点不同
- number=b?2n
0<=|b|<1
- 比如(除了2之外都是用二进制表示)
10.01=0.1001?22 - 还可以乘别的数来表示,比如
10.01=0.1001?41只不过4的指数每变化1小数点就要移动2格
- 这种对b的规范,默认了小数点的位置
- 进一步,如果默认了乘的数就是2的话,那么需要存储的只有小数b和指数n了,我们叫这个乘数2为基值
浮点数的格式
- 对于一个有小数部分的二进制数number,我们写出规范化的科学记数法的形式
number=b?2n0<=|b|<1
- 那么number就可以用b和n两个数来存储,同时默认了两个条件
- n是需要乘的2的指数
- b在0到1之间,也就是说b只能是0.XXX的形式
- 对于n来说,n就是一个整数
- b是0.XXX的形式,那么我们就只需要存储“0.XXX”的“XXX”部分就可以了,这也是一个整数的形式
- 起个规范化的名字,我们称 n 为阶码,称 b 为尾数
- 看图
- 考虑到正负的问题,分别留出一位Es和Ms作为符号位,同样是0代表正数,1代表负数
规范化格式
- 问题又来了?依旧用10.01来举例
10.01=0.1001?2210.01=0.01001?23
- 也就是说,尽管尾数已经在0到1之间了,我依旧可以移动小数点,再顺势改变一下阶码的值,同一个数存起来就有多种存法
- 于是对于计算机内存储的时候,还需要进一步规格化
- 很简单,你想移动小数点,规格化了之后就让你无法移动小数点
- 即规定:M1必须为1
- 那么10.01就只能存成
0.1001?22的形式了
浮点数表示的问题
- 但是,这种规格化也带来了另一个问题,那就是在实现规格化的过程中,我们总是想让M1为1,就需要移动小数点,也就需要改变阶码的值
- 问题是,你移动了多少多少位来使得M1为1,但是要改变的阶码的值却超出了阶码的表示范围!!就是说阶码的表示范围是有限的!!
- 这个问题就是浮点数不得不直接面对的问题了,另外尾数的位数限制也是浮点数表示法所固有的精度问题
- 举例,取E1-E3共3位来记录阶值,Es一位记录阶符,M1-M3共3位来记录尾数的绝对值,Ms一位来记录尾符
- 最大值:M1-M3均为1,Ms为0表示正,E1-E3均为1,Es为0表示正,即
MaxValue=0.111?27=1110000 - 最小值,即最大值的Ms取1表示负,即
MinValue=?0.111?27=?1110000 - 规范化表示下最接近零的正数M1取1,M2取0,M3取0,Ms取0,Es取1,E1E2E3均为1,即
ClosestToZero=0.100?2?7=0.00000001 - 规范化表示下最接近零的负数自然就是
ClosestToZero=?0.100?2?7=?0.00000001
- 最大值:M1-M3均为1,Ms为0表示正,E1-E3均为1,Es为0表示正,即
- 问题就是:
- 超过了最大值最小值,无法表示,称为上溢。这个可以理解,毕竟就算整数也有上限下限。一般运算超过了上限下限会出错,而不像整型那样截断。
- 比较独特的地方在于,比“最接近零的数”更接近零的数无法表示。原因在于阶码范围的限制。在这个靠近零的狭小范围内,用浮点数来表示都是0,也就是机器中为全0来记录,尽管真实的数值可能很接近零但不是零,然而计算机表示的能力有限,太小的数无法表示,成为“机器零”,称为下溢。
- 最后就是精度的限制与损失。精度限制的原因是尾数的位数有限,而精度的损失一方面来源于“规格化”时小数点的移动,这使得某些数位可能移到了尾数表示范围之外,如
101.01=0.10101?23=0.101?23后两位01没有了,因为尾数总够3位只能保存前面的3位;另一方面来源于十进制转换成二进制所固有的限制,比如
(0.25)10=(0.01)2这样是没有损失的,但
(0.2)10=(0.0011001...)2你会发现十进制的 0.2 根本无法用二进制来完美的表示。其实固然如此,就好比十进制无法完美表示三进制的0.1一样。
(0.1)3=1/3=(0.3333333...)10
IEEE标准
-
通常
- float为 4 Byte = 32 bit , 即float中Es,Ms各占一位外,E1-Em和M1-Mn共占 32 - 2 = 30 位。
- double为 8 Byte = 64 bit ,即double中Es,Ms各占一位外,E1-Em和M1-Mn共占 64 - 2 = 62位。
- 那么如何分配 n 和 m 的值,这就需要一个统一的标准
- 美国电气与电子工程师协会IEEE(Institute of Electrical and Electronic Engineers)为了便于软件的移植,为采用软件对浮点数运算时发生特殊情况进行处理提供支持,并鼓励开发出面向数值计算的优秀程序,于1985年推出了“浮点数表示及运算标准”,即IEEE标准754。
- 其设计者Kahan因此荣获了1989年的图灵奖。
- IEEE标准如下
- 最高位为尾数符号位S
- 次高位段为 用移码表示的 阶码E
- 低位字段为 尾数F
- 基值为2
- 基本单精度格式:E占8位,F占23位,共32位
- 基本双精度格式:E占11位,F占52位,共64位
- 扩充单精度格式:E>=11位,F>=31位
- 扩充双精度格式:E>=15位,F>=63位
- 还有一些很奇怪的地方,我尽可能按我的理解解释一下
- 阶码采用移码表示,为的是便于比较。因为当浮点数进行加减的时候,必须在统一了阶码的前提下才可以对尾数进行加减。
- 单精度格式阶码E为例,其范围是1~254,也就是
单精度阶码 E : 00000001 ~ 11111110偏移值是127,也就是说实际的阶码值是 1-127 ~ 254-127 即 -126~+127之间,也就是
单精度阶码 E : 10000010 ~ 01111111此时的表示方式就变成了补码
- 上一条中偏移值是127可能需要再解释一下。普通的移码的定义是,为了使得最高位为符号位下编码的二进制正负直接可以直接比较,我们把最高位取反来得到移码,而最高位取反的操作,以4 bit的“0000”为例,也就相当于加上了“1000” 即 2^3=8,对应上条来说,偏移值本应该是128。但别忘了,移码的最终目的是为了使二进制编码的数字可以在正负之间直接比较,达成这个目的同样可以用加上2^3-1 = 7即“0111”来达到,对应可解释上条中的偏移值为什么是127也同样可以了。
- 阶码E中上有 全0 和 全1 没有编入,这个有特殊的用途,见下面的表格
- 还记得规格化的要求吗,即M1=1,既然强制了M1=1,那么就不需要存储了嘛,所以IEEE标准754引入了“隐藏位”技术,即不存储规格化要求了的M1=1,这样就使得尾数多出一位来,精度也就提升了一位。
-
IEEE的五种实体
实体 | 阶码E | 尾数F |
---|---|---|
零0 | 全0 | 全0 |
非规格化数 | 全0 | 非全0 |
规格化数 | [1,254] | F |
无穷大 | 全1 | 全0 |
非数NaN | 全1 | 非全0 |
- 还记得没编入阶码的 全0 和 全1 吗,这两种状态分别与尾数的全0和非全0组合成了四种特殊情况,分别表示了除了规格化数之外的四种实体,这也使得IEEE在处理浮点数操作的时候变更为灵活
- 非数是Kahan的一个创新,其目的是当浮点运算发生一些特殊情况的时候,软件可以根据NaN的内容进行相应的处理,从而减少了特殊情况下软件处理的工作量
- 非数分为“发信号的非数”Signaling NaN 和“静默的非数”Quiet NaN两类
- “发信号的非数”用于在出现无效运算时发出异常信号
- “静默的非数”只记录发生的特殊请况,而不发出异常信号,比如
(+∞)±(?∞)0?∞
0/0
负数????√
- 非数的有效部分是尾数,用于区分“发信号”还是“静默”,并可以指明是那种异常情况产生了这个非数,从而可以根据非数的内容进行处理。对于不同异常情况对应的编码标准没有规定,不同的实现方案可以有不同的尾数编码方案。
- 非规格化数也是一种特殊的处理方案,目的是为了避免出现下溢,从而允许用非规格化的形式来表示很小的数,而此时隐藏位就不在是 1 而是 0 了,因为已经不是规格化的范围。这种方案也叫“逐渐下溢(Gradual Underflow)”
时间: 2024-10-12 16:12:28