计算机中的小数表示


前言

本文会详细解释浮点数在计算机中的表示,读者需要有简单二进制无符号数,补码表示的基本知识即可。耐心看完就能掌握计算机中的浮点数的表示及其特点。文中我已经尽量多用图、表帮助大家理解。

整数表示的缺陷

对于 N b i t Nbit Nbit 表示的无符号数,其可以表示从 0 − ( 2 N − 1 ) 0 - (2^{N}-1) 0(2N1)范围内的所有整数。当 N = 32 N=32 N=32 时,其表示的最大值为 ( 2 32 − 1 ) = 4 , 294 , 967 , 295 (2^{32}-1)=4,294,967,295 (2321)=4,294,967,295
对于用补码表示的 N b i t Nbit Nbit 表示的有符号数,其可以表示从 ( − 2 N − 1 ) − ( 2 N − 1 − 1 ) (-2^{N-1})-(2^{N-1}-1) (2N1)(2N11) 范围内的所有整数。当 N = 32 N=32 N=32 时,其表示的最大值为 ( 2 31 − 1 ) = 2 , 147 , 483 , 647 (2^{31}-1)=2,147,483,647 (2311)=2,147,483,647

缺点在于我们无法表示特别大的小数以及特别小的小数以及小数。
例如: 3.155692611 × 2 10 3.155692611×2^{10} 3.155692611×210 3.155692611 × 2 − 11 3.155692611×2^{-11} 3.155692611×211 或一个简单的 1.5 1.5 1.5

定点小数

定点小数,即小数点位置固定的小数表示方法。在十进制中,以小数 11.1101 11.1101 11.1101 为例,其每一位的位权如下图所示:
在这里插入图片描述
和十进制表示类似,大家可以猜到在二进制表示中,以 6 b i t 6bit 6bit 形式定点小数 x x . y y y y xx.yyyy xx.yyyy 为例,其每一位的位权则如下图所示:
在这里插入图片描述

此时, 11.110 1 2 11.1101_2 11.11012 表示的小数十进制下其值为: 2 + 1 + 0.5 + 0.25 + 0.0625 = 3.8125 2+1+0.5+0.25+0.0625=3.8125 2+1+0.5+0.25+0.0625=3.8125

按照上方定点小数表示方法,可表示的数字范围最小值: x   y x\ y x y 0 0 0 则为 0 0 0,最大值: x   y x\ y x y 1 1 1 则为 2 + 1 + 0.5 + 0.25 + 0.125 + 0.0625 = 3.9375 2+1+0.5+0.25+0.125+0.0625=3.9375 2+1+0.5+0.25+0.125+0.0625=3.9375。表示整数部分的长度为 m m m,表示小数部分的长度为 n n n,最大值可表示为 ( 2 m − 2 − n ) (2^m-2^{-n}) (2m2n)。简单理解:全 1 1 1 表示的二进制小数 11.111 1 2 11.1111_2 11.11112 00.000 1 2 = 2 − n 00.0001_2=2^{-n} 00.00012=2n 即为 100.000 0 2 = 2 m 100.0000_2=2^m 100.00002=2m

定点小数加法乘法运算

为了过程简单, 1.5 1.5 1.5 用上方二进制定点小数但小数部分少一位来表示为 01.10 0 2 01.100_2 01.1002,0.5表示为 00.10 0 2 00.100_2 00.1002。加法则按顺序排列,位权相同对应位置相加,乘法和十进制数字加法一样,分别如下图左右所示。
在这里插入图片描述
使用上述定点小数表示法仍旧有效无法表示特别大和特别小的小数。例如: 3.155692611234 × 2 10 3.155692611234×2^{10} 3.155692611234×210 3.155692611 × 2 − 11 3.155692611×2^{-11} 3.155692611×211

浮点数

我们先以十进制科学计数法数字: 6.02 × 1 0 23 6.02×10^{23} 6.02×1023 为例, 6.02 6.02 6.02 是尾数 ( s i g n i f i c a n d ) (significand) (significand) 23 23 23 是阶码 ( e x p o n e n t ) (exponent) (exponent) 10 10 10 是基数 ( b a s e / r a d i x ) (base/radix) (base/radix)。其中,规格化的要求中尾数的最高有效位不为 0 0 0 且小数点左侧只能有一位。
规格化: 1.0 × 1 0 − 9 1.0×10^{-9} 1.0×109,不能是 0.1 × 1 0 − 8 0.1×10^{-8} 0.1×108,也不能是 10.0 × 1 0 − 10 10.0×10^{-10} 10.0×1010

