首先,让我们先来看一段代码:
#include <iostream> #include <iomanip> int main() { using namespace std; cout<<setprecision(17); float num1 = 1.1; double num2 = 1.1; if (num1 == num2) cout << "yes"<<endl; else cout << "no"<<endl; cout<<num1<<";"<<num2<<endl; return 0; }
代码很简单,比较下num1和num2是否相等,那么是否相等呢?看字面值是一样的,理论上确实应该相等,但实际上却不是。为什么?这里涉及到了一个浮点型数据的精度和表示问题。
好,我们就来谈谈这两个问题。
1. 精度(Precision)
精度可以理解成在不丢失数据的前提下,可以存储多少位数据的能力。比如,1/3这个分数,如果用小数表示应该是0.333333...的无线循环,因此若想在内存中表示这个数据,需要无限的存储空间,这显然是不可能的。我们常见的float和double型分别是4 bytes和8 bytes,都是只能存储有限位的小数,float和double分别能表示6-7个小数位和15-16个小数位。
2. 进位(Rounding)
这里的进位问题是值10进制转化成2进制的过程。我们都知道十进制整数转化成二进制的方法是不断的除以2,直到除尽为止,而十进制小数转化成二进制则是不断的乘以2,直到值为1为止。这里面有个问题,整数不断的除以2,总有最后为0的时候,但小数乘以2,却不一定得到1,也就是说,并不是所有十进制小数转成有限的二进制数据,而因为上面提到的精度问题,因此,会出现精度缺失问题,这种情况,我们称之为“进位错误”,英文是Rounding error。
基于以上两点,上面那段代码就不难理解了。
num1和num2虽然字面值一样,但是在内存中的存储数据完全不同,在存储1.1的过程中,精度缺失和进位错误同时出现,因此num1和num2做==比较的时候,遵循精度保留原则,num1会被编译器premote成double类型,但由于本身精度是7位小数,强制提升为15位小数后,后面补位的数字都是随机的,这点可以在最后的输出中可以看到(为了以便观察,输出的精度设了17位)。
那么,如何解决这种问题?
1. 可以设定一个可接受的误差值,当误差小到一定程度的时候,就可以忽略了。譬如,abs(num1-num2)<0.00001。
2. 可以给高精度类型强制降精度,比如用这种方式:num1 == static_cast<float>(num2)。但注意,不要给低精度类型强制提升精度,如果这样就等于和编译器做的一样了,结果也是一样的。