写一篇逻辑严密的文章,涉及到的东西太多,实在很困难,所以某些方面就不深究下去,仅做一个知识点的粗浅归纳。
关于浮点数
IEEE754标准规定:
浮点数的构成:1位符号位 N位阶码 M位尾数(原码表示)
单精度浮点数:1位符号位,8位阶码,23位尾数,共32位,占4个字节
双精度浮点数:1位符号位,11位阶码,52位尾数,共64位,占8个字节
长双精度浮点数:1位符号位,15位阶码,64位尾数,共80位,占10个字节
其中,阶码是由原码加上移码构成,所谓移码(exponential bias),值为2^(N-1)-1,如单精度时,移码为2^7-1=128
NaN表示not a number : 当阶码全为1,尾数不全为0,此时为NaN;这里又涉及到 quiet NaN和signaling NaN;如果尾数的首位是1,那么就是quiet NaN;如果尾数的首位是0,其余尾数有不为0,那么就是signaling NaN。这是 大多数处理器,包括Intel与AMD的x86系列、Motorola68000系列、AIMPowerPC系列、ARM系列、SunSPARC系列,采取的标准。这个标准被IEEE754采纳。
而 PA-RISC与MIPS处理器,采取了首位为'is_signaling'标记位,恰与上述标准相反。
那么,什么是quiet NaN和signaling NaN?
意思是:
当一个操作导致一个安静的NaN,没有指示任何异常,直到程序检查结果并看到一个NaN。也就是说,如果在软件中实现浮点,计算将继续进行,而没有来自浮点单元(FPU)或库的任何信号。信号NaN将产生一个信号,通常以FPU的例外形式。是否抛出异常取决于FPU的状态。
以上介绍了浮点数的NaN的定义,下面介绍 Inf和-Inf的定义。
Inf,即正无穷大:当符号位是0,阶码全为1,尾数全是0,此时为正无穷大;
-Inf,即负无穷大:当符号位是1,阶码全为1,尾数全是0,此时为负无穷大;
所以对于64位的双精度浮点数:
NaN 为:0xFFF8000000000000ULL
Inf为:0x7FF0000000000000ULL
-Inf为:0xFFF0000000000000ULL
下面继续问:
单精度,双精度,能表示的最小的小数是多少?我如果知道了每种精度能表示的最小的小数,那么就会很清楚的去选择正确的精度类型。
已知:float,单精度浮点数,1位符号位,8位阶码,23位尾数。
既然尾数有23位,那么能表示的最小的小数对应着23位尾数中,只有最后一位是1,其余全是0
即0.0000,0000,0000,0000,0000,001B ,这个二进制对应的小数是2^(-23),即0.000000119D
那么0.00000012D对应的二进制是多少呢?因为0.000000119D对应的二进制是0.0000,0000,0000,0000,0000,001B,这已经是最小的二进制数,当二进制增加1时,即
0.0000,0000,0000,0000,0000,010B,对应的小数已经是0.000000228D
而,0.00000012D介于0.000000119D和0.000000228D之间,所以0.00000012D的二进制仍旧是0.0000,0000,0000,0000,0000,001B
意思就是:小数点后第8位并不是有效位;那么小数点后第7位呢?
0.0000,0000,0000,0000,0000,001对应的十进制是0.0000001,
0.0000,0000,0000,0000,0000,010对应的十进制是0.0000002,
所以,float的有效位就是小数点后7位。
以上是从二进制的形式来分析数的有效位;
下面如果面对的是十进制数
比如:0.100000111D,与0.100000112D,这两个数,如果声明类型为float,那么这两个数是不是相等的?
首先,要对十进制数进行归一化为1.xxxxx的形式;
如果遇到整数部分不是1的,那么就会浮动小数点,得到1.00000111和1.00000112
当然小数点浮动了,阶码也改变了。这样得到的两个小数,他们的小数部分就是7位有效位。
所以1.00000111就是1.0000011;而1.00000112也是1.0000011,所以说0.100000111,与0.100000112是相等的。
那么0.00000011122233f 和 0.00000011122234f这两个数是不是相等的?
从表面看,虽然他们已经在小数点后14位才不一样,似乎他们应该是相等的。但其实,他们并不相等。因为虽然前面有那么多的0,但是这是浮点数,它会移动小数点,以达到最精简的表达这个数,归一化后为1.1122233f和1.1122234f,此时他们的小数点后是7为数字,不同的位置在第7位,所以计算机可以区分出这两个数是不同的,所以0.00000011122233f 和 0.00000011122234f这两个数是不相等的。
float x11=3.00000012f 和 float x22=3.00000010f这两个数是否相等?
答案是:
不相等。
虽然表明上看,这两个数的小数部分的前7位都是一样的,整数部分也一样,似乎应该是相等的。
x11=3.00000012f的小数部分明显大于2^(-23),也就是说尾数的第23位是1;x22=3.00000010f的小数部分明显小于2^(-23),也就是说尾数的第23位是0;
但是,他们的整数部分是3,即二进制11,归一化后的尾数的第1位被3的二进制的第0位的1占据。
这样,x11的归一化之前的尾数的第23位就被划在了归一化后的尾数的范围之外。也就是说x11和x22在归一化后,他们的尾数都是一样的了。可是为什么结果还是不相等呢?
原因在于x11归一化后,虽然尾数的第23位是0了,但是它的第24位是1,这个1等于了float的
机器ε(machine epsilon)的一半,所以不能舍去,于是向尾数的第23位进1,于是尾数的第23位并不是0,而是1。
至于什么是 机器ε, 机器ε是指最小的尾数,即尾数的第23位是1时的值。
如果上面的理解是正确的,那么如果我将x22的值,归一化之前的小数部分的尾数的第22位设置为1,这样当将整数部分的3归一化后,22位上的1变成了23位上的1,这样x22就与x11一样了。下面验证一下,是不是一样。
当22位上是1时,那么值就是2^(-22),即0.000000238,于是我程序比较
float x11=3.00000012f 和 float x22=3.00000024f; 这两个数是不是相等。如果相等,证明我的理解是正确的。
说明我的猜测是正确的。
几个例子
float x11=3.00000011f 和 float x22=3.00000012f是不相等的;
float x11=3.00000011f 和 float x22=3.00000010f是相等的;
请问,float x11=100.00000022f和float x22=100.00000011f是否相等。
100,划分为二进制是1100100,归一化后,它的100100归入了尾数中,导致归一化之前的尾数中的第22,23位都被分离了出去,原来的位17变成了现在的位23,并且原来的位18也是0,即现在的位24是0,所以24位及其后面的都被舍去。即x11和x22是相同的。
也就是说:当一个浮点数,整数部分的值很大时,这会占据小数部分的有效位。
另外,也可以这样理解:
x11=100.00000022f,归一化就是1.00 00000022f ;
x22=100.00000011f,归一化就是1.00 00000011f;
从小数部分数7位,发现以上都是一样的,所以x11和x22是相等的;
这就是规律。
即:先把数归一化,得到首尾是1的数,再判断它的小数部分,7位有效位,同时判断第8位的值是否超过机器ε的一半。若有,则有进位。否则,舍去。
对于double,双精度浮点数:
1位符号位,11位阶码,52位尾数,共64位。
那么它的机器ε就是:2^(-52),即0.000000000000000222
即双精度浮点数的有效位是小数点后16位。
下面,实际的计算一下,3.1415这个浮点数当类型是双精度时,在内存中的表示。
3换算为二进制就是11
0.1415换算为二进制就是0010,0100,0011,1001,0101,1000,0001,0000,0110,0010,0100,1101,1101{0...}
所以3.1415的二进制表示就是:
11. 0010,0100,0011,1001,0101,1000,0001,0000,0110,0010,0100,1101,1101{0...}
归一化为:
1.1 0010,0100,0011,1001,0101,1000,0001,0000,0110,0010,0100,1101,1101{0...}
因为归一化了,将整数部分的1划入了尾数,所以此时尾数是:
1 0010,0100,0011,1001,0101,1000,0001,0000,0110,0010,0100,1101,110{10...}
第53位是1,进1,得到:
1.1 0010,0100,0011,1001,0101,1000,0001,0000,0110,0010,0100,1101,111
尾数换算为16进制就是:921CAC083126F
阶码的原码是1,即2^1,因为这个是双精度浮点数,可知它的移码是2^(11-1)-1,即:
十进制数1023,换算为16进制数是3FF,加上阶1,得到400
所以3.1415,用双精度表示就是:
0 {11-1111-1111} {1 0010,0100,0011,1001,0101,1000,0001,0000,0110,0010,0100,1101,111}
从后往前,4bit一组,构成16进制,表示为:
0x400921CAC083126F
下面,用软件来验证3.1415的双精度浮点数,在内存中的值是不是 0x400921CAC083126F
unsigned long long a=0x0;
double b=3.1415;
a=*((unsigned long long *)(&b));
意思是:取得变量b的地址,将这个地址的类型转换为unsigned long long *,然后取得这个地址的内容,送给a
debug中断,查看&b的值是0x0012FEA8, 从这个地址开始的8个字节的内存值是:
我的电脑是Intel处理器,数据在内存中是以小尾形式保存的,即数据的高字节保存在内存的高地址处;数据的低字节保存在内存的低地址处。
因此,从地址0x0012FEA8开始的8个字节,组成的数据是0x400921CAC083126F
,