你真的会向上取整吗?小心Math.ceil的精度丢失

背景

在处理一个分页工作时,需要做一个向上取整的操作,类似这样:

// 总数
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。举个例子:

  1. 假设某两个数的ceil计算结果原本是2.0,但由于精度问题,ceil结果其实是1.9999999999999999999999999
  2. 在结果转为int型数据时,发生了精度丢失,计算结果由2.0转换为1,相当于少了一页
  3. 在分页处理过程中,缺失了最后一页的数据
  4. 在复现过程中,仅有极个别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博客

本文搬自我的博客,欢迎参观!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容