CreateArtTechnology
/ Blog
Login
最新文章
Java
语言相关
库相关
虚拟机相关
CreateArtTechnology
项目搭建
使用的工具
自研的工具
开源工具
ELK
ElasticSearch
Jenkins
Markdown
GraphQL
Arthas
生产工具
Linux
Nginx
VersionControl
Subversion
Git
Redis
Archiva
Maven
Zookeeper
Spring
SpringBoot
MySql
HBase
Cassandra
容器化
Docker
Kubernetes
服务容器化从零开始
未分类笔记
算法相关
概念相关
豆知识
机器学习
机器学习从零开始
你真的会向上取整吗?小心Math.ceil的精度丢失
29
2019-08-27 21:44:18
Java
语言相关
## 背景 在处理一个分页工作时,需要做一个向上取整的操作,类似这样: ```java // 总数 int totalSize = 799; // 页大小 int pageSize = 200; // 计算页数需要向上取整 // 这里的向上取整如何处理? int pageCount = (double)totalSize / pageSize; ``` 这个向上取整计算其实很常用,但是似乎没有一个比较标准的处理方式。 ## 常见的处理方式 **方案一:检查余数** 检查余数是否为0,这是我常用的写法。 ```java // ... int pageCount = totalSize / pageSize; if (totalSize % pageSize > 0) { pageCount++; } ``` 这种方式很笨,同时还有点啰嗦。 **方案二(不推荐!):Math.ceil(double x) ** 也许看完第一个方案已经有人在说了,官方Math工具就提供了向上取整的方法,为什么不用呢? ```java // ... int pageCount = (int)Math.ceil((double)totalSize / pageSize); ``` 最大的问题:***Math.ceil的返回类型是double***。 且不说这个场景是两个整型运算,结果也是整型,中间就要经过两次类型转换操作,看起来不够流畅;我们还应该考虑到在类型转换中可能存在的精度丢失问题。 精度丢失可能仅仅在很极端的场景下出现,可一旦出现就是非常难以排查的隐式bug。举个例子: 1. 假设某两个数的ceil计算结果原本是2.0,但由于精度问题,ceil结果其实是1.9999999999999999999999999 2. 在结果转为int型数据时,发生了精度丢失,计算结果由2.0转换为1,相当于少了一页 3. 在分页处理过程中,缺失了最后一页的数据 4. 在复现过程中,仅有极个别case中会出现这种bug,且从代码逻辑来看其实也没什么问题 再举个例子,假设使用double类型的pageCount,如下一段代码就有可能出现ArrayOutOfBoundsException: ```java // ... double pageCount = 2d; // 可能实际存储的是2.000000000000001,请意会 for (int page = 0; page < pageCount; page++) { // Array或List的遍历操作 } ``` ***在任何可能出现与浮点型数据进行比较,或浮点型与整型的类型转换都必须非常注意。*** **方案三(推荐):增加一点小处理** ```java int pageCount = (totalSize + pageSize - 1) / pageSize; ``` 这个计算方式在一行代码内解决,没有类型转换或精度丢失,处理上是进位思想,只要理解了就非常自然。 唯一的缺点是如果值过大可能产生溢出,但足够满足绝大多数场景。 ## 补充 **为什么Math.ceil返回的是浮点数?** 是的,返回**浮点整数**虽然符合ceil的语义,但并不符合我们平常的直观理解,如果能返回整型数该多好? 但是这个方法的入参就是double类型,而无论是int类型或是long类型的表示范围都远远小于double类型的范围,如果使用整型就有可能表示不了,因而造成这一奇怪的现实。 表示范围详见[参考资料](https://blog.csdn.net/qfikh/article/details/52832087)。 **便捷的List切分方式** 写到一半时想了想,实际上我本次的处理场景是将一个整体任务的List数据水平切分为多个子任务List,再进行分发处理。 如果是针对List的切分场景,其实非常推荐使用Google的guava包来处理: ```java // 直接分割,非常方便 List
> subTaskLists = Lists.partition(taskList, pageSize); ``` Lists.partition的实现方式:固定步长,多次取subList。 ## 参考资料 [使用Math.ceil将Java四舍五入为int - 问答 - 云+社区 - 腾讯云](https://cloud.tencent.com/developer/ask/65787) [java中short、int、long、float、double取值范围 - qfikh的博客 - CSDN博客](https://blog.csdn.net/qfikh/article/details/52832087)
发布文章 101
文章被阅读 1817
最近修改
什么是“丝滑”的曲线
2021-12-08 15:19:20
高效空间数据索引R树及其批量加载方法STR简介
2021-09-29 20:33:37
关于分库分表的一些事儿
2021-06-25 11:51:25
获得诺奖的稳定匹配理论之TTC算法与GS算法
2021-03-14 23:04:48
算法小白的机器学习入门实践,从零到上线
2021-01-13 14:28:27
分站宗旨
一站式资料平台,减少重复检索,减少重复采坑。