计算机中的小数表示

文章目录

  • 前言
  • 整数表示的缺陷
  • 定点小数
      • 定点小数加法乘法运算
  • 浮点数
  • IEEE754浮点数标准
      • 移码
      • 阶码的移码表示
  • IEEE754中的特殊点
      • 两个0
      • 非规格化数字
      • 正常浮点数
      • 无穷大
      • NaN
  • 浮点数简单举例
  • 浮点数一些其余特性
      • 浮点数计算不符合结合律
      • 浮点数舍入规则
      • 浮点数与整数之间的相互转换
  • 总结


前言

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

整数表示的缺陷

对于 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 这么大的数字。

无穷大

书接上文。标准规定,阶码为 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;
}

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

总结

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/823871.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

记录一次k8s pod之间ip无法访问,问题排查与定位

记录一次k8s pod之间ip无法访问&#xff0c;问题排查与定位 问题展现现象 node之间通信正常 部分node上的pod无法通信 排查有问题node 使用启动网络测试工具 环境准备 docker 数据库mysql 使用有状态副本集合 --- apiVersion: apps/v1 kind: StatefulSet metadata:anno…

生成对抗网络GAN的扩展应用理解

注&#xff1a;本文仅个人学习理解&#xff0c;如需详细内容请看原论文&#xff01; 一、cycleGAN 1.整体架构&#xff1a; 将图片A通过生成器生成图片B&#xff0c;然后通过判别器判别图片B的真假&#xff1b;并且图片B通过另一个生成器生成A‘&#xff0c;让A和A’近似。 2…

C++ queue priority_queuestack 详解及模拟实现

1. stack的介绍和使用 1.1 stack的介绍 1. stack是一种容器适配器&#xff0c;专门用在具有后进先出操作的上下文环境中&#xff0c;其删除只能从容器的一端进行元素的插入与提取操作。 2. stack是作为容器适配器被实现的&#xff0c;容器适配器即是对特定类封装作为其底层的容…

JVM垃圾回收与算法

1. 如何确定垃圾 1.1 引用计数法 在 Java 中&#xff0c;引用和对象是有关联的。如果要操作对象则必须用引用进行。因此&#xff0c;很显然一个简单 的办法是通过引用计数来判断一个对象是否可以回收。简单说&#xff0c;即一个对象如果没有任何与之关 联的引用&#xff0c;即…

Python pyglet制作彩色圆圈“连连看”游戏

原文链接&#xff1a; Python 一步一步教你用pyglet制作“彩色方块连连看”游戏(续)-CSDN博客文章浏览阅读1.6k次&#xff0c;点赞75次&#xff0c;收藏55次。上期讲到相同的色块连接&#xff0c;链接见&#xff1a; Python 一步一步教你用pyglet制作“彩色方块连连看”游戏-…

Ai2024安装包(亲测可用)

目录 一、软件简介 二、软件下载 一、软件简介 Adobe illustrator&#xff0c;常被称为“AI”&#xff0c;是一种应用于出版、多媒体和在线图像的工业标准矢量插画的软件。作为一款非常好的矢量图形处理工具&#xff0c;该软件主要应用于印刷出版、海报书籍排版、专业插画、多…

Fiddler抓包工具之高级工具栏中的Inspectors的使用

高级工具栏中的Inspectors的使用 Inspectors 页签允许你用多种不同格式查看每个请求和响应的内容。JPG 格式使用 ImageView 就可以看到图片&#xff0c;HTML/JS/CSS 使用 TextView 可以看到响应的内容。Raw标签可以查看原始的符合http标准的请求和响应头。Cookies标签可以看到…

手机拍照技术

拍照技巧 说明: 本文将主要介绍摄影和手机常见技巧&#xff1b; 1. 摄影的基本知识 **说明&#xff1a;**关于摄影&#xff0c;手机和相机的原理都是相同的&#xff0c;不同的是相机在很多方面优于手机&#xff0c;但是专业的设备对于我们这种的非专业的人来说&#xff0c;刚…

Linux时间同步练习

题目如下&#xff1a; 一.配置server主机要求如下&#xff1a; 1.server主机的主机名称为 ntp_server.example.com 2.server主机的IP为&#xff1a; 172.25.254.100 3.server主机的时间为1984-11-11 11&#xff1a;11&#xff1a;11 4.配置server主机的时间同步服务要求可以被所…

重磅,巫师3即将发布mod编辑器并开放创意工坊