同理扩展到二进制之中,则以 1.0 1 2 × 2 − 1 1.01_2×2^{-1} 1.012×21 为例, 1.01 1.01 1.01 为尾数, − 1 -1 1 为阶码, 2 2 2 为基数。其中,由于二进制中只有 0 0 0 1 1 1 ,除了 0 0 0 之外的每一个规格化之后的二进制小数小数点左侧都为 1 1 1。这就是浮点数。用科学计数法形式更准确表示规格化之后的数字为如下形式: 1. x x x . . . x ∗ 2 y y y . . . y 1.xxx...x * 2^{yyy...y} 1.xxx...x2yyy...y

IEEE754浮点数标准

二进制中除了 0 0 0 之外的所有小数规格化之后小数点左侧都为 1 1 1,标准在表示尾数时把它省略,尾数部分只表示 x x x . . . x xxx...x xxx...x 部分,阶码用于表示 y y y . . . y yyy...y yyy...y 部分,首位数符表示数据的正负。 32 b i t 32bit 32bit 单精度浮点数的各个字段及其长度如下图所示:

在这里插入图片描述
上述 I E E E 754 IEEE754 IEEE754 标准中,由一组 32 b i t 32bit 32bit 二进制数计算其表示的小数的公式为: ( − 1 ) S ∗ ( 1.0 + s i g n i f i c a n d ) ∗ 2 E x p o n e n t − 127 \color{red}(-1)^S*(1.0+significand)*2^{Exponent-127} (1)S(1.0+significand)2Exponent127。其阶码要减掉 127 127 127 的原因在于标准采用了移码来表示。具体随后详细解释。

此时,一位表示数符, 8 8 8 位用于表示阶码,阶码位数越多,则表示的范围越大。 23 23 23 位表示尾数,尾数位数越多,则表示的精度越高。

最低有效位 L e a s t   S i g n i f i c a n t   B i t Least \ Significant \ Bit Least Significant Bit 的位权为 2 − 23 2^{-23} 223 ,具体可类比上方定点小数表示中小数位数及其位权的关系。

移码

移码本身非常容易理解。对于 N b i t Nbit Nbit 无符号数,其表示的数据范围是 [ 0 , 2 N − 1 ] [0, 2^N-1] [0,2N1]。移码的特性在于在无符号数的基础上添加了一个 b i a s bias bias 偏移量。其表示的范围变为 [ − b i a s ,   2 N − 1 − b i a s ] [-bias,\ 2^N-1-bias] [bias, 2N1bias]。直观来说:移码用 a + b i a s a+bias a+bias 的二进制序列来存储 a a a 的值。其规律如下图所示:
在这里插入图片描述
移码最直观的特点:越小的数字,其二进制表示下的无符号数也越小。这为浮点数比较提供了方便。

对于 N b i t Nbit Nbit 表示的移码,为了保持移码表示的正负数的数量基本一致,其偏移量一般取值为 2 N − 1 − 1 2^{N-1}-1 2N11。对于 8 b i t 8bit 8bit一般的便宜量取值为 127 127 127。这也是 I E E E 754 IEEE754 IEEE754 标准的取值。上方未标明 b i a s bias bias 的取值,但可观察到 b i a s bias bias 3 3 3

注:课程 C S 61 C CS61C CS61C 在数据表示课程中的移码把 b i a s bias bias 理解为负数,例如取偏移量为 − 3 -3 3,而获得表示值的过程变为其无符号数加上 b i a s bias bias。这和我们的 b i a s bias bias 取正,获取到真值的过程为无符号数减掉 b i a s bias bias 异曲同工。加负数和减掉正数的区别。标准中为我们理解的 b i a s bias bias 取正值。

阶码的移码表示

阶码用 8 8 8 位表示,由于每一个符合标准的小数都必须经过规格化,当阶码不相等时,则比较阶码,阶码相等再去比较尾数。此时为了在没有特定浮点数硬件的机器上支持浮点数,例如:使用整数比较指令对浮点数进行排序,标准制定者对阶码使用移码表示。

若使用补码表示,那么当阶码为负数时,其二进制序列表示的无符号整数比阶码为正数时更大,例如: 32 b i t 32bit 32bit − 1 -1 1 1 1 1 用补码表示分别为: 0 x 11111111 0x11111111 0x11111111 0 x 00000001 0x00000001 0x00000001

设计者选择移码来表示阶码。为 32 b i t 32bit 32bit 的单精度浮点数中 8 b i t 8bit 8bit 移码选择的 b i a s bias bias 偏移量为 127 127 127。其余长度下浮点数各字段长度如下表所示:
在这里插入图片描述
我们知道按照 b i a s bias bias 127 127 127 来计算,其移码可以表示的范围是 [ − 127 , 128 ] [-127, 128] [127,128],区间两个端点 − 127 -127 127 对应的阶码二进制为 8 8 8 位全 0 0 0 128 128 128 对应阶码二进制为 8 8 8 位全 1 1 1,这两个端点在标准中都有特殊意义,故在表格中没有他们。

IEEE754中的特殊点

