浮点数在计算机中的表示

浮点数

早期的计算机使用定点数来表示实数,由于定点数的小数点位置固定,而计算机字长有限,定点数无法表示很大和很小的实数,因此而在计算机科学中有了对于实数近似值数值的表示法——浮点数。这种表示法类似于十进制中的科学计数法——它的优点是数的数量级、精确度以及数值都非常准确,浮点数表示其基数为 2 ,因此一个浮点数 a 由两个数 m e 来表示: a = m × b e ,其中 b 表示基数, m 称为尾数。下面对浮点数在计算机中的表示详细介绍。

1.1 浮点数在计算机中的表示

浮点数可以这样表示[1]

V a l u e = s i g n × e x p o n e n t × f r a c t i o n

也就是说浮点数的实际值等于,符号值乘以指数偏移值,再乘以分数值。

我们知道当前绝大多数计算机(据说有过三进制计算机[7])的数据都是二进制编码(0 和 1),在计算机中,二进制的浮点数按照以上公式在一定字长内(单精度浮点数32位,双精度浮点数64位)分别存储“符号位(sign bit)”,“指数部分”——次高有效的 e 个比特,以及“有效数(significand)”的小数部分——非规约数(稍后介绍)整数部分为 0,其他整数部分一律为 1。存储形式如下图所示:

