浮点数类型在计算机里面的表示方法

上次在校园招聘的时候,问了几个学生一个关于浮点数计算的问题,就是下面的代码为什么第一行返回false,而第二行和第三行都返回true

 

        Console.WriteLine("1.123f + 1.345f == 2.468f ? {0}",

            1.123f + 1.345f == 2.468f); // False

        Console.WriteLine("1.123f + 1.344f == 2.467f ? {0}",

            1.123f + 1.344f == 2.467f); // True

        Console.WriteLine("1.123 + 1.345 == 2.468 ? {0}",

            1.123 + 1.345 == 2.468); // True

 

我们知道,integer类型占用的是4个字节,可以表示的数字范围在2**-322**32之间.而且每一个整数在计算机里面都有相应的内存表示方法,例如数字12345的表示方法就是:

00000000 00000000 00110000 00111001

即根据二进制到十进制的转换方法: 12345 = 213 + 212 + 25 + 24 + 23 + 20

 

而在计算机float,这种表示方法最大的问题就是如何在二进制里面保存小数点的位置--因为小数点的位置不固定.于是计算机专家们就想到了用科学计数法的方式来表示浮点数,因为在科学计数法里面,小数点的位置总是固定,就是在第一个数字的后面,例如12345的科学计数法的表示为1.2345 * 105。而0.012345的科学计数法表示方式为1.2345 * 10-2.

 

使用公式来表示的话,科学计数法的公式是 

 

因为我们的计算机比较笨,只能处理01,所以在计算机里面表示浮点数的时候,上面的公式中的基数b2,而不是10.在计算机内存当中,保存的实际是浮点数的计算公式,而不是确切的值,所以说计算机里面浮点数都是近似值,而不是确切的值.在计算机中,float类型为例,内存中32个位所代表的内容分别是:

SEEEEEEE EMMMMMMM MMMMMMMM MMMMMMMM

其中S代表符号位,1代表这个浮点数是一个负数,而0则表示是正数。而E就是科学计数法里面的指数e,由于 e既有可能是正数,也有可能是负数,因为1234.5的科学计数法是1.2345 * 104,0.12345的科学计数法是1.2345 * 10-1。所以e的计算规则是EEEEEEE E表示的数减去27- 17是因为我们有8个位来表示指数),这样8个表示指数的位就可以用来表示负数和正数了。

 

M就是用来计算科学计数法里面的m,计算规则是,从左往右开始,第一个M代表2-1,第二个M表示2-2,依次类推。由于m要么就是大于1的小数,要么就是小于1的小数(记住,我们计数法里面的基数是,而不是10),如果e的值大于0,那么我们就可以加上这个1,如果e的值小于0,那么我们可以省略这个1,这是因为我们总是可以通过调整e的值来做到这一点。因此m里面小数点前面的1被省略掉了,这个省略掉的1可以根据e的值来推算出m的小数点前面到底有没有这个1

 

而对于double类型来说,在计算机中各个位表示的信息如下所示:

SEEEEEEE EEEEMMMM MMMMMMMM MMMMMMMM MMMMMMMM MMMMMMMM MMMMMMMM MMMMMMMM

现在我们再回过头来看看上面的代码:

        double a = 1.345f;

        double b = 1.123f;

        double result = a + b;

        double expected = 2.468f;

 

        Console.WriteLine("result == expected ? {0}", result == expected);

调试这个程序,在内存中看一看a的值是3ff5851ec0000000(为了说明方便,我将文章开头的程序改头换面了一下),在计算器里面将这个十六进制的值换算成二进制的是:

11111111110101100001010001111011000000000000000000000000000000

 

因为计算器会将前缀零省略掉,因此上面的值实际上是:

00111111 11110101 10000101 00011110 11000000 00000000 00000000 00000000

 

由于e部分的值大于等于0,因为127 – (27 – 1) = 1,所以我们在计算m的时候需要加上隐含的1,也就是说上面黄色背景的值实际上表示的是:

 

(十进制的)1.0 + 0101 10000101 00011110 11000000 00000000 00000000 00000000所表示的小数

= 1.0 + 2**-2 + 2**-4 + 2**-5 + 2**-10 + 2**-12 + 2**-16 + 2**-17 + 2**-18 + 2**-19 + 2**-21 + 2**-22

= 1.0 + 0.25

      + 0.0625

      + 0.03125

      + 0.0009765625

      + 0.000244140625

      + 0.0000152587890625

      + 0.00000762939453125

      + 0.000003814697265625

      + 0.0000019073486328125

      + 0.000000476837158203125

      + 0.0000002384185791015625

= 1.0 + 0.3450000286102294921875

= 1.3450000286102294921875

 

因此按照上面的算法,b的值实际上应该是1.1230000257492065,因此a + b实际上是2.468000054359436,而上面的程序expected的实际值应该是2.4679999351501465,这就是为什么上面1.345f + 1.123f != 2.468f的原因。

 

1.344f + 1.123f == 2.467f,纯粹是巧合,因为前两者相加的值恰好等于后者在计算机里面的表现形式。

 

最后洋洋洒洒一大篇的结论就是,浮点类型不能用==号来判断,因为浮点数是一个近似值,只能通过两者相减小于一个可以接受的误差来判断。



本文转自 donjuan 博客园博客,原文链接:http://www.cnblogs.com/killmyday/archive/2009/03/22/1419079.html   ,如需转载请自行联系原作者

上一篇:Java浮点数精确计算


下一篇:“共携手,趣挑战!”——阿里云开发者能力评测团队排位赛正式开赛!