我们知道 I E E E 754 IEEE754 IEEE754 标准中规定了阶码的表示值的有效范围为 [ − 126 , 127 ] [-126, 127] [126,127] ,其两个端点的特殊情况以及尾数的取值的具体意义如下表所示:

在这里插入图片描述
阶码为 0 0 0,尾数为 0 0 0,表示数字 0 0 0
阶码为 0 0 0,尾数不为 0 0 0,表示非规格化小数。
阶码的范围 [ 1 , 254 ] [1, 254] [1,254] 对应表示值范围为 [ − 126 , 127 ] [-126, 127] [126,127] 。无特殊意义,正常表示浮点数。
阶码为 255 255 255,尾数为 0 0 0,表示无穷大,符号位的正负作为正负无穷大的区分。
阶码为 255 255 255,尾数不为 0 0 0,表示 N o t   a   n u m b e r ( N a N ) Not \ a \ number(NaN) Not a number(NaN)

正式介绍这几种情况之前,先给出一个初步不全面的浮点数的表示范围。
最小正数:数符为正,阶码最小: − 126 -126 126,尾数最小: 0 0 0。其表示的浮点数为: 1.0 ∗ 2 − 126 ≈ 1.1754 ∗ 1 0 − 38 1.0*2^{-126} \approx 1.1754*10^{-38} 1.021261.17541038
最大正数:数符为正,阶码最大: 127 127 127,尾数最大: 23 b i t 23bit 23bit 1 1 1,其表示的浮点数为: ( 1 + 0.5 + . . . + 2 − 23 ) ∗ 2 127 = ( 2 − 2 − 23 ) ∗ 2 127 ≈ 3.4028235 ∗ 1 0 38 (1+0.5+...+2^{-23})*2^{127}=(2-2^{-23})*2^{127} \approx 3.4028235*10^{38} (1+0.5+...+223)2127=(2223)21273.40282351038
最小负数:数符为负,阶码最大: 127 127 127,尾数最大: 23 b i t 23bit 23bit 1 1 1,其表示的浮点数为: ( − 1 ) ∗ ( 1 + 0.5 + . . . + 2 − 23 ) ∗ 2 127 = − ( 2 − 2 − 23 ) ∗ 2 127 ≈ − 3.4028235 ∗ 1 0 38 (-1)*(1+0.5+...+2^{-23})*2^{127}=-(2-2^{-23})*2^{127} \approx -3.4028235*10^{38} (1)(1+0.5+...+223)2127=(2223)21273.40282351038
最大负数:数符为负,阶码最小: − 126 -126 126,尾数最小: 0 0 0,其表示的浮点数为: − 1.0 ∗ 2 − 126 ≈ − 1.1754 ∗ 1 0 − 38 -1.0*2^{-126} \approx -1.1754*10^{-38} 1.021261.17541038
用数轴表示其范围如下所示:
在这里插入图片描述

两个0

对于除了 0 0 0 之外的小数,规格化之后的尾数 s i g n i f i c a n d significand significand 范围永远在 ( 0 , 1 ) (0,1) (0,1) 之间。由于 0 0 0 的小数表示没有小数点左侧的 1 1 1 ,标准规定阶码和尾数为 0 0 0 用于表示数字 0 0 0。注意到符号位可以为 1 1 1,表示负数。所以我们有两个 0 0 0 32 b t i 32bti 32bti 0 0 0 表示 0 0 0;符号位为 1 1 1,其余全 0 0 0 表示 − 0 -0 0。但他们都是 0 0 0。经过下图程序验证, 0 = = − 0 0==-0 0==0

#include <iostream>
#include <cstdio>
union num{
    float f_num;
    int32_t field;
};
int main(){
    std::cout<<"hello,world"<<std::endl;
    std::cout<<"sizeof(float)=="<<sizeof(float)<<std::endl;

    num f1;
    f1.field=0;
    num f2;
    f2.field = 0x80000000;
    
    std::cout<<"f1 = "<<f1.f_num<<std::endl;
    std::cout<<"f2 = "<<f2.f_num<<std::endl;
    if(f1.f_num == f2.f_num)std::cout<<"0 == 0x80000000"<<std::endl;
    else std::cout<<"0 != 0x80000000" <<std::endl;

    return 0;
}

环境为 64 b i t   w i n d o w s 64bit \ windows 64bit windows v s c o d e + m i n g w 64 vscode+mingw64 vscode+mingw64 时,程序输出如下图所示:

在这里插入图片描述
另外,当所有位置全 0 0 0 时,按照浮点数公式的严格定义下,其表示的数字应该为 1.0 ∗ 2 0 − 127 = 2 − 127 1.0*2^{0-127}=2^{-127} 1.020127=2127 不为 0 0 0 但无限接近于 0 0 0 ,下面我们会知道阶码 0 0 0 表示的非规格化数,不能按照规格化的计算方式来计算其表示的值,标准则规定全 0 0 0 表示 0 0 0

