你真的會向上取整嗎?當心Math.ceil的精度丟失

背景

在處理一個分頁工做時,須要作一個向上取整的操做,相似這樣:java

// 總數
int totalSize = 799;
// 頁大小
int pageSize = 200;
// 計算頁數須要向上取整
// 這裏的向上取整如何處理?
int pageCount = (double)totalSize / pageSize;
複製代碼

這個向上取整計算其實很經常使用,可是彷佛沒有一個比較標準的處理方式。工具

常見的處理方式

方案一:檢查餘數spa

檢查餘數是否爲0,這是我經常使用的寫法。.net

// ...
int pageCount = totalSize / pageSize;
if (totalSize % pageSize > 0) {
	pageCount++;
}
複製代碼

這種方式很笨,同時還有點囉嗦。code

**方案二(不推薦!):Math.ceil(double x) **cdn

也許看完第一個方案已經有人在說了,官方Math工具就提供了向上取整的方法,爲何不用呢?blog

// ...
int pageCount = (int)Math.ceil((double)totalSize / pageSize);
複製代碼

最大的問題:Math.ceil的返回類型是doubleget

且不說這個場景是兩個整型運算,結果也是整型,中間就要通過兩次類型轉換操做,看起來不夠流暢;咱們還應該考慮到在類型轉換中可能存在的精度丟失問題。博客

精度丟失可能僅僅在很極端的場景下出現,可一旦出現就是很是難以排查的隱式bug。舉個例子:it

  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博客

本文搬自個人博客,歡迎參觀!

相關文章
相關標籤/搜索