(图片引用自[1]

1.1.1 指数偏移值

这里着重介绍关于“指数部分”的偏移量,IEEE754 中规定,单精度浮点数的指数域是 8 个比特,因此它的指数偏移值为 2 8 1 1 = 127 ,64 位双精度浮点数的值数域是 11 个比特,它的指数偏移值为 2 11 1 1 = 1023 为什么要指数编码时需要加上一个偏移值?

我们知道,在 10 进制的科学计数法中,指数可以是负数,而计算机存储浮点数的“指数部分”又是一个无符号的整数,因此,在 IEEE754 标准规定,exponent 必须减去一个偏移值而得到真实的“指数值”。

为什么单精度浮点数指数偏移值是 127,双精度浮点数指数偏移值是 1023 呢? IEEE754 标准规定了几个特殊值[1]:

  • 如果指数是 0 并且尾数的小数部分是 0,这个数是 ±0(和符号位有关)。
  • 如果指数 = 2 e 1 并且尾数的小数部分是 0,这个数是 ±∞(同样和符号位有关)。
  • 如果指数 = 2 e 1 并且尾数的小数部分非 0,这个数表示为不是一个数 NaN。
    (注意,这里所说的指数是指编码后的指数值(即阶码),而非指数真值,务必了解清楚。)

[8]这就意味着,单精度浮点数的 8 位阶码(即移码表示的指数部分)除特殊值外所表示范围为 ( 1 , 254 ) ,我们知道 8 位有符号数的补码取值范围是 ( 128 , + 127 ) 假设偏移值是 +128,在表示真值 +127 时阶码为 1111 1111,显然,根据 IEEE754 标准,255 是保留数值,偏移值是 +128 表达 +127时产生了上溢。与此同时,偏移值为 +127,在表达 -127 时,阶码为 0000 0000,同样依据 IEEE754 标准,0 也是保留数值,于是产生了下溢(更无法表达 -128 真值),因此阶码真值去掉 -127 和 -128,其取值范围为 ( 126 , + 127 )

此外,关于偏移量取值为 127 而不是 128 有另一种说法:为了使浮点数上下(正负)取值范围相对对称[9],相对而言,更倾向上文的说法。

为什么计算机存储浮点数的“指数部分”要用移码表示?

有符号数的原码、反码和补码一个共同特点是将符号作为最高位,与数值部分一起编码,正好是 “0”,负号是 “1”。这样在比较不同符号数据时比较麻烦,机器认为 “1” 比 “0” 大,因此,正负数比较时可能得到的结果是负数比较大,这显然不合理,而移码和补码的区别是符号位刚好相反,这样,便可以直接使用移码对不同符号数据进行比较了。

1.1.2 规约浮点数[1]

规约——指用唯一确定的浮点形式表示一个值。 wiki 中对规约浮点数做了说明:如果指数编码值在区间 0 < e x p o n e n t < 2 e 2 之间(例如,单精度浮点数,其指数编码值区间为 0 < e < 255 ),且在科学计数法表示下,其分数的最高有效位为 是 1。(注意一点,规约浮点数尾数的最高有效位 1 是隐藏的。)

1.1.3 非规约浮点数

如果浮点数指数部分编码值是 0,分数部分非 0,那么这样的浮点数称为非规约浮点数。IEEE754 标准规定,非规约浮点数指数偏移值比规约浮点数指数偏移值小 1,例如,单精度规约浮点数其指数偏移值是 127,其非规约浮点数指数偏移值就是 126,这也就意味着,非规约浮点数的指数真值是 -126,这也其指数编码值刚好是 0。

非规约浮点数有两个作用一是提供了表示数值 0 的方法,因为规约浮点数规定了尾数 1 ,因此规约浮点数不能表示数值 0,当阶码字段全为 0 时(这表明该浮点数是一个非规约浮点数),尾数有效位也是 0 时,该浮点数绝对值是 0,再结合符号位,我们可以得到 ± 0

非规约数第二个作用是可以表示接近 0 的数值,它比最小规约浮点数更趋近 0,IEEE754-1985 标准使用非规约浮点数填补最小规约数与 0 的距离,非规约浮点数提供了一种属性称为“逐渐式下溢出(gradual underflow)”,所有可能的数值均分分布的接近 0,否则会发生“突然式下溢出(abrupt underflow)”,对于单精度浮点数,我们知道最小规约浮点数的绝对值是 1.0 × 2 126 (尾数真值是 1.0,指数真值是 1),次小的最小规约浮点数是 1.0 × 2 126 × 2 23 ,会发现最小规约浮点数 与 0 的距离是与相邻次小规约浮点数距离的 2 23 倍,非常突然的下溢出到 0,这会造成一种情况,两个小浮点数 X Y (在区间 [ 1.0 × 2 126 , 1.0 × 2 126 ] 内)相减的结果为 0,显然这不合理,非规约浮点数可以解决该问题,依据非规约浮点数的定义(注意非规约浮点数指数编码值是 0,其实际值是 -126),它的最大和最小绝对值表示分别为: ( 1.0 2 23 ) × 2 126 2 23 × 2 126 ,这些数值相邻距离是 2 149 ,最小非规约浮点数绝对值和 0 的距离也是 2 149 ,因此,非规约浮点数扩大了 0 附近的浮点数表示范围。

为什么非规约浮点数这样设置指数偏移值( B i a s 1 ,这里注意下,在有些教程中偏移值是负值 1 B i a s )?

这种方式提供了一种从非规格化数值平滑转换到规格化数值的方法。《深入理解计算机系统》第三章 81 页以 8 位浮点数举例,假设其阶码占 4 位,小数位占 3 位,计算出来最大的非规约浮点数为 7 512 ,最小的规约浮点数为 8 512 ,从非规约浮点数到规约浮点数的平滑转变归功于将指数 E 定义为 1 B i a s B i a s 1 ),而不是 B i a s B i a s ),这样可以补偿非规约浮点数尾数没有隐含的开头 1。最大非规约浮点数和最小规约浮点数的阶码相同,并且其尾数刚好相差 2 n (尾数占位是 n,以上文 8 位浮点数举例,小数占 3 位,则最大非规约浮点数尾数为 0.111,最小规约浮点数尾数为 1.000,两者之差刚好是 2 3 ),这样正好可以完成非规约浮点数到规约浮点数的平滑过渡。

1.2 单精度和双精度浮点数