非规格化数字

根据数轴我们知道,正常情况之下标准能够表示的最小正数和最大正数为 + 2 − 126 +2^{-126} +2126 − 2 − 126 -2^{-126} 2126 。此时我们想要表示更小的非 0 0 0 小数则会超过标准阶码的表示范围引发下溢。从 0 0 0 + 2 − 126 +2^{-126} +2126 之间没有内容,无法表示。

此时为了减小下溢的概率,标准规定:阶码为0,尾数不为0则表示非规格化数字,其指数默认为-126。其表示的小数计算公式变为: ( − 1 ) S ∗ ( s i g n i f i c a n d ) ∗ 2 − 126 \color{red}(-1)^S*(significand)*2^{-126} (1)S(significand)2126 。其中,按照移码的规则阶码为0表示值为 − 127 -127 127,但标准规定非规格化数字的阶码默认为 − 126 -126 126 不变,所有非规格化数字阶码全 0 0 0 一样。

此时我们能够表示的最小正数变为:数符为正,阶码为 0 0 0,尾数为 1 1 1,其表示的值为: 2 − 23 ∗ 2 − 126 = 2 0 ∗ 2 − 149 = 2 − 149 2^{-23}*2^{-126}=2^0*2^{-149}=2^{-149} 2232126=202149=2149
非规格化数能够表示的下一个数字:数符为正,阶码为 0 0 0,尾数为 2 2 2,其二进制序列为: 0 b 00000000000000000000010 0b00000000000000000000010 0b00000000000000000000010,其表示的值为: 2 − 22 ∗ 2 − 126 = 2 − 148 = 2 1 ∗ 2 − 149 2^{-22}*2^{-126}=2^{-148}=2^1*2^{-149} 2222126=2148=212149
非规格化数能够表示的下一个数字:数符为正,阶码为 0 0 0,尾数为 3 3 3,其二进制序列为: 0 b 00000000000000000000011 0b00000000000000000000011 0b00000000000000000000011,其表示的值为: ( 2 − 22 + 2 − 23 ) ∗ 2 − 126 = 2 − 148 + 2 − 149 = ( 2 0 + 2 1 ) ∗ 2 − 149 (2^{-22}+2^{-23})*2^{-126}=2^{-148}+2^{-149}=(2^0+2^1)*2^{-149} (222+223)2126=2148+2149=(20+21)2149
… …
… …
非规格化数能够表示的最后一个数字:数符为正,阶码为 0 0 0,尾数为全 1 1 1,其二进制序列为: 0 b 11111111111111111111111 0b11111111111111111111111 0b11111111111111111111111,其表示的值为: ( 2 − 1 + . . . + 2 − 22 + 2 − 23 ) ∗ 2 − 126 = 2 − 127 + . . . + 2 − 148 + 2 − 149 = ( 2 22 + 2 21 + . . . + 2 1 + 2 0 ) ∗ 2 − 149 (2^{-1}+...+2^{-22}+2^{-23})*2^{-126}=2^{-127}+...+2^{-148}+2^{-149}=(2^{22}+2^{21}+...+2^1+2^0)*2^{-149} (21+...+222+223)2126=2127+...+2148+2149=(222+221+...+21+20)2149
另外从另一方面考虑这个结果, ( 2 − 1 + . . . + 2 − 22 + 2 − 23 ) ∗ 2 − 126 = ( 1 − 2 − 23 ) ∗ 2 − 126 = 2 − 126 − 2 − 149 (2^{-1}+...+2^{-22}+2^{-23})*2^{-126}=(1-2^{-23})*2^{-126}=2^{-126}-2^{-149} (21+...+222+223)2126=(1223)2126=21262149

规格化数能够表示的第一个数字:数符为正,阶码二进制为 0 b 00000001 0b00000001 0b00000001,尾数为全 0 0 0 其表示的值为: 1.0 ∗ 2 ∗ 1 − 127 = 2 − 126 1.0*2*{1-127}=2^{-126} 1.021127=2126

此时,尾数加 1 1 1,表示的小数值加 2 − 149 2^{-149} 2149。换一种理解方式为从 0 0 0 2 − 126 2^{-126} 2126 之间的步长为 2 − 149 \color{red}2^{-149} 2149。尾数从全 0 0 0 到全 1 1 1,一共 2 23 2^{23} 223 种取值,我们理解为区间分做 2 23 2^{23} 223 份。具体见下表。

数符阶码尾数真值
00b000000000b000000000000000000000000
00b000000000b00000000000000000000001 2 − 149 2^{-149} 2149
00b000000000b00000000000000000000010 2 ∗ 2 − 149 2*2^{-149} 22149
00b00000000… …… …
00b000000000b11111111111111111111111 2 − 126 − 2 − 149 2^{-126}-2^{-149} 21262149

