虽然不是专业的CS出生,想想接触CS相关的东西也这么久了,居然不知道浮点数在内存中的表示是怎样的,实在是惭愧啊!于是最近查阅了相关资料,终于弄清了浮点数在内存中的表示,现在对相关的东西做一个总结。
以下总结主要来至于文档 A Tutorial on Data Representation Integers, Floating-point Numbers, and Characters 中的内容。当然还参考了其它一些朋友的文章以及自己的总结。
#####十进制数转换为二进制数
十进制数转换为二进制数时,整数部分和小数部分分别处理。至于十进制数转换为八进制,十六进制数,原理差不多,只是乘除的基不同。
- 整数部分
将整数部分除以2,取余数。然后将商除以2,取余数。然后将商除以2,取余数。重复以上商除以2,取余数的过程直到商为0为止。最后十进制数整数部分转换得到的二进制数为以上得到的余数的逆序排列,即转换得到的二进制数的最高位为最后的余数,最低位为得到的第一个余数。 - 小数部分
将小数部分乘以2,取整数部分。然后将上一步得到的小数部分乘以2,取整数部分。重复以上过程知道小数部分为0为止。最后十进制数小数部分转换得到的二进制数为以上得到的取整数的顺序排列,即转换得到的二进制数的最高位为第一个取整数,最低位为最后得到的取整数。小数部分比较特殊的一点是,可能会出现多次乘2后小数部分永远不为0的情况出现,这时可以根据精度的要求取特定位数的小数即可。这里也可以进行类似十进制中四舍五入的操作:假如要保留4位二进制小数,若第五位二进制小数位为0则不进位,若第五位二进制小数位为1则进位。
Example 1
十进制数:18.6875
-
整数部分=18
18/2 => 商=9 余数=0
9/2 => 商=4 余数=1
4/2 => 商=2 余数=0
2/2 => 商=1 余数=0
1/2 => 商=0 余数=1
因此,18D= 10010B -
小数部分=0.6875
0.68752=1.375 => 取整: 1
0.3752=0.75 => 取整: 0
0.752=1.5 => 取整: 1
0.52=1.0 => 取整: 1
因此 0.6875D = 0.1011B
最后可得: 18.6875D = 10010.1011B
Example 2
十进制数:1.1
-
整数部分=1
1/2 => 商=0 余数=1
因此,1D= 1B -
小数部分=0.1
0.12=0.2 => 取整: 0
0.22=0.4 => 取整: 0
0.42=0.8 => 取整: 0
0.82=1.6 => 取整: 1
0.62=1.2 => 取整: 1
0.22=0.4 => 取整: 0
…(无限循环)
假设此时保留四位小数。此时第五位小数位1,因此进一位。因此 0.1D ≈0.0010=B
最后可得: 1.1D≈1.0010B
#####浮点数在内存中的表示
我想大家对整数在内存中的表示已经比较熟悉了。整数分为无符号数和有符号数。有符号数也就是整数和负数,正数和负数在计算机内存中的表示是不同的。不过要注意的是整数在计算机中内存中有一个大端模式与小端模式的问题。
- 大端模式是指数据的低位字节保存在内存的高地址中,而数据的高位字节保存在内存的低地址中;
- 小端模式是指数据的低位字节保存在内存的低地址中,而数据的高位字节保存在内存的高地址中。
十六进制数0x1234分别在大端模式和小端模式下在内存中的存储形式如图1所示:
接下来转入正题,开始来谈一谈浮点数在内存中的存储形式。浮点数一般用科学计数法表示成
F
×
r
E
F×r^E
F×rE ,这里F表示小数部分,E表示指数部分,r为基。E和F均可以为正数或负数。十进制数的r为10,二进制的r为2。浮点数的科学计数法表示并不是唯一的。例如,上小节提到的十进制小数18.6875可以表示为:$ 186.875×10^{-1}$ , $ 1.86875×10^1$ , $0.186875×10^2 $ 。十进制小数18.6875对应的二进制表示形式为10010.1011。该二进制形式用科学计数法又可以表示为
10010.1011
×
2
−
1
10010.1011×2^{-1}
10010.1011×2−1 ,
1001.01011
×
2
1
1001.01011×2^1
1001.01011×21 ,
100.101011
×
2
2
100.101011×2^2
100.101011×22 ,
10.0101011
×
2
3
10.0101011×2^3
10.0101011×23 ,
1.00101011
×
2
4
1.00101011×2^4
1.00101011×24 。
科学计数法有一种“规范化的形式”。在规范化形式中要求
1
<
=
∣
F
∣
<
r
1<=|F|<r
1<=∣F∣<r。在十进制中
1
<
=
∣
F
∣
<
10
1<=|F|<10
1<=∣F∣<10 , 在二进制中
1
<
=
∣
F
∣
<
2
1<=|F|<2
1<=∣F∣<2 。因此在十进制和二进制浮点数的规范化的形式中,小数部分F的小数点前面仅有一位非零整数,特别在二进制浮点数中小数点前的一位非零整数固定为1(浮点数0,接近0的非常小的正数,接近0的非常小的负数,正无穷大,负无穷大,非数NaN(Not a Number)除外)。在规范化形式中,二进制浮点数可以表示为
(
−
1
)
S
∗
(
1.
F
R
)
∗
2
E
(-1)^S*(1.FR)*2^E
(−1)S∗(1.FR)∗2E (不适用于特殊情况),这里FR为
∣
F
∣
|F|
∣F∣ 中的小数部分。二进制浮点数10010.1011的规范化形式为
(
−
1
)
0
∗
1.00101011
×
2
4
(-1)^0*1.00101011×2^4
(−1)0∗1.00101011×24 ,
S
=
0
,
F
R
=
00101011
,
E
=
4
S=0,FR=00101011,E=4
S=0,FR=00101011,E=4。
现代计算机采用IEEE 754标准来存储规范化形式的浮点数,主要有32位单精度和64位双精度。浮点数在计算机内存中的32位单精度存储格式如图2所示。
S
′
S^{'}
S′(共一位)存储S的值,表示浮点数的正负,0代表正数,1代表负数,即
S
′
=
S
S^{'}=S
S′=S。
E
′
E^{'}
E′(共八位)存储规范化的浮点数的实际指数部分E,但是这里存储的不是E本身,而是存储的
E
+
127
E+127
E+127 的值,即
E
′
=
E
+
127
E^{'}=E+127
E′=E+127,细节会在后续说明。
F
R
′
FR^{'}
FR′(共23位)存储规范化浮点数的
∣
F
∣
|F|
∣F∣ 中的小数部分FR,即
F
R
′
=
F
R
FR^{'}=FR
FR′=FR。这是由于在规范化的形式下二进制浮点数的F中小数点前一位为固定一位的非零整数1,因此就没有存储这一位而只存储了FR,这样也节省了一个比特位的存储空间。当然这里没有考虑到一些特别的数,接下来会详细说明。
这里比较特别的一点是,虽然实际的指数E可正可负,但是作为存储指数部分的E的 E ′ E^{'} E′是被当做一个无符号数,因此 E ′ E^{'} E′的范围是0到255,也就是说这里存储的并不是指数的实际值。这256个值又可以分为三个部分来说明:
- 1 < = E ′ < = 254 1<=E^{'}<=254 1<=E′<=254,此时实际表示的数为 N = ( − 1 ) S × 1. F × 2 E ′ − 127 N = (-1)^S × 1.F × 2^{E^{'}-127} N=(−1)S×1.F×2E′−127且是以规范化形式表示的,符号位表示数的符号(正或负),小数部分被规范化为默认小数点前有一个非零的默认值1,存储的指数值是实际的指数值加上127,因此实际表示的指数值是 [ − 126 , 127 ] [-126,127] [−126,127],这是为了可以表示正指数和负指数。
-
E
=
0
′
E = 0^{'}
E=0′, 此时实际表示的数为
N
=
(
−
1
)
S
×
0.
F
×
2
−
126
N = (-1)^S × 0.F × 2^{-126}
N=(−1)S×0.F×2−126,这时可以表示4种特别的数,且默认F中小数点前一位为0而不是1,实际的指数E为-126:
- 正0: S = 0 , F R = 0 S=0,FR=0 S=0,FR=0。
- 负0: S = 1 , F R = 0 S=1,FR=0 S=1,FR=0。
- 接近0的很小的正数: S = 0 , F R (不全为 0 ) S=0,FR(不全为0) S=0,FR(不全为0)。
- 接近0的很小的负数: S = 1 , F R (不全为 0 ) S=1,FR(不全为0) S=1,FR(不全为0)。
-
E
′
=
255
E^{'}=255
E′=255:此时用来表示正无穷大,负无穷大以及非数:
- 正无穷大: S = 0 , F R = 0 S=0,FR=0 S=0,FR=0。
- 负无穷大: S = 1 , F R = 0 S=1,FR=0 S=1,FR=0。
- 非数NaN: S = 1 或 0 , F R (不全为 0 ) S=1或0,FR(不全为0) S=1或0,FR(不全为0)。
多嘴一下,固定的比特位数能够表示的数时有限的,但是任一区间中的实数都是无限的,因此有些实数只能以近似的表示,这样就会出现精度缺失。
#####二进制浮点数在内存中的存储形式实例(32位,单精度)
- 18.6875D = 10010.1011B= ( − 1 ) 0 ∗ 1.00101011 × 2 4 (-1)^0*1.00101011×2^4 (−1)0∗1.00101011×24 , S ′ = 0 , F R ′ = 00101011 , E = 4 = 131 − 127 , = > E ′ = 131 = ( 10000011 ) 2 S^{'}=0,FR^{'}=00101011,E=4=131-127,=>E^{'}=131=(10000011)_2 S′=0,FR′=00101011,E=4=131−127,=>E′=131=(10000011)2。
- 负0: S ′ = 1 , F R ′ = 00000000000000000000000 , E = − 126 , E ′ = 00000000 S^{'}=1,FR^{'}=00000000000000000000000,E=-126,E^{'}=00000000 S′=1,FR′=00000000000000000000000,E=−126,E′=00000000。此时默认小数点前的一位整数为0。
- 正无穷大 : S ′ = 0 , F R ′ = 00000000000000000000000 , E ′ = 11111111 S^{'}=0,FR^{'}=00000000000000000000000,E^{'}=11111111 S′=0,FR′=00000000000000000000000,E′=11111111 ,这一类数就没有什么实际指数E以及小数点前的一位整数的问题。
#####二进制浮点数在内存中的存储形式实例(64位,双精度)
64位双精度浮点数的存储格式如图6所示:
从图6可知64位双精度浮点数的存储格式与32位单精度的存储形式类似,只是存储各部分的比特位的个数不同。还有处理实际指数值E的偏执值也不同,在32位单精度时是127,在64位双精度时是1023。 S ′ S^{'} S′(共一位)存储S的值,表示浮点数的正负,0代表正数,1代表负数,即 S ′ = S S^{'}=S S′=S。 E ′ E^{'} E′(共11位)存储规范化的浮点数的实际指数部分E,但是这里存储的不是E本身,而是存储的 E + 1023 E+1023 E+1023 的值,即 E ′ = E + 1023 E^{'}=E+1023 E′=E+1023。 F R ′ FR^{'} FR′(共52位)存储规范化浮点数的 ∣ F ∣ |F| ∣F∣ 中的小数部分FR,即 F R ′ = F R FR^{'}=FR FR′=FR。
#####常用字符编码
-
7比特ASCII编码(又名:US-ASCII, ISO/IEC 646, ITU-T T.50)
-
ASCII编码最早使用7个比特位来表示128个字符,现在已被扩展为8个比特位(在早期的电脑上第八个比特位用来作为奇偶校验之用)。
-
32D-126D是可打印的字符。0D-31D和127D是特殊的不可打印的控制字符。这些不可打印的字符许多在早期被用来进行传输控制,现在许多已经不再使用
-
8比特Latin-1(又名 ISO/IEC 8859-1)
-
Latin-1也是8比特编码,其向下兼容ASCII编码。即其0D-127D编码与ASCII编码相同。其编码128D-159D没有分配,编码160D-255D有分配字符。ISO/IEC 8859标准其实是一系列8比特编码标准,一共有16个部分,第十二部分现在已经被丢弃。Latin-1属于其第一部分。每一部分除了160D-255D处分配的字符不同,其它编码分配的字符都与Latin-1相同,十六个部分的160D-255D这一段编码分别对应了西欧不同国家的字符。
-
其它对ASCII编码进行扩展的8比特编码
-
ANSI (American National Standards Institute) (又名Windows-1252或者Windows 代码页 1252)。其基本与ISO/IEC 8859-1相同,只不过它对ISO/IEC 8859-1没有分配的128D-159D这一段进行了分配,即分配了一些字符。
-
EBCDIC (Extended Binary Coded Decimal Interchange Code)主要用在早期的IBM电脑上。其先于ASCII编码出现。EBCDIC编码具有英文字母的不连续性以及中间出现多次断续的一些不便利性。ASCII编码参考了EBCDIC编码,且消除了这些不便利性。
-
Unicode 编码(又名ISO/IEC 10646通用字符集) :Unicode 编码出现之前没有没有一个单一的字符编码方案可以表示所有语言中的字符。而Unicode 编码正是为了这样的目的而出现的。Unicode 编码向下兼容ASCII编码和Latin-1编码。即前128个字符编码与ASCII编码相同,前256个字符编码与Latin-1编码相同。Unicode 编码有两种编码方案:
-
UCS-2(两个字节): 一共可以表示65536个字符,但是其中有些16位比特组合没有被赋予字符。 UCS-2里面所包含的字符覆盖了当前在使用的主要语言的所有字符。这一部分被称作Basic Multilingual Plane (BMP)。UCS-2目前已经过时。
-
UCS-4(4个字节):包含Basic Multilingual Plane (BMP,0000H-FFFFH)以及不常用的一些字符(Supplementary Characters,1FFFFH-10FFFFH)。
-
UTF-8编码:当文本中包含大量的ASCII字符时,unicode的2字节或4字节编码效率太低且浪费空间。UTF-8编码使用1-4个字节来表示Unicode 编码中对应的一个字符。变长编码方案可以改善效率太低且浪费空间的这种情况。
-
UTF-16编码:与UTF-8编码类似,也是针对unicode的变长编码方案。只不过2-4字节。
-
UTF-16编码:同UCS-4。
-
BOM (Byte Order Mark)是一个特殊的unicode编码(FEFFH)字符,它用来区分多字节字符(unicode)文本文件内存存储的大端与小端格式。若BOM在内存中的存储格式为FFFEH,则为小端模式。若BOM在内存中的存储格式为FEFFH,则为大端模式。(假设地址从左到右上升)
-
字符编码方案在windows中称为代码页(codepage)
-
中文字符集:
-
GB2312/GBK:GB2312使用两个字节来表示简体中文字符。GBK是对GB2312的扩展,同时包含了更多字符和繁体中文字符。
-
BIG5使用两个字节来表示繁体中文字符,它和GBK不兼容。即同样的编码表示不同的字符。
更多字符编码相关内容可以参考:
https://www.crifan.com/files/doc/docbook/char_encoding/release/htmls/ch01_enc_background.html
其它参考博客:
http://www.cnblogs.com/lds85930/archive/2007/09/19/897912.html
http://blog.csdn.net/zhaoshuzhaoshu/article/details/37600857/
http://blog.csdn.net/chy555chy/article/details/52065250