C/C++ 中 float 和 double 分别表示单精度类型浮点数和双精度类型浮点数,float 是 32 位 4 个字节,double 是 64 位 8 个字节,在 IEEE754[1] 标准规范中它们的“符号位”,“指数部分”以及“尾数”(即小数部分)占位如下:

(图片引用自[1]

(图片引用自[1]

1.3 浮点数转二进制

假设我们有一个 10 进制浮点数 x = 12.34 x 的整数部分可以表示为 1 × 10 1 + 2 × 10 0 ,小数部分可以表示为 3 × 10 1 + 4 × 10 2 ,类似推广到其他进制表示,例如我们有一个二进制数 110.011 ,其整数部分可以表示为 1 × 2 2 + 1 × 2 1 + 0 × 2 0 ,小数部分表示为 0 × 2 1 + 1 × 2 2 + 1 × 2 3 。其他进制表示同理。(注意,我们通常习惯的运算是基于 10 进制的,以上进制的运算表达式所得数值也是 10进制。)

(图片引用自[10]
28 的三进制表示即为 1001 (通常高位在左,低位在右),三进制 1001 可表示为: 1 × 3 3 + 0 × 3 2 + 0 × 3 1 + 1 × 3 0 ,可以看出以上短除法运算过程其实是三进制整数转换为 10 进制数的逆过程。

对于小数部分,如何从 10 进制转换为二进制表示?回顾上文,采用乘 2 取整法,即将小数部分乘以 2,取结果的整数部分,剩下的小数部分继续乘以 2,再取整数部分,一直循环,知道小数部分为 0,如果小数部分一直不能为 0,就需要按照保留位数,对保留位之外的数值原则上舍 0 入 1。最后对全部所取整数从第一个到最后一个依次排列,这一点与短除法转换 10 进制整数刚好相反。

1.3.1 无小数浮点数转二进制表示

依据 IEEE754 标准,我们知道浮点数有几个特殊值,比如数值 0,单精度浮点数的表示为:

s e f 0 | 0000 0000 | 000 0000 0000 0000 0000 0000.

其他浮点数特殊值都有其对应二进制表示。

那么对于一个普通的无小数浮点数,例如 12.0 ,如何将其转换成二进制表示呢?该浮点数整数部分是 12 ,小数部分是 0 ,首先将它们分别转换为二进制表示。

利用短除法,10 进制数 12 可以表示为: 1100 ,因为无小数, 12.0 二进制表示为 1100.0 ,根据 IEEE754 标准,规约浮点数尾数有一个隐含的高位 1,所以小数点向左移动三位, 12.0 就表示为 1.100 × 2 3 ,以单精度浮点数为例,尾数 23 位,指数偏移值是127,尾数 M = .100 后面补 0 直到总共有 23 位,指数编码值 e = 3 + 127 = 130 ,最后我们得到:

0 | 1000 0010 | 1000 0000 0000 0000 0000 000.

1.3.2 含小数浮点数转二进制表示

对于含小数的浮点数,需要将整数部分和小数部分分开处理,上文也分别介绍过两者转换二进制表示的方法,这里不再赘述。整数和小数部分都成功表示为二进制之后,依据 IEEE754 标准,规约化浮点数,最后可得到浮点数在计算机中的存储。

1.4 浮点数的精度

前文我们曾以单精度浮点数举例,相邻规约浮点数间的距离是 2 23 ,显然,浮点数不能表示所有的实数。我们知道,如果浮点数的小数部分能够在有限位内转换为二进制数,则浮点数在计算机中是精确表示的。然而,实际上不是所有的小数都可以在有限位内转换为二进制数,前文介绍过保留位之外取得的数值原则上舍 0 取 1,这样截断必然会造成精度的缺失。如果想要得到更精确的浮点数表示,只能依靠不断增加小数有效位,这也就是双精度浮点数比单精度浮点数更精确的原因。

例如,10 进制小数 0.1,我们使用乘 2 取整的方法,发现小数部分永远无法乘尽,最后会得到无限循环的 ( 0011 )

1.5 浮点数运算

首先我们明确浮点数的精度问题浮点数运算不遵循结合律和分配律,例如两个浮点数运算由于精度限制可能会产生“舍入”行为,另外我们经常说浮点运算比整数运算要慢,可是慢在哪里却不了解。下面将针对性介绍浮点运算的规则。

1.5.1 浮点加减运算

通过前文我们了解到计算机中浮点数阶码直接反应了实际尾数有效值小数点的位置,因此,当两个阶码不同的浮点数加减时,尾数不能直接进行加减运算,浮点加减运算必须按照以下几步进行[13]

  • 对阶,目的是使两个浮点数小数点位置对其,即使两个浮点数阶码相等。
    阶码对齐的原则是小阶向大阶看齐,所以,尾数每向右移 1 位,其对应阶码加 1。
  • 尾数求和,尾数求和依照定点运算[13](第 6 章“定点运算”)方式进行加减运算。
  • 规格化(规约化),尾数求和后我们需要依照 IEEE754 标准对求和结果规约化。
    左规:尾数左移一位,阶码减 1。
    右规:尾数右移一位,阶码加1。
  • 舍入,要考虑尾数右移丢失的数值,在有限位字长内保证浮点数的精度,前文介绍过,保留位之后的数值原则上“舍 0 取 1”。
  • 溢出判断,判断加减结果是否溢出。
    补码定点加减运算的溢出判断通常有两种方式:一、使用一个符号位时,溢出只可能发生在正正相加,负负相加,以及正负相减的情形,通常以最高有效位产生的进位和符号位产生的进位异或操作,若结果为 1 表示溢出,结果为 0 表示无溢出;二、使用双符号位,双符号位高位符号发生进位时,自然丢掉,且高位符号位是真正的符号位,当两个符号位不同时则表示发生了溢出。
    浮点数的溢出判断是根据阶码来判断是否溢出的。形如 01. x x x x x x . . . 10. x x x x x x x . . . 的尾数并不表示溢出,只需要右规完成规格化,再根据阶码来判断浮点数是否溢出。一般来说,浮点数下溢指的是数值接近 0,一般将其作为机器零处理,浮点数真正的溢出指的是上溢。
1.5.2 浮点乘除运算

和浮点加减法一样,浮点乘除运算分别对阶码进行加减运算和对尾数进行乘除运算。

  • 阶码运算。
    从上文我们知道阶码中计算机中使用移码存储,如果我们用双符号位表示阶码,即在原移码最高位前加一个符号位,该位恒为 0,如果运算之后,最高符号位变成了 1,则表示溢出,这时低位符号位为 0 表示上溢;低位符号位为 1 表示下溢。若最高符号位是 0,则表示没有溢出,这时低位符号位为 1 表示结果为正;低位符号位为 0 表示结果为负。
  • 尾数运算。
    尾数运算可参考定点小数乘除法。运算结果可能要进行左规,左规后还可能涉及到尾数舍入的问题,这时有两种处理方式,一是直接丢弃有效尾数位之外的数值,这种方式影响精度;二是采用浮点加减运算中介绍的舍入规则。

1.6 规约浮点数和非规约浮点数运算效率[12]

引文中举出了一段比较规约浮点数和非规约浮点书运算效率的代码。

Reference

[1]. IEEE754-wiki
[2]. 浮点数-wiki
[3]. 科学计数法-wiki
[4]. 定点数运算
[5]. 关于2的补码
[6]. 原码、补码、反码
[7]. 三进制计算机
[8]. 阶码偏移量为什么是127/1023
[9]. 阶码偏移量是 127 而不是 128 的另一种说法
[10]. 有趣的二进制
[11]. 你应该知道的浮点数基础知识
[12]. 除法-短除法(Wiki)
[13]. 计算机组成原理-唐朔飞

猜你喜欢

转载自blog.csdn.net/jvandc/article/details/81176294