起因是当我想要计算浮点数的小数位位数(利用当浮点数num减去其整数位 )
我的想法是先分离出小数位,然后每次循环给小数位乘上10
,直到不存在小数位时,就会满足当num - (int)num == 0
通过这种方式就可以得到小数位的长度
#include <iostream>
using namespace std;
int main(){double num = 12.34;num = num -(int)num;int len = 0;while(num - (int)num != 0){//当该数减去该数整数位为0时停止循环cout << num << ' ' << (int)num << ' ' << (double)(num - (int)num) << endl;num *= 10;len++;}cout << len << endl;
}
结果出现如下情况且无限循环
该浮点数和该数的整数位相减始终无法得到0
,在后续将浮点数进行强制类型转换时发现结果会比预期值少1
经过查阅发现:这是因为浮点数精度丢失导致的(下面我将尽可能详细解释该现象)
第一方面:计算机中数据的存储
首先我们需要知道,在计算机中,所有数据都是以二进制的形式存储的:
如我们所知,在10
进制中存在不能精确表示的一些数(无限循环小数)如0.333333...
同理,用二进制数也无法精确表示某些十进制数(其实通常的十进制数都不能用二进制精确表示)
如1.234
用二进制表示为整数位是1
,小数位是00111011111001110110110......
这也造成了浮点数在计算机能是无法精确表示的
但是也有部分可以精确表示的浮点数,如小数点后为0.5
, 0.25
, 0.125....
,如下:
先是浮点数强制类型转换会少一的问题,这个很好解决,如这样(int)(num + 0.5)
,加0.5
即可解决,但是仍然无法解决相减始终无法为0
的问题,如下图:
即是看起来整数位对上了,但是结果有多出了很多莫名其妙的很小的小数,这也说明浮点数在计算机中是无法精确表示的。下面我将解释这些很小的小数为何物。
第二方面:C++浮点数存储格式
C++的浮点数是采用IEEE浮点格式进行存储的
什么是IEEE浮点格式呢?
其实就是对浮点数在内存中进行存储的规范。这里就不详细说明(因为我也不太懂),如果有想了解的可以查阅以下链接:
为何浮点数可能丢失精度
Microsoft-IEEE 浮点表示形式
如果希望深一步了解,特别是关于舍入与计算误差方面,推荐阅读著作:
《Sun Studio 11:数值计算指南》
而利用进制转换计算器,我们可以得到这些小数的IEEE标准表示,转换器链接在此:进制转换器
无论是4个字节的float类型还是8
个字节的double类型,在其IEEE标准下的十进制表示中,都不是单纯的12.34
,而是在其后方有很多很多无法看出规则的小数位,之前浮点数减去整数位所得很小的小数也属于该部分。
总结
出现无法精确相减的原因便是:浮点数存在丢失精度的情况,无论如何都无法得到一个浮点数精确的小数位位数。
但是我们可以通过人为设置一个精度完成我们想要的功能,如下:
#include <iostream>
using namespace std;
int main(){double num = 12.345;int Accuracy = 100000;//设置精度为十的5次方num -= (int)num; //先只保留小数位num *= Accuracy; //用x存储将小数位乘上设置的精度,因为只有整型才能取余//0.345会变成34500, 接着将零去掉,即可得到小数长度int len = 5;while((int)(num + 0.5) % 10 == 0){num /= 10;len--;}cout << len << endl;
}