0%

【专题】那些被忽略的细节

关于浮点数

请先阅读下面的程序,并猜测输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <cmath>
const double eps = 1e-6;
double x = 5.2, y = 5.1 + 0.1;
int main() {
printf("%d\n", x == y);
printf("%d\n", x < y);
printf("%d\n", x > y);
printf("%e\n", x - y); // %e 是科学计数法的意思
printf("%d\n", fabs(x-y) < eps);
printf("%d\n", x > y + eps);
return 0;
}

大多数同学预测的输出应该是:

1
2
3
4
5
6
1
0
0
0
1
0

然而,正确的输出应该是:

1
2
3
4
5
6
0
0
1
8.88178e-16
1
0

是不是令你大吃一惊呢?让我来解释一下这个现象的原因。说到底,就是一个精度问题。

系统中的数据都是以二进制形式存储的。

例如:\(114514 = 2^{1}+2^{4}+2^{6}+2^{8}+2^{9}+2^{10}+2^{11}+2^{12}+2^{13}+2^{15}+2^{16}\)

在存储小数的时候,可能会遇到精度不够的问题。这就导致了在存储时与原数会有一点点的差距。差距一般在 \(0.000001\) 左右。所以,当我们想要判断两个浮点数是否相等时,只需判断两数之差是否小于 \(0.000001\) 即可。如果小于,则判断两个浮点数相等(如前面的程序的第 \(10\) 行所示)。

同理,我们可以推出 \(>\ <\ \leq\ \geq\) 的判断式(\(eps=10^{-6}\)):

判断内容 具体写法
判断浮点数 == fabs(x-y) < eps
判断浮点数 > x > y + eps
判断浮点数 < x < y - eps
判断浮点数 >= x > y - eps
判断浮点数 <= x < y + eps

关于输出

输出,那各位可再熟悉不过了。可别小看了输出,其中依然有坑。

常见的输出函数有四种:coutprintfputsputchar。关于它们的用法,请出门左转到 CSDN。

我们还是先来看一段程序。这段程序从是我的 这篇博客 中摘录的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
using namespace std;
const int N = 32;
int n, a[N];
bool isFirst = true;
string s[N] = {"", "11/(45-1)*4", "-11+4-5+14", "-11-4+5+14", "11-4+5/1-4", "11-4-5+14", "1*1+45-14", "11*4+5*1*4", "1+1+(4+5)*14", "(114-51)*4+(-11-4+5+14)", "1+1-4+514", "1*(1+4)*51*4+(-11-4+5+14)", "-11+4*514+(11*-4+51-4)", "(1+1)*451*4+(11*(45-1)+4)", "114*5*14+((1+1)*4+51*4)", "1145*14+((11-4)*51-4+(11/(45-1)*4))", "114*51*4+((1+1)*4514+(11*45-14+(11*-4+51-4)))", "114*514+(11*45*14+(-11/4+51/4))", "114514+(1145*14+(1*14+514))", "114514*(-11+4-5+14)+(114*51*4+((1+1)*4514+(11+4*51*4+(11-4*5+14))))", "114514*(-11-4+5+14)+(114*514+(1+14*514+((1+145)*-(1-4)+(11/(45-1)*4))))", "114514*(11-4+5+1-4)+(1145*14+((11+451)*4+(-1-1+4+5*14)))", "114514*(1+1+4*5*1-4)+(114*51*4+(11451+4+(1145+14+(1*-1+45-14))))", "114514*(11+4*5+1+4)+(114*514+(11451+4+(114*-5*(1-4)+(-11+45+1+4))))", "114514*(1*14*5-1+4)+(114*51*4+(114*51+4+(-11+4+5+14)))", "114514*(11+45*-(1-4))+(11*4514+(114*5*14+(1+14+514+(11-4+5+1-4))))", "114514*(11+4*5*14+(-11+4-5+14))+(114*(5-1)*4+(1-14+5+14))", "114514*(114*5+14+(-11+4-5+14))+((1+1)*451*4+(11+45/1-4))", "114514*(1145+14+(1*14-5/1+4))+(1+14*514+(114-5+14))", "114514*(114*5*1*4+(11*4+5*1*4))+(1+14514+(-1-14*(5-14)))", "114514*((1145+1)*4+(114-5-1-4))+(114*51*4+(114*51+4+(11*4*5-14)))", "114514*((1+1)*4514+(11*(45-14)+(11-4+5-1-4)))+(11*4514+(114*5*14+(-(1-14)*5*14+(11-4-5+14))))"};
void print(int x) {
if (isFirst) {
isFirst = false;
} else {
if (s[x][0] != '-') cout << '+';
}
cout << s[x];
}
void BinarySplit(int x) {
for (int i = 30; i >= 0; i--)
if ((1<<i) & x)
print(i+1);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n;
BinarySplit(n);
return 0;
}

这是正解程序。但是,一开始,我的 print 函数是这样写的:

1
2
3
4
5
6
7
8
void print(int x) {
if (isFirst) {
isFirst = false;
} else {
if (s[x][0] != '-') putchar('+'); // 区别在这
}
cout << s[x];
}

乍一看,似乎没有什么区别。但当我随便输入一个数(例如 \(114514\))时,输出是这样的:

1
++++++++114*514+(11*45*14+(-11/4+51/4))114*51*4+((1+1)*4514+(11*45-14+(11*-4+51-4)))114*5*14+((1+1)*4+51*4)(1+1)*451*4+(11*(45-1)+4)-11+4*514+(11*-4+51-4)1*(1+4)*51*4+(-11-4+5+14)1+1-4+514(114-51)*4+(-11-4+5+14)11*4+5*1*411-4-5+14-11+4-5+14

我调了半天,也不知道哪里出了问题,索性把 putchar 改成了 cout。结果发现,问题竟然解决了!

1
114*514+(11*45*14+(-11/4+51/4))+114*51*4+((1+1)*4514+(11*45-14+(11*-4+51-4)))+114*5*14+((1+1)*4+51*4)+(1+1)*451*4+(11*(45-1)+4)-11+4*514+(11*-4+51-4)+1*(1+4)*51*4+(-11-4+5+14)+1+1-4+514+(114-51)*4+(-11-4+5+14)+11*4+5*1*4+11-4-5+14-11+4-5+14

当时我就懵了。怎么会这样呢?这个问题我到现在也没搞懂。如果有大神知道原因,请不吝指教,谢谢!

总之,这个例子说明了:千万不要把各种输出混用。C 风格就全程 C 风格,C++ 就全程 C++