而浮点数最大值能够表示到 3.4 ∗ 1 0 38 3.4*10^{38} 3.41038,一直保持这么小的步长必然不行。

正常浮点数

书接上文,我们将揭示为什么浮点数能够表示数据范围如此之大,重点关注每次阶码加 1 1 1 之后区间的两个端点和步长的变化。

阶码为 1 1 1的规格化数能够表示的第二个数字:数符为正,阶码二进制为 0 b 00000001 0b00000001 0b00000001,尾数为 1 1 1 其表示的值为: ( 1.0 + 2 − 23 ) ∗ 2 1 − 127 = 2 − 126 + 2 − 149 (1.0+2^{-23})*2^{1-127}=2^{-126}+2^{-149} (1.0+223)21127=2126+2149
… …
阶码为 1 1 1 的规格化数能够表示的最后一个数字:数符为正,阶码二进制为 0 b 00000001 0b00000001 0b00000001,尾数为全 1 1 1 其表示的值为: ( 1.0 + 2 − 1 + . . . + 2 − 23 ) ∗ 2 1 − 127 = 2 − 126 + 2 − 127 + . . . + 2 − 149 (1.0+2^{-1}+...+2^{-23})*2^{1-127}=2^{-126}+2^{-127}+...+2^{-149} (1.0+21+...+223)21127=2126+2127+...+2149
阶码为 2 2 2 的规格化数能够表示的第一个数字:数符为正,阶码二进制为 0 b 00000010 0b00000010 0b00000010,尾数为 0 0 0 其表示的值为: ( 1.0 ) ∗ 2 2 − 127 = 2 − 125 (1.0)*2^{2-127}=2^{-125} (1.0)22127=2125

此时,从 2 − 126 2^{-126} 2126 2 − 125 2^{-125} 2125 之间步长仍旧为 2 − 149 \color{red}2^{-149} 2149,区间分做 2 23 2^{23} 223 份。

此时完善表格如下:

数符阶码尾数真值
00b000000000b000000000000000000000000
00b000000000b00000000000000000000001 2 − 149 2^{-149} 2149
00b000000000b00000000000000000000010 2 ∗ 2 − 149 2*2^{-149} 22149
00b00000000… …… …
00b000000000b11111111111111111111111 2 − 126 − 2 − 149 2^{-126}-2^{-149} 21262149
00b000000010b00000000000000000000000 2 − 126 2^{-126} 2126
00b000000010b00000000000000000000001 2 − 126 + 2 − 149 2^{-126}+2^{-149} 2126+2149
00b000000010b00000000000000000000010 2 − 126 + 2 ∗ 2 − 149 2^{-126}+2*2^{-149} 2126+22149
00b00000001… …… …
00b000000010b11111111111111111111111 2 − 125 − 2 − 149 2^{-125}-2^{-149} 21252149
00b000000100b00000000000000000000000 2 − 125 2^{-125} 2125

阶码为 2 2 2 的规格化数能够表示的第二个数字:数符为正,阶码二进制为 0 b 00000010 0b00000010 0b00000010,尾数为 1 1 1 其表示的值为: ( 1.0 + 2 − 23 ) ∗ 2 2 − 127 = 2 − 125 + 2 0 ∗ 2 − 148 (1.0+2^{-23})*2^{2-127}=2^{-125}+2^0*2^{-148} (1.0+223)22127=2125+202148
… …
阶码为 2 2 2 的规格化数能够表示的最后一个数字:数符为正,阶码二进制为 0 b 00000010 0b00000010 0b00000010,尾数为全 1 1 1 其表示的值为: ( 1.0 + 2 − 1 + . . . + 2 − 23 ) ∗ 2 2 − 127 = 2 − 125 + 2 − 127 + . . . + 2 − 148 (1.0+2^{-1}+...+2^{-23})*2^{2-127}=2^{-125}+2^{-127}+...+2^{-148} (1.0+21+...+223)22127=2125+2127+...+2148
阶码为3的规格化数能够表示的第一个数字:数符为正,阶码二进制为 0 b 00000011 0b00000011 0b00000011,尾数为 0 0 0 其表示的值为: ( 1.0 ) ∗ 2 3 − 127 = 2 − 124 (1.0)*2^{3-127}=2^{-124} (1.0)23127=2124

此时,从 2 − 125 2^{-125} 2125 2 − 124 2^{-124} 2124 之间步长已经变为 2 − 148 \color{blue}2^{-148} 2148

此时完善表格如下:

