关于double转int时结果不一致问题

- 3 mins read

问题背景:

先来看一个案例:

#include <bits/stdc++.h>

using namespace std;

int main(void){
	double a; int b;
	a = 64.35;a*= 100;
	b = a; b %= 10;
	cout << b;
    return 0;
}

很显然,通过常理推断,这里的答案应该输出5,但是他却输出了4

我首先猜测是强制转换的问题,将第八行改成了b = (int)a测试运行之后竟然还是不对

解决:

float和double类型的主要设计目的是为了科学计算和工程计算。它们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。

然而,它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合。

float和double类型对于货币计算尤为不合适,因为要让一个float或者double精确地表示0.1(或者10的任何负数次方值)是不可能的,这种舍入错误产生的原因是浮点数实际上是用二进制系统实现的,而分数在二进制系统中没有精确的表示,其道理就如同在十进制系统中无法精确表示1/3一样;再比如0.5在二进制系统中有精确表示,而0.55在二进制系统中没有精确表示。

如上的情况,为了避免这个问题,另加上小数,如

b = a + 0.01

就可以使结果正确了。

原因分析:

首先,我们举出一个更方便理解的例子:我们如果去写一个代码来表示分数之间的加法,例如: $$ \frac 1 2 + \frac 1 3 = \frac 5 6 $$ 但是如果我们如下写代码:

int main(void){
	double a, b, c, d;
	a = 1;a /= 3;
	b = 1;b /= 2;
	d = 5;d /= 6;
	c = a + b;
	puts(c == d ? "true" : "false");
}

就会神奇的发现程序运行结果是false。这是为什么呢?这就需要考虑小数失真,为什么会失真?

首先我们看,代码运行完毕后,a的值应该是三分之一,但我们人类在进行表示的时候,只能表示出有限的位数,如零点三三循环,我们只能舍去精度后面的位数,这时,这个数字就会失真。

再想,计算机存储数据采用二进制,目前的浮点数采用符号位-指数-尾数的方法进行存储。在十进制中,我们如下表示一个小数: $$ a.bc = a\times10^0+b\times10^{-1}+c\times10^{-2} $$ 那么,如果是由01构成的二进制呢?我们大概只能这样: $$ a.b = m\times2^0+n\times2^{-1} $$ 这个时候就不难理解,我们表示0.5的时候,只需要像十进制一样保存二的负一次方就行了。但是如果想要表示0.1,就如同十进制中想要保存0.3一样,所以,当我们使用小数的时候,通常不去使用" == "(判等),而是去使用一个范围例如:(bool)(abs(a - b) <= 1e-7)此时,采用这句话,带入刚才的程序:

#include <cmath>

int main(void){
	double a, b, c, d;
	a = 1;a /= 3;
	b = 1;b /= 2;
	d = 5;d /= 6;
	c = a + b;
	puts((abs(c - d) <= 1e-7) ? "true" : "false");
}

运行结果就是true啦~

所以,为了避免以上情况再次发生,除了加上小数之外,我们还可以单独保存小数部分,将小数部分保存为整型,就可以解决很多困扰。