背景
在处理一个分页工作时,需要做一个向上取整的操作,类似这样:
// 总数
int totalSize = 799;
// 页大小
int pageSize = 200;
// 计算页数需要向上取整
// 这里的向上取整如何处理?
int pageCount = (double)totalSize / pageSize;
这个向上取整计算其实很常用,但是似乎没有一个比较标准的处理方式。
常见的处理方式
方案一:检查余数
检查余数是否为0,这是我常用的写法。
// ...
int pageCount = totalSize / pageSize;
if (totalSize % pageSize > 0) {
pageCount++;
}
这种方式很笨,同时还有点啰嗦。
**方案二(不推荐!):Math.ceil(double x) **
也许看完第一个方案已经有人在说了,官方Math工具就提供了向上取整的方法,为什么不用呢?
// ...
int pageCount = (int)Math.ceil((double)totalSize / pageSize);
最大的问题:Math.ceil的返回类型是double。
且不说这个场景是两个整型运算,结果也是整型,中间就要经过两次类型转换操作,看起来不够流畅;我们还应该考虑到在类型转换中可能存在的精度丢失问题。
精度丢失可能仅仅在很极端的场景下出现,可一旦出现就是非常难以排查的隐式bug。举个例子:
- 假设某两个数的ceil计算结果原本是2.0,但由于精度问题,ceil结果其实是1.9999999999999999999999999
- 在结果转为int型数据时,发生了精度丢失,计算结果由2.0转换为1,相当于少了一页
- 在分页处理过程中,缺失了最后一页的数据
- 在复现过程中,仅有极个别case中会出现这种bug,且从代码逻辑来看其实也没什么问题
再举个例子,假设使用double类型的pageCount,如下一段代码就有可能出现ArrayOutOfBoundsException:
// ...
double pageCount = 2d; // 可能实际存储的是2.000000000000001,请意会
for (int page = 0; page < pageCount; page++) {
// Array或List的遍历操作
}
在任何可能出现与浮点型数据进行比较,或浮点型与整型的类型转换都必须非常注意。
方案三(推荐):增加一点小处理
int pageCount = (totalSize + pageSize - 1) / pageSize;
这个计算方式在一行代码内解决,没有类型转换或精度丢失,处理上是进位思想,只要理解了就非常自然。
唯一的缺点是如果值过大可能产生溢出,但足够满足绝大多数场景。
补充
为什么Math.ceil返回的是浮点数?
是的,返回浮点整数虽然符合ceil的语义,但并不符合我们平常的直观理解,如果能返回整型数该多好?
但是这个方法的入参就是double类型,而无论是int类型或是long类型的表示范围都远远小于double类型的范围,如果使用整型就有可能表示不了,因而造成这一奇怪的现实。
表示范围详见参考资料。
便捷的List切分方式
写到一半时想了想,实际上我本次的处理场景是将一个整体任务的List数据水平切分为多个子任务List,再进行分发处理。
如果是针对List的切分场景,其实非常推荐使用Google的guava包来处理:
// 直接分割,非常方便
List<List<Object>> subTaskLists = Lists.partition(taskList, pageSize);
Lists.partition的实现方式:固定步长,多次取subList。
参考资料
使用Math.ceil将Java四舍五入为int - 问答 - 云+社区 - 腾讯云
java中short、int、long、float、double取值范围 - qfikh的博客 - CSDN博客
本文搬自我的博客,欢迎参观!