数符阶码尾数真值
00b000000000b000000000000000000000000
00b000000000b00000000000000000000001 2 − 149 2^{-149} 2149
00b000000000b00000000000000000000010 2 ∗ 2 − 149 2*2^{-149} 22149
00b00000000… …… …
00b000000000b11111111111111111111111 2 − 126 − 2 − 149 2^{-126}-2^{-149} 21262149
00b000000010b00000000000000000000000 2 − 126 2^{-126} 2126
00b000000010b00000000000000000000001 2 − 126 + 2 − 149 2^{-126}+2^{-149} 2126+2149
00b000000010b00000000000000000000010 2 − 126 + 2 ∗ 2 − 149 2^{-126}+2*2^{-149} 2126+22149
00b00000001… …… …
00b000000010b11111111111111111111111 2 − 125 − 2 − 149 2^{-125}-2^{-149} 21252149
00b000000100b00000000000000000000000 2 − 125 2^{-125} 2125
00b000000100b00000000000000000000001 2 − 125 + 2 − 148 2^{-125}+2^{-148} 2125+2148
00b000000100b00000000000000000000010 2 − 125 + 2 ∗ 2 − 148 2^{-125}+2*2^{-148} 2125+22148
00b00000010… …… …
00b000000100b11111111111111111111111 2 − 124 − 2 − 148 2^{-124}-2^{-148} 21242148
00b000000110b00000000000000000000000 2 − 124 2^{-124} 2124

… …
… …
中间省略掉大部分,我们跳到阶码为 127 127 127,二进制表示为 0 b 01111111 0b01111111 0b01111111,其表示值为 0 0 0 。读者可以猜测,此时区间两端点为多少?
阶码为127的规格化数能够表示的第一个数字:数符为正,阶码二进制为 0 b 01111111 0b01111111 0b01111111,尾数为 0 0 0 其表示的值为: ( 1.0 ) ∗ 2 127 − 127 = 2 0 = 1 (1.0)*2^{127-127}=2^0=1 (1.0)2127127=20=1
阶码为 127 127 127 的规格化数能够表示的第二个数字:数符为正,阶码二进制为 0 b 01111111 0b01111111 0b01111111,尾数为 1 1 1 其表示的值为: ( 1.0 + 2 − 23 ) ∗ 2 127 − 127 = 2 0 + 2 − 23 = 1 + 2 − 23 (1.0+2^{-23})*2^{127-127}=2^0+2^{-23}=1+2^{-23} (1.0+223)2127127=20+223=1+223
… …
阶码为 127 127 127 的规格化数能够表示的最后一个数字:数符为正,阶码二进制为 0 b 01111111 0b01111111 0b01111111,尾数为全 1 1 1 其表示的值为: ( 1.0 + 2 − 1 + . . . + 2 − 23 ) ∗ 2 127 − 127 = 2 0 + 2 − 1 + . . . + 2 − 23 (1.0+2^{-1}+...+2^{-23})*2^{127-127}=2^{0}+2^{-1}+...+2^{-23} (1.0+21+...+223)2127127=20+21+...+223
阶码为128的规格化数能够表示的第一个数字:数符为正,阶码二进制为 0 b 10000000 0b10000000 0b10000000,尾数为 0 0 0 其表示的值为: ( 1.0 ) ∗ 2 128 − 127 = 2 1 = 2 (1.0)*2^{128-127}=2^{1}=2 (1.0)2128127=21=2

此时,从 1 1 1 2 2 2 之间步长已经变为 2 − 23 \color{blue}2^{-23} 223。还是发现不了规律?我们继续看表

数符阶码尾数真值
00b000000000b000000000000000000000000
00b000000000b00000000000000000000001 2 − 149 2^{-149} 2149
00b000000000b00000000000000000000010 2 ∗ 2 − 149 2*2^{-149} 22149
0… …… …… …
00b011111110b00000000000000000000000 1 1 1
00b1111111… …… …
00b011111110b11111111111111111111111 1 − 2 23 1-2^{23} 1223
00b100000000b00000000000000000000000 2 2 2

… …
… …
中间再省略掉大部分,我们跳到阶码为 150 150 150,二进制表示为 0 b 10010110 0b10010110 0b10010110,其表示值为 23 23 23 。读者可以猜测,此时步长为多少?
阶码为 150 150 150 的规格化数能够表示的第一个数字:数符为正,阶码二进制为 0 b 10010110 0b10010110 0b10010110,尾数为 0 0 0 其表示的值为: ( 1.0 ) ∗ 2 150 − 127 = 2 23 (1.0)*2^{150-127}=2^{23} (1.0)2150127=223
阶码为 150 150 150 的规格化数能够表示的第二个数字:数符为正,阶码二进制为 0 b 01111111 0b01111111 0b01111111,尾数为 1 1 1 其表示的值为: ( 1.0 + 2 − 23 ) ∗ 2 150 − 127 = 2 23 + 1 (1.0+2^{-23})*2^{150-127}=2^{23}+1 (1.0+223)2150127=223+1
… …
阶码为 150 150 150 的规格化数能够表示的最后一个数字:数符为正,阶码二进制为 0 b 01111111 0b01111111 0b01111111,尾数为全 1 1 1 其表示的值为: ( 1.0 + 2 − 1 + . . . + 2 − 23 ) ∗ 2 150 − 127 = 2 23 + 2 22 + . . . + 2 0 = 2 24 − 1 (1.0+2^{-1}+...+2^{-23})*2^{150-127}=2^{23}+2^{22}+...+2^{0}=2^{24}-1 (1.0+21+...+223)2150127=223+222+...+20=2241
阶码为 151 151 151 的规格化数能够表示的第一个数字:数符为正,阶码二进制为 0 b 10000000 0b10000000 0b10000000,尾数为 0 0 0 其表示的值为: ( 1.0 ) ∗ 2 151 − 127 = 2 24 (1.0)*2^{151-127}=2^{24} (1.0)2151127=224

