写一篇逻辑严密的文章,涉及到的东西太多,实在很困难,所以某些方面就不深究下去,仅做一个知识点的粗浅归纳。

关于浮点数

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; 这两个数是不是相等。如果相等,证明我的理解是正确的。

浮点数的阶值(初步了解一下浮点数)(1)

说明我的猜测是正确的。

几个例子

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个字节的内存值是:

浮点数的阶值(初步了解一下浮点数)(2)

我的电脑是Intel处理器,数据在内存中是以小尾形式保存的,即数据的高字节保存在内存的高地址处;数据的低字节保存在内存的低地址处。

因此,从地址0x0012FEA8开始的8个字节,组成的数据是0x400921CAC083126F

,