小朋友学C语言(43):浮点数的深入分析

生活        2019-07-11   来源:qy雪路浪游

IEEE二进制浮点数算术标准(IEEE 754)是20世纪80年代以来最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用。这个标准定义了表示浮点数的格式(包括负零-0)与反常值(denormal number)),一些特殊数值(无穷∞与非数值NaN),以及这些数值的“浮点数运算符”。 IEEE 754规定了四种表示浮点数值的方式:单精确度(32位)、双精确度(64位)、延伸单精确度(43比特以上,很少使用)与延伸双精确度(79比特以上,通常以80位实现)。只有32位模式有强制要求,其他都是选择性的。大部分编程语言都有提供IEEE浮点数格式与算术,但有些将其列为非必需的。例如,IEEE 754问世之前就有的C语言,现在有包括IEEE算术,但不算作强制要求 C语言的float通常是指IEEE单精确度,而double是指双精确度。

一、浮点数的表示方法

一个浮点数 (Value) 的表示其实可以这样表示: Value = sign * exponent * fraction。 也就是浮点数的实际值,等于符号位(sign bit)乘以指数偏移值(exponent bias)再乘以分数值(fraction)。分数值也称尾数值。

二、浮点数在内存中的存储方式

方式位数标准模式
float32bitIEEE R32.24第31位(最高位)存符号,第23-30位存指数部分,共8位,第0-22位位存尾数部分,共23位
double64bitIEEE R64.53第63位(最高位)存符号,第52-62位位存指数部分,共11位,第0-51位存尾数部分,共52位

三、指数偏差