此时,从区间 2 23 2^{23} 223 2 24 2^{24} 224 之间步长已经变为 1 \color{blue}1 1 a m a z i n g ! ! ! \color{red}amazing ! ! ! amazing!!!

此时,再问一个问题:阶码为 151 151 151 时,步长和区间两端点各为多少?分别是 2 2 2 [ 2 24 , 2 25 ] [2^{24}, 2^{25}] [224,225]
… …
… …
阶码为 254 254 254 时,其表示值为 127 127 127 ,此时步长为 2 104 2^{104} 2104,区间端点变为 [ 2 127 , + ∞ ] [2^{127}, +∞] [2127,+],无穷大随后详细解释。

至此,我们已经揭示了规律:阶码从1开始,阶码每次加1,步长乘2,区间两端点乘以2。
另外,区间内永远分作 2 23 2^{23} 223 份。这也是为什么,浮点数能够表示 3.4 ∗ 1 0 38 3.4*10^{38} 3.41038 这么大的数字。

小tips:我们能够表示的最大奇数为: 2 24 − 1 2^{24}-1 2241。原因在于以后的步长不会再为 1 1 1 了。

无穷大

书接上文。标准规定,阶码为 255 255 255,尾数为 0 0 0,表示无穷大。数符用来区分正无穷和负无穷。

我们知道,按照移码的规律和标准规定,阶码为 254 254 254,表示值 127 127 127 已经为 32 b i t 32bit 32bit 单精度浮点数能够表示的最大阶码。
阶码为 254 254 254 的规格化数能够表示的最后一个数字:数符为正,阶码二进制为 0 b 11111110 0b11111110 0b11111110,尾数为全 1 1 1 其表示的值为: ( 1.0 + 2 − 1 + . . . + 2 − 23 ) ∗ 2 254 − 127 = 2 127 + 2 126 + . . . + 2 104 (1.0+2^{-1}+...+2^{-23})*2^{254-127}=2^{127}+2^{126}+...+2^{104} (1.0+21+...+223)2254127=2127+2126+...+2104

此时,再进一步,浮点数能够表示的下一个数应当是阶码为255的规格化数能够表示的第一个数。即:阶码为 255 255 255,尾数为 0 0 0。标准将其规定为无穷大。非常合理。

NaN

无穷大之后再进一步,标准规定阶码为 255 255 255,尾数非 0 0 0 N o t   a   N u m b e r Not \ a \ Number Not a Number
读者可通过一些协商/协议/标准来自定义其 N a N NaN NaN 具体类型,例如:尾数为 1 1 1,表示对负数做平方根,尾数为 2 2 2,表示除以 0 0 0 的非法操作等等等。

浮点数简单举例

对于一个浮点数二进制序列: 0 b 1   10000001   11100000000000000000000 0b1 \ 10000001 \ 11100000000000000000000 0b1 10000001 11100000000000000000000,计算其表示值为: ( − 1 ) ∗ ( 1.0 + 0.5 + 0.25 + 0.125 ) ∗ 2 129 − 127 = − 7.5 (-1)*(1.0+0.5+0.25+0.125)*2^{129-127}=-7.5 (1)(1.0+0.5+0.25+0.125)2129127=7.5

如何表示 1 / 3 1/3 1/3
1 / 3 = 0.333333333... = 0.25 + 0.0625 + 0.015625 + . . . = 1 / 4 + 1 / 16 + 1 / 64 + 1 / 256 + . . . = 2 − 2 + 2 − 4 + 2 − 8 + . . . 1/3 = 0.333333333...=0.25+0.0625+0.015625+...=1/4+1/16+1/64+1/256+...=2^{-2}+2^{-4}+2^{-8}+... 1/3=0.333333333...=0.25+0.0625+0.015625+...=1/4+1/16+1/64+1/256+...=22+24+28+...

