浮点数的大小比较为何不能用等号?

  前些天参加深信服面试,面试官问了这样一个问题:浮点数的大小比较为何不能用等号?那时就没回答好,由于本身一直把“浮点数大小比较不可以用等号”当作默认事实而没有去深究其背后原理。本文将试着解释这个事实。ios

小数在计算机中的表示

  计算机中是如何存储和表达数字的?对于整数,状况比较简单,直接按照数学中的进制转换方法处理便可,即连续除以2取余。这并非难点,真正的难点在于小数是如何转换为二进制码(即浮点数)的(注意区别小数和浮点数)。面试

  固然,从数学的角度来说,十进制的小数能够转换为二进制小数(整数部分连续除2,小数部分连续乘2),例如125.125D=1111101.001B,但问题在于计算机根本就不认识小数点“.,更不可能认识1111101.001B。那么计算机是如何处理小数的呢?缓存

  历史上计算机科学家们曾提出过多种解决方案,最终得到普遍应用的是IEEE 754标准中的方案,目前最新版的标准是IEEE std 754-2008。该标准提出数字系统中的浮点数是对数学中的实数(小数)的近似,同时该标准规定表达浮点数的0、1序列被分为三部分(三个域):优化

  

  以32位单精度浮点数为例,其具体的转换规则是:首先把二进制小数(补码)用二进制科学计数法表示,好比上面给出的例子1111101.001=1.111101001*2^6。符号位sign表示数的正负(0为正,1为负),故此处填0。exponent表示科学计数法的指数部分,请务必注意的是,这里所填的指数并非前面算出来的实际指数,而是等于实际指数加上一个数(指数偏移),偏移量为2^(e-1)-1,其中e是exponent的宽度(位数)。对于32位单精度浮点数,exponent宽度为8,所以偏移量为127,因此exponent的值为133,即10000101。以后的fraction表示尾数,即科学计数法中的小数部分11110100100000000000000(共23位)。所以32位浮点数125.125D在计算机中就被表示为01000010111110100100000000000000。spa

  对于32位单精度浮点数,sign是1位,exponent是8位(指数偏移量是127,fraction是23位。对于64位双精度浮点数,sign是1为,exponent是11位(指数偏移量是1023,fraction是52位。.net

  用程序能够看出小数在计算机中是如何表示的(即浮点数):3d

 1 #include <iostream>
 2 #include <bitset>
 3 using namespace std;
 4 
 5 int main()
 6 {
 7     float input;
 8     cin >> input;
 9     unsigned long long nMem = *(unsigned long long *)&input;
10     bitset<32> myBit(nMem);
11     cout << myBit << endl;
12 
13     return 0;
14 }

  程序运行结果:code

  

浮点数的大小比较为何不能用等号?

  下文将从几个方面来探索这个问题:blog

浮点数精度不一样

  请见程序:ip

 1 #include <iostream>
 2 #include <cmath>
 3 using namespace std;
 4 
 5 int main()
 6 {
 7     float a = (float)0.1;
 8     float b = (float)0.1;
 9     if(a == b)
10         cout << "a == b" << endl;
11     else
12         cout << "a == b" << endl;
13 
14     float c = (float)0.1;
15     double d = (double)0.1;
16     if(c == d)
17         cout << "c == d" << endl;
18     else
19         cout << "c != d" << endl;
20 
21     float e = (float)0.1;
22     float f = (double)0.1;
23     if(abs(e - f) < 0.0001)
24         cout << "e == f" << endl;
25     else
26         cout << "e != f" << endl;
27 
28     return 0;
29 }

  程序运行结果以下:

  

  从该例子能够看出,对于同一个小数,当用不一样精度表示时,结果是不同的,不能直接用等号比较大小。

寄存器与内存表示浮点数精度不一样

  按照博文计算机中基本类型float值表示和大小比较问题的说法: 

  即便在精度相同的状况下,比较也可能会出问题。由于在运算过程当中会将内存(或高速缓存)中的值加载到CPU浮点寄存器(80 bit扩展精度)中,而后再进入CPU浮点计算单元进行计算,计算结果写回浮点寄存器,而后写回内存(或高速缓存)。从内存到浮点寄存器,浮点数的精度会扩展,从浮点寄存器到内存,浮点数的精度会下降(精度扩展一般没问题,但若是精度下降了,极可能值会发生变化,出现截断),而浮点运算的结果因为下面还要使用因此暂时保存在浮点寄存器中留待下次使用(没有及时写回内存,这是一种优化策略),从而致使数据并非内存中和内存中的数据比较而是浮点寄存器中的值和内存中的值进行比较,而不管内存中是float类型仍是double类型,其精度和浮点寄存器精度都不相同,从而致使比较结果是不相等。  

  详细的可参考维基百科条目Extended precision

参考资料

  浅谈计算机中浮点数的表达方法(IEEE 754)

  计算机中基本类型float值表示和大小比较问题

相关文章
相关标签/搜索