指数偏差(表示法中的指数为实际指数减掉某个值)为 ,其中的e为存储指数的比特的长度。减掉一个值因为指数必须是有号数才能表达很大或很小的数值,但是有号数通常的表示法——补码(two's complement),将会使比较变得困难。这是因为补码的大小很难直接看出来。 为了解决这个问题,指数在存储之前需要做偏差修正,将它的值调整到一个无符号数的范围内以便进行比较。此外,指数采用这种方法表示的优点还在于使得浮点数的正规形式和非正规形式之间有了一个平滑的转变。

四、指数偏移值

指数偏移值(exponent bias),是指浮点数表示法中的指数域的编码值为指数的实际值加上某个固定的值,IEEE 754标准规定该固定值为2e-1 - 1,其中的e为存储指数的比特的长度。 以单精度浮点数为例,它的指数域是8个比特,固定偏移值是28-1 – 1 = 127。此为有号数的表示方式,单精度浮点数的指数部分实际取值是从-128到127。例如指数实际值为1710,在单精度浮点数中的指数域编码值为1710 + 12710 = 14410采用指数的实际值加上固定的偏移值的办法表示浮点数的指数,好处是可以用长度为e个比特的无符号整数来表示所有的指数取值,这使得两个浮点数的指数大小的比较更为容易,实际上可以按照字典序比较两个浮点表示的大小。 这种移码表示的指数部分,中文称作阶码。

下面举一些例子来加深对移码的理解:

例1

如果我们要表示 0,则有0 + 127 = 127,用二进制表示即为 0000 0000 + 0111 1111= 0111 1111

例2

如果我们要表示1,则有1 + 127 = 128,用二进制表示即为 0000 0001 + 0111 1111 = 1000 0000

例3

如果我们要表示2,则有2 + 127 = 129,用二进制表示即为 0000 0010 + 0111 1111 = 1000 0001

例4

如果我们要表示128,则有128 + 127 = 255,用二进制表示即为 1000 0000 + 0111 1111 = 1111 1111 这个128是我们能够使用 8位二进制移位存储算法表示的最大的正数了,再大就溢出了。

我们再来看看负数:

例5

如果我们要表示-1,则有(-1)+ 127 = 127 – 1 = 126,用二进制表示即为 0111 1111 – 0000 0001 = 0111 1111

例6

如果我们要表示-2,则有(-2)+ 127 = 127 – 2 = 125,用二进制表示即为 0111 1111 – 0000 0010 = 0111 1101

例7

如果我们要表示-127,则有(-127)+ 127 = 127 – 127 = 0,用二进制表示即为 0111 1111 – 0111 1111 = 0000 0000 这个 -127是我们能够使用 8位二进制采用移位存储所能表示的最小的负数了,再小就会溢出。

由上面的分析我们可以得出规律,采用移位存储技术,我们可以使用 8位二进制来表示从 -127~128 共计:27个负数+零(0)+ 128个正数=256个数

例8:求十进制数8.25在内存中的储存方式

分析: 8.25用二进制形式表示为1000.01,表示成二进制的指数形式为1.00001 * 23,用科学计数法则表示为1.00001 * 2E3。 因为是正数,符号位即最高位为0; 指数位为3 + 127(移位存储) = 130,二进制形式是10000010; 尾数部分00001 = 0000100 00000000 00000000(23位)。 所以8.25在内存中储存为:0 10000010 00001000000000000000000

例9:二进制1 10000010 00001000000000000000000是一个单精度浮点数,对应的十进制数是多少?

分析: 最高位为1,表示负数; 指数位为10000010(2) = 130(10),130 – 127 = 3; 尾数为00001000000000000000000,换成十进制为1 + 1/32。注意这里的1不要忘了加。 所以表示的数为-(1 + 1/32) * 23 = -8.25

例10:求十进制数-0.125在内存中的存储方式

分析: -0.125用二进制表示为:-0.001,表示成二进制的指数形式为-1.0 * 2-3,用科学计数法则表示为-1.0 * 2E-3。 因为是负数,符号位即最高位为1; 指数位为-3 + 127 = 124,二进制形式是01111100(8位); 尾数部分为0000000 00000000 00000000(23位)。 所以-0.125在内存中储存为1 01111100 00000000000000000000000。

例11:二进制0 01111100 00000000000000000000000表示一个单精度浮点数,求对应的十进制数

符号位为0,表示这是一个正数; 011111002= 12410,十进制为124 – 127 = -3; 尾数00000000000000000000000表示1,注意这里的1不要忘了加。 所以十进制为1.0 * 2-3 = 0.125

例12:求十进制数120.5在内存中的存储方式

分析: 120.5用二进制形式表示为:1111000.1,表示成二进制的指数形式为1.1110001 * 26,用科学计数法则表示为1.1110001 * 2E6。 因为是正数,符号位即最高位为0; 指数位为6 + 127(移位存储) = 133,二进制形式是10000101; 尾数部分1110001 = 1110001 00000000 00000000(23位)。 所以120.25在内存中储存为:0 10000101 11100010000000000000000

例13:已知二进制01000001001000100000000000000000表示的是一个单精度浮点数,求它的十进制表示形式

分析: 最高位为0,所以这是个正数; 指数位为10000010,即十进制为130。所以指数为130 – 127 = 3。 尾数位为01000100000000000000000,换算成十进制为1 + 1/4 + 1/64。注意这里的1不要忘了加。 所以对应的十制数为23 * (1 + 1/4 + 1/64) = 8 + 2 + 0.125 = 10.125

五、规约形式的浮点数

如果浮点数中指数部分的编码值在1 <= exponent <= 2e - 2之间,且在科学表示法的表示方式下,尾数部分最高有效位(即整数字)是1,那么这个浮点数将被称为规约形式的浮点数。“规约”是指用唯一确定的浮点形式去表示一个值。 单精度(32-bit)的规约形式浮点数在指数偏移值的值域为00000001到11111110,尾数部分则是000……000(共23个0)到111……111(共23个1)。 双精度 (64-bit)的规约形式浮点数在指数偏移值的值域为00000000001到11111111110,尾数部分则是000……000(共52个0)到111……111(共52个1)。

例14:求规约数0 00000001 00000000000000000000000所表示的十进制数

分析: 第一个0表示正数; 指数位为1 – 127 = -126; 尾数位为1(这个1是隐藏的,别忘了加上)。 所以对应的十进制为2-126 ,这是规约数所能表示的最小的正数。 同理规约数1 00000001 00000000000000000000000表示的是最大的负数为-2-126

例15:求规约数0 00000001 11111111111111111111111所表示的十进制数

分析: 尾数部分为(1 + 1/2 + 1/4 + … + 1/23),这个数略小于2但很接近于2。 所以对应的十进制接近但略小于2 * 2-126

六、非规约形式的浮点数

如果浮点数的指数部分的编码值是0,分数部分非零,那么这个浮点数将被称为非规约形式的浮点数。一般是某个数字相当接近零时才会使用非规约型式来表示。IEEE754标准规定:非规约形式的浮点数的指数偏移值比规约形式的浮点数的指数偏移值小1。 例如,最小的规约形式的单精度浮点数的指数部分编码值为1,指数的实际值为-126;而非规约的单精度浮点数的指数域编码值为0,对应的指数实际值也是-126而不是-127。 实际上非规约形式的浮点数仍然是有效可以使用的,只是它们的绝对值已经小于所有的规约浮点数的绝对值;即所有的非规约浮点数比规约浮点数更接近0。规约浮点数的尾数大于等于110且小于210,而非规约浮点数的尾数大于010且小于110。

例16:求非规约数0 00000000 00000000000000000000001所表示的十进制

分析: 因为是非规约数,所以指数位是-126,而不是0 – 127 = -127; 非规约数的尾数部分没有隐含的1,所以尾数部分为2-23; 所以对应的十进制为2-23 * 2-126 = 2-149,这是非规约数所能表示的最小的正数。 同理非规约数所能表示的最大负数为0 00000000 00000000000000000000001= -2-149

例17:求非规约数0 00000000 11111111111111111111111所表示的十进制

分析: 因为是非规约数,所以指数位是-126,而不是0 – 127 = -127; 非规约数的尾数部分没有隐含的1,所以尾数部分为1/2 + 1/4 + … + 1/23 ≈ 1(但略小于1); 所以对应的十进制数略小于2-126。 这也是最大的正非规约数,接近但略小于最小的规约数2-126。 同理最大的负非规约数,接近但略大于最大的规约数-2-126

七、浮点数的三个特殊值

这里有三个非规约浮点数的特殊值必须指出(以单精度为例):

例18:如果指数是0并且尾数的小数部分是0,则这个数是±0

  1. 0 00000000 00000000000000000000000表示0

  2. 1 00000000 00000000000000000000000表示-0

例19:如果指数为2e – 1并且尾数的小数部分是0,这个数是±∞

对于float类型,因为指数位有8位,则e = 8,

  1. 2<sup>e</sup> – 1 = 2<sup>8</sup> 1 = 255 = 11111111<sub>2</sup>,则有

  2. 0 11111111 00000000000000000000000 =

  3. 0 11111111 00000000000000000000000 = -∞

对于double类型,因为指数位有11位,则e = 11, 2e – 1 = 211 – 1 = 111111111112,则有

  1. 0 11111111111 0000000000000000000000000000000000000000000000000000 =

  2. 0 11111111111 0000000000000000000000000000000000000000000000000000 = -∞

例20:如果指数为2e – 1并且的尾数的小数部分非0,这个数表示NaN,即不是一个数

对于float类型,有

  1. 0 11111111 00000000000000000000001 = NaN

  2. 0 11111111 00000000000000000000010 = NaN

  3. 0 11111111 00000000000000000000011 = NaN

  4. ……(为NaN的数共有223 1个)

对于double类型,有

  1. 0 11111111111 0000000000000000000000000000000000000000000000000001 = NaN

  2. 0 11111111111 0000000000000000000000000000000000000000000000000010

    相关阅读