二进制小数表示为 0.010101010101.. . 2 ∗ 2 0 0.010101010101..._{2} *2^0 0.010101010101...220,规格化之后变为: 1.01010101.. . 2 ∗ 2 − 2 1.01010101..._2 *2^{-2} 1.01010101...222。此时转为 32 b i t 32bit 32bit 标准表示:数符为0,阶码为 − 2 + 127 = 125 = 0 b 01111101 -2+127=125=0b01111101 2+127=125=0b01111101,尾数为: 0 b 01010101010101010101010 0b0101 0101 0101 0101 0101 010 0b01010101010101010101010
综上, 1 / 3 1/3 1/3 的浮点数表示为 0 b 0   01111101   01010101010101010101010 0b0 \ 01111101 \ 01010101010101010101010 0b0 01111101 01010101010101010101010

浮点数一些其余特性

浮点数计算不符合结合律

假设 x = 1.5 ∗ 1 0 38 x=1.5*10^{38} x=1.51038 y = − 1.5 ∗ 1 0 38 y=-1.5*10^{38} y=1.51038 z = 1 z=1 z=1 x + y + z x+y+z x+y+z 的结果和 x + z + y x+z+y x+z+y 的结果不一致。
验证程序如下所示:

#include <iostream>

int main(){
    float f1=1.5e38,f2=-1.5e38,f3=1.0;

    std::cout<<"(1.5e38+(-1.5e38))+1.0 = "<<f1+f2+f3<<std::endl;
    std::cout<<"(1.5e38+1.0)+(-1.5e38) = "<<f1+f3+f2<<std::endl;

    return 0;
}

程序输出如下所示:
在这里插入图片描述
主要问题在于:浮点数的加法执行流程粗略来讲为:首先要对两个小数统一阶码,阶码统一之后才可以尾数相加,尾数相加之后再重新规格化。这其中 23 b i t 23bit 23bit 精度的限制必然会导致位的缺失。

浮点数舍入规则

既然尾数位数有限,必然要有舍入的规则来确定多余的位如何处理。浮点数的舍入规则为舍入到偶数

对于普通小数,例如:2.4则四舍五入为2,2.6则四舍五入为3。对于中间位置,例如:2.5舍入为2,3.5舍入为4。

上述规则是在十进制下的,二进制下同理,以定点小数为例: 11. 1 2 11.1_2 11.12舍入时考虑到前方为 1 1 2 11_2 112,为奇数,则舍入为 10 0 2 100_2 1002 10. 1 2 10.1_2 10.12 舍入时则直接丢弃小数部分,舍入为 1 0 2 10_2 102 。下标 2 2 2 表示二进制。

浮点数与整数之间的相互转换

考虑以下问题:一个整数,强制类型转换为浮点数再转为整数是否还和原来相等?

uint32_t i;
if(i == (uint32_t)(float)i){
  printf("true!");
}

答案是不全部相等。根据上文的区间分析我们知道,当阶码为 151 151 151 时,其表示值为 24 24 24,此时区间端点为 [ 2 24 , 2 25 ] [2^{24}, 2^{25}] [224,225],区间步长为 2 2 2。此时我们就定义整数初值为: 2 24 + 1 2^{24}+1 224+1,按照此时 2 2 2 的步长浮点数刚好无法表示,此时转换必然存在位丢失情况,而C语言中的浮点数到整数的类型转换仅仅是丢弃全部小数,只取整数 2 24 = 16 , 777 , 216 2^{24}=16,777,216 224=16,777,216
程序验证如下所示:

#include <iostream>
int main(){
    float f10=3.2f,f11=3.5f,f12=3.9f;
    
    std::cout<<"(uint32_t)3.2 = "<<(uint32_t)f10<<std::endl;
    std::cout<<"(uint32_t)3.5 = "<<(uint32_t)f11<<std::endl;
    std::cout<<"(uint32_t)3.9 = "<<(uint32_t)f12<<std::endl;
    std::cout<<"-------------------------"<<std::endl;

    uint32_t target=(1<<24)+1;

    
    std::cout<<"(float)(1<<24)+1 = "<<((float)target)<<std::endl;
    std::cout<<"(uint32_t)(float)(1<<24)+1 = "<<((uint32_t)(float)target)<<std::endl;

    return 0;
}

程序输出结果如下图所示:
在这里插入图片描述

正如我们猜测的结果一样。

考虑另外问题:一个浮点数,强制类型转换为整数再转为浮点数是否还和原来相等?

float f;
if(f == (float)(uint32_t)f){
  printf("true!");
}

当然不等。考虑 1.5 1.5 1.5,转为整数后变为 1 1 1,浮点数可以表示 1 1 1。故转换之后结果变为 1 1 1
程序验证如下:

int main(){
    float f1=1.5f;

    std::cout<<"(float)(uint32_t)(1.5) = "<<(float)((uint32_t)(f1))<<std::endl;

    return 0;
}

结果输出如下图所示:
在这里插入图片描述
好!

总结

完结撒花!你已经完全了解计算机中小数表示了。

  • 26
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值