热乎乎的消息&#xff01;巫师3即将推出mod编辑器和开放创意工坊&#xff01; 根据巫师3官方Steam消息&#xff0c;听说年底将推出mod编辑器&#xff0c;目前已经开始内测。想试用的玩家们&#xff0c;可以到redkit商店页面申请访问权限&#xff0c;体验最新的创意工具。 此外&…

存入Redis的值前面有很多空格

说明&#xff1a;记录一次使用Redis的错误&#xff1b; 场景 在将验证码存入Redis时&#xff0c;发现存入的值前面有很多空格&#xff0c;导致在与前端传入的值比较时&#xff0c;一直是false&#xff0c;验证不通过。如下&#xff1a; 上面这些“□”是占位符&#xff0c;复…

学习笔记(4月17日)vector底层原理

1.vector<vector>底层原理 vector是表示可变大小数组的序列容器&#xff0c;相当于一个动态的数组&#xff0c;比数组优越的在于它具有可动态改变的大小&#xff0c;同时&#xff0c;它写成了类模板&#xff0c;说明可以适用于其他类型&#xff0c;包括vector本身&#…

rust 学习笔记(13-19)

13 迭代器与闭包 Rust 的设计灵感来源于很多现存的语言和技术。其中一个显著的影响就是 函数式编程&#xff08;functional programming&#xff09;。函数式编程风格通常包含将函数作为参数值或其他函数的返回值、将函数赋值给变量以供之后执行等等。 闭包&#xff08;Closu…

游戏、app抓包

文章目录 协议app抓包游戏抓包 协议 在抓包之前&#xff0c;首先我们要对每个程序使用什么协议有个大致的了解&#xff0c;比如网页这种就是走的http协议。 在一些app中我们通过发送一个请求&#xff0c;然后服务器接受&#xff0c;响应&#xff0c;返回一个数据包&#xff0c…

网站模板-慈善捐赠基金会网站模板 Bootstrap4 html

目录 一.前言 二.预览 三.下载链接 一.前言 这是一个慈善网站的页面。页面包含了导航栏、横幅部分、关于、使命、新闻、活动、捐赠和页脚等不同的部分。该网站还包含了一些CSS样式和JavaScript脚本来实现交互和样式效果。 这个网站的具体结构如下&#xff1a; 导航栏部分&a…

吐血整理102个Python项目,从基础到高级,练完你就牛了!

前言 Python 初学者在迈过安装编程环境和基本语法的门槛&#xff0c;准备大展身手的时候&#xff0c;可能突然就会进入迷茫期&#xff1a; 不知道做些什么、再学些什么。。。 然后对编程的兴趣就会慢慢消退&#xff0c;找不到坚持下去的理由&#xff0c;从而慢慢淡忘之前学会…

OpenCV基本图像处理操作(九)——特征匹配

Brute-Force蛮力匹配 Brute-Force蛮力匹配是一种简单直接的模式识别方法&#xff0c;经常用于计算机视觉和数字图像处理领域中的特征匹配。该方法通过逐一比较目标图像中的所有特征点与源图像中的特征点来寻找最佳匹配。这种方法的主要步骤包括&#xff1a; 特征提取&#xff…

热烈祝贺中国特医首次获得蒙特国际品质奖!中国特医健效达品质永攀世界高峰

近日&#xff0c;第63届Monde Selection品质评鉴活动圆满落幕&#xff0c;健效达旗下优康力和优益力产品凭借其卓越品质&#xff0c;成功摘得世界蒙特奖&#xff0c;这是中国特医食品首次获得蒙特奖国际品质奖。 健效达特医树立世界特医新标杆&#xff0c;永攀世界高峰&#xf…

Java定时任务

一、java.util.Timer java.util.Timer 类允许您在未来的某个时间执行一个任务&#xff0c;或者在一定的时间间隔执行任务。您可以创建一个 Timer 实例&#xff0c;并调用其 schedule() 方法来安排任务的执行。这种方式比较简单&#xff0c;但在高并发环境下可能不够灵活。 1.…

放大招,推广手机流量卡,佣金丰厚等你来拿

流量卡推广是一个非常冷门但又在身边非常常见的行业&#xff0c;知道的人目前靠着这个信息&#xff0c;发了很多小财&#xff0c;可以说早知道这一行的人会非常容易变成暴发户。 你可能也会好奇&#xff0c;为什么那么多广告都是在推流量卡&#xff0c;他们推卡到底有多高的利…