文章目录
- [1.3 浮点数与算术运算](#1.3 浮点数与算术运算)
1.3 浮点数与算术运算
在 C++ 中,我们将小数称为浮点数,这是因为小数点是浮动存储的。另外还有一种小数点固定存储的方式,称为定点数,多用在保留小数位数的时候。
【D1141 [22 年 3 月一级] 双精度浮点数的输入输出】输入一个双精度浮点数,保留 8 位小数,输出这个浮点数。
C++ 中存储双精度浮点数 的类型是 double,在内存中占用 8 个字节,能够表示的数据范围约为 ± 2.2 × 10 − 308 ∼ ± 1.7 × 10 308 ±2.2×10^{-308}\sim ±1.7×10^{308} ±2.2×10−308∼±1.7×10308,并保证小数点后约 15 ∼ 16 15\sim 16 15∼16 位的准确值。另有一种占用 4 个字节的 float 型,即单精度浮点型 ,数据范围约为 ± 1.17 × 10 − 38 ± 3.4 × 10 38 ±1.17×10^{-38}~±3.4×10^{38} ±1.17×10−38 ±3.4×1038,精度约为小数点后 6 ∼ 7 6\sim 7 6∼7 位。
C++ 中保留小数位数需要用到格式控制工具,这些工具都声明在 iomanip 这个头文件中。首先需要用 fixed 这个工具将浮点数转换为定点数,然后通过 setprecision 这个工具来控制小数位数。下面看完整代码,注意 fixed 和 setprecision 的用法形式。
cpp
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
double f;
cin >> f;
cout << fixed << setprecision(8) << f << endl;
return 0;
}
比较方便的是,一旦在程序中使用了 fixed 这个工具,那么之后的浮点数在输出时都会自动转换为定点数,无需重复设置。
由于 C++ 兼容绝大部分 C 语言的语法特性,下面介绍一种 C 语言中用于输出的工具 printf,在控制小数位数时是很方便的。这个工具包含在头文件 cstdio 中,先看代码。
cpp
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
double f;
cin >> f;
printf("%.8f", f);
return 0;
}
其中 %f 是用于输出浮点数的格式控制符,%.8f 表明输出时保留 8 位小数。格式控制符需要用双引号包裹起来,其作用是占位 和格式控制,表示用指定格式进行输出解析,在执行输出时会用具体数值进行替换。例如上面的程序执行输出时用变量f的值进行了替换。
【小贴士】C 语言早期的标准规定输出浮点数只能用 %f,之后为了便于程序员区分输出数据的类型,从 C99 开始引入了 %lf,其作用与 %f 完全一致。
不论哪种控制小数位数的方式,程序都会自动在下一位上进行四舍五入操作。
【P2252 [ABC117A] Entrance Examination】给出整数 t t t 和 x x x,求 t t t 除以 x x x 的值。保留到小数点后 10 位。
注意到 t t t 和 x x x 都是整数,如果直接做除法运算,得到的结果是整数商。C++ 要求参与算术运算的数据类型必须相同,两个不同类型的数据进行运算,编译器会自动将表示范围较小的类型转换为范围较大的类型。如果把输入看做一种运算,上面的规则就意味着我们可以使用浮点型变量来接收输入的整型数据。
cpp
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
double t, x;
cin >> t >> x;
printf("%.10f", t / x);
return 0;
}
注意到上面的代码中,执行 printf 时用表达式 t / x 的值替换了格式控制符 %.10f,这一点与 cout 是类似的,即 printf 可以输出任意符合格式控制符规则的表达式的值。
利用自动类型转换的规则,我们还可以在不改变变量类型的前提下,写出如下程序。
cpp
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int t, x;
cin >> t >> x;
printf("%.10f", 1.0 * t / x);
return 0;
}
其中 1.0 是浮点数,在与整数 t 相乘的过程中,编译器会自动将t转换为浮点型,于是 1.0 * t 的值是浮点型,再与 x 进行除法运算时,又将 x 转换为浮点型,最终得到小数除法的值。注意如果将 1.0 与分母 x 相乘,考虑到运算符的优先级,应当写作 t / (1.0 * x)。
下面我们通过一个例题来学习强制类型转换。强制类型转换允许我们在任意相似的数据类型之间相互转换,注意将范围较大的类型转为较小的类型会丢失精度。C++ 中的强制类型转换规则较为复杂,我们直接学习 C 风格类型转换,将复杂的事情交给编译器去做。
【D1114 [21 年 12 月一级] 输出整数部分】输入一个双精度浮点数 f f f,输出其整数部分。
cpp
#include <iostream>
using namespace std;
int main()
{
double a;
cin >> a;
cout << int(a);
return 0;
}
上面程序中变量 a 的括号也可以添加在类型标识符 int 上,即 (int)a、(int)(a) 也是合法的。类型标识符会将其后第一个表达式的值转换为目标类型,由此也可以看出变量 a 的括号并非必须的。事实上,C++ 程序会将整个括号看做一个表达式,其值就是括号中表达式的值,因此如果用强制类型转换解决上一个例题,在 t 和 x 都是 int 类型的情况下,应该写作 (double)t / x 或 t / (double)x,不能写成 (double)(t / x)。
需要注意的是,如果类型标识符是由多个关键字组成的,比如 long long 类型,那么就必须给类型标识符添加括号。此外,将浮点型转换为整型时并不会进行四舍五入,而是直接舍弃小数点及其之后的部分,称为截断。
【P2791 [ABC226A] Round decimals】给定一个实数 x x x,它最多可以用三位小数表示,并且输入时包含三位小数。将 x x x 四舍五入到最接近的整数并输出结果。
我们可以给 x x x 加上 0.5 0.5 0.5 之后,利用截断特性,完成四舍五入的操作,详见代码。
cpp
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
double x;
cin >> x;
cout << int(x + 0.5);
return 0;
}
【D1001 [20 年 9 月一级] 新冠疫情死亡率】2020 年全世界爆发了新冠疫情,请根据某个国家报告的新冠确诊数和死亡数,计算新冠疫情在该国的死亡率。以百分数形式输出,保留 3 位小数。
显然确诊数和死亡数均为整数,在保留 3 位小数时,将在第 4 位上进行四舍五入,这里我们将使用截断特性来完成这个任务。由于要以百分数形式输出,因此实际上需要在小数点后第 6 位上进行四舍五入。我们先对死亡率乘以 100000.0 100000.0 100000.0,再加上 0.5 0.5 0.5,然后转换为 int 型进行截断,最后除以 1000.0 1000.0 1000.0 即可。注意 printf 输出 % 时应该使用格式控制符 %%。
cpp
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int n, x;
cin >> n >> x;
printf("%.3f%%", int(100000.0 * x / n + 0.5) / 1000.0);
return 0;
}