在 C++ 中,如果想要把带小数的浮点数变成整数,比如结账抹零或计算分页总页数,我们可以调用 <cmath> 头文件提供的取整函数来实现。
根据不同的业务需求,“取整” 的方向也是不一样的。<cmath> 头文件常用的取整函数有 4 个,它们对正数 3.7 和负数 -3.7 的处理结果如下表所示。
| 函数 | 英文原意与规则 | 输入 3.7 | 输入 -3.7 |
|---|---|---|---|
| std::floor(x) | floor(地板):向下取整,往更小的方向取 | 3 | -4 |
| std::ceil(x) | ceiling(天花板):向上取整,往更大的方向取 | 4 | -3 |
| std::round(x) | round(四舍五入):四舍五入到最近的整数 | 4 | -4 |
| std::trunc(x) | truncate(截断):直接抹去小数部分 | 3 | -3 |
向下取整与向上取整:floor() 和 ceil()
我们先来看方向相反的一对函数:std::floor() 和 std::ceil()。很多初学的小伙伴容易把这两个名字记混,其实结合英文原意就很好记:“地板(floor)” 在下面,“天花板(ceiling)” 在上面。
1. std::floor() 向下取整
在 C++ 中,我们可以使用 <cmath> 头文件中的 std::floor() 函数来向下取整,也就是沿着数轴往更小的方向找最近的整数。
语法:
std::floor(x)说明:
std::floor(x) 会返回小于或等于 x 的最大整数值,默认返回 double 类型。比如 std::floor(3.7) 的结果是 3。对于负数 -3.7,因为 -4 比 -3.7 更小,所以 std::floor(-3.7) 的结果是 -4。
2. std::ceil() 向上取整
ceil 是英文单词 “ceiling(天花板)” 的缩写。在 C++ 中,std::ceil() 与 std::floor() 正好相反,用于向上取整,也就是沿着数轴往更大的方向找最近的整数。
语法:
std::ceil(x)说明:
std::ceil(x) 会返回大于或等于 x 的最小整数值,默认返回 double 类型。比如 std::ceil(3.7) 的结果是 4。对于负数 -3.7,因为 -3 比 -3.7 更大,所以 std::ceil(-3.7) 的结果是 -3。
示例 1:使用 floor() 和 ceil() 取整
#include <cmath>
#include <iostream>
int main() {
double pos = 3.7;
double neg = -3.7;
std::cout << "floor(3.7) = " << std::floor(pos) << '\n';
std::cout << "ceil(3.7) = " << std::ceil(pos) << '\n';
std::cout << "floor(-3.7) = " << std::floor(neg) << '\n';
std::cout << "ceil(-3.7) = " << std::ceil(neg) << '\n';
return 0;
}运行结果如下。
floor(3.7) = 3
ceil(3.7) = 4
floor(-3.7) = -4
ceil(-3.7) = -3分析:
从运行结果可以看出,正数 3.7 向下取整得到 3,向上取整得到 4,这和我们的日常直觉完全一致。
初学者最容易出错的是负数 -3.7,千万不要把 std::floor() 的 “向下取整” 误解成 “直接去掉小数”。你可以想象一下温度计上的刻度:零下 3.7 度处于零下 4 度和零下 3 度之间,往更低的方向看是 -4,往更高的方向看才是 -3。
在实际开发中,如果结账活动需要 “抹零”,比如 99.8 元只收 99 元,我们就可以调用 std::floor()。如果是计算列表分页的总页数,比如每页显示 10 条记录,23 条记录除以 10.0 得到 2.3 页。多出来的 3 条记录也需要单独占一页,这时调用 std::ceil() 向上取整就能算出 3 页。
注意 在计算分页总页数时,如果总条数和每页条数都是 int 整数,千万不要直接写成 std::ceil(23 / 10)。在 C++ 的除法运算中,两个整数相除会先丢弃小数变成整数 2,我们应该写成 std::ceil(23 / 10.0) 先算出小数再取整。
四舍五入与向零截断:round() 和 trunc()
除了固定往小或往大取整之外,平时写代码更常遇到的是 “四舍五入” 和 “直接抹掉小数部分”,这时我们可以使用 std::round() 和 std::trunc() 函数。
1. std::round() 四舍五入
在 C++ 中,std::round() 实现的就是数学里最常用的四舍五入规则:看哪一个整数离当前数值最近,就取哪一个。
语法:
std::round(x)说明:
当小数部分小于 0.5 时,std::round(x) 会舍去小数;当小数部分大于 0.5 时,则会向整数部分进位。如果小数部分刚好等于 0.5,处于两个整数的正中间,它会统一往远离 0 的方向取整。
2. std::trunc() 向零截断
trunc 是英文单词 “truncate(截断)” 的缩写。在 C++ 中,std::trunc() 的规则非常干脆:不管正数还是负数,一律直接抹掉小数部分,只保留整数部分。
语法:
std::trunc(x)说明:
由于直接去掉了小数部分,std::trunc(x) 的结果总是向 0 的方向靠近。比如 std::trunc(3.7) 会得到 3,而 std::trunc(-3.7) 会得到 -3。
示例 2:使用 round() 和 trunc() 取整
#include <cmath>
#include <iostream>
int main() {
double pos = 3.7;
double neg = -3.7;
std::cout << "round(3.7) = " << std::round(pos) << '\n';
std::cout << "trunc(3.7) = " << std::trunc(pos) << '\n';
std::cout << "round(-3.7) = " << std::round(neg) << '\n';
std::cout << "trunc(-3.7) = " << std::trunc(neg) << '\n';
return 0;
}运行结果如下。
round(3.7) = 4
trunc(3.7) = 3
round(-3.7) = -4
trunc(-3.7) = -3分析:
对比示例 1 和示例 2 的输出可以发现:只看正数 3.7 时,std::trunc(3.7) 和 std::floor(3.7) 的结果都是 3,很容易让人误以为它们是一回事。
换成负数 -3.7 之后,两者的差别就很清楚了:std::floor(-3.7) 往更小的方向走得到 -4,而 std::trunc(-3.7) 只是单纯去掉小数部分,得到 -3。如果小数部分刚好等于 0.5,std::round() 又会怎么处理呢?下面我们再来看一个例子。
示例 3:round() 处理 0.5 边界
#include <cmath>
#include <iostream>
int main() {
std::cout << "round(3.4) = " << std::round(3.4) << '\n';
std::cout << "round(3.5) = " << std::round(3.5) << '\n';
std::cout << "round(-3.4) = " << std::round(-3.4) << '\n';
std::cout << "round(-3.5) = " << std::round(-3.5) << '\n';
return 0;
}运行结果如下。
round(3.4) = 3
round(3.5) = 4
round(-3.4) = -3
round(-3.5) = -4分析:
从运行结果可以看出,std::round(3.5) 和 std::round(-3.5) 分别返回了 4 和 -4。因为 3.5 处于 3 和 4 的正中间,往远离 0 的方向取整得到 4;同理,-3.5 处于 -3 和 -4 的正中间,往远离 0 的方向取整得到 -4。
将取整结果转为整型
看到前面屏幕上打印出的 3、4、-4,可能有些小伙伴会以为这些取整函数返回的就是 int 整型。实际上,因为 double 能表示的数值范围远大于 int,前面这 4 个函数默认返回的依然是 double 浮点类型。
如果后续代码需要把四舍五入的结果保存到整型变量中,我们有 2 种常用方式:一种是在取整后配合强制类型转换转为 int,另一种是直接调用返回 long 整型的 std::lround() 函数。下面我们来看一个例子。
示例 4:将取整结果保存为整型
#include <cmath>
#include <iostream>
int main() {
double price = 19.8;
int a = (int)std::round(price); // 先四舍五入再转为 int
long b = std::lround(price); // 四舍五入并直接返回 long
std::cout << a << '\n';
std::cout << b << '\n';
return 0;
}运行结果如下。
20
20分析:
在这个例子中,变量 a 先通过 std::round(price) 把 19.8 四舍五入为 20.0,再通过 (int) 强制转换为整型 20。而 std::lround(price) 则一步到位,四舍五入后直接返回 long 类型。如果待处理的数值超出了 int 的上限(约 21 亿),我们还可以调用返回 long long 的 std::llround()。
需要注意的是,如果跳过 std::round() 而直接对浮点数做强制类型转换,C++ 会直接抹掉小数部分,效果等同于 std::trunc()。下面这种写法得到的就是 19 而不是 20。
int c = (int)19.8; // 直接截断小数,结果为 19因此当我们需要按四舍五入规则存入整型变量时,小伙伴们记得先调用 std::round() 再转换,或者直接使用 std::lround()。
常见问题
1. 如何将浮点数四舍五入并保留 2 位小数?
std::round() 默认只能取整到个位。在实际开发中,如果想把 3.146 四舍五入保留为 2 位小数 3.15,我们可以按以下 2 种场景分别处理:
仅用于屏幕展示:如果只是想在输出时显示 2 位小数,不需要修改变量本身的值,我们在引入 <iomanip> 后配合 std::fixed 和 std::setprecision(2) 输出即可,打印时会自动按四舍五入显示。
需要得到四舍五入后的数值:我们可以先乘以 100.0 调用 std::round() 取整,再除以 100.0,写成 double res = std::round(x * 100.0) / 100.0;。不过二进制浮点数本身存在精度限制,如果是订单结算这类要求分毫不差的场景,实际开发中通常会把 “元” 换算成 “分”,统一用 long long 整数存储计算。
