浮点数类型在计算机⾥⾯的表⽰⽅法
上次在校园招聘的时候,问了⼏个学⽣⼀个关于浮点数计算的问题,就是下⾯的代码为什么第⼀⾏返回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**-32到2**32之间.⽽且每⼀个整数在计算机⾥⾯都有相应的内存表⽰⽅法,例如数字12345的表⽰⽅法就是:
00000000 00000000 00110000 00111001
即根据⼆进制到⼗进制的转换⽅法: 12345 = 213 + 212 + 25 + 24 + 23 + 20
⽽在计算机float型,这种表⽰⽅法最⼤的问题就是如何在⼆进制⾥⾯保存⼩数点的位置--因为⼩数点的位置不固定.于是计算机专家们就想到了⽤科学计数法的⽅式来表⽰浮点数,因为在科学计数法⾥⾯,⼩数点的位置总是固定,就是在第⼀个数字的后⾯,例如12345的科学计数法的表⽰为: 1.2345 * 105。⽽0.012345的科学计数法表⽰⽅式为: 1.2345 * 10-2.
使⽤公式来表⽰的话,科学计数法的公式是。
因为我们的计算机⽐较笨,只能处理0和1,所以在计算机⾥⾯表⽰浮点数的时候,上⾯的公式中的基数b是2,⽽不是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- 1(7是因为我们有8个位来表⽰指数),这样8个表⽰指数的位就可以⽤来表⽰负数和正数了。
⽽M就是⽤来计算科学计数法⾥⾯的m,计算规则是,从左往右开始,第⼀个M代表2-1,第⼆个M表⽰2-2,依次类推。由于m要么就是⼤
于1的⼩数,要么就是⼩于1的⼩数(记住,我们计数法⾥⾯的基数是2 ,⽽不是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,纯粹是巧合,因为前两者相加的值恰好等于后者在计算机⾥⾯的表现形式。
最后洋洋洒洒⼀⼤篇的结论就是,浮点类型不能⽤==号来判断,因为浮点数是⼀个近似值,只能通过
两者相减⼩于⼀个可以接受的误差来判断。