2016-07-08 32 views
4

當我用HALF_EVEN模式舍入了一個雙精度數時,我發現了一個問題,我不知道它是否是JDK的一個bug?請看下面的代碼:是Java RoundingMode.HALF_EVEN錯誤?

public static void main(String[] args) { 
    RoundingMode mode = RoundingMode.HALF_EVEN; 
    for (int i = 10; i < 100; i++) { 
     double d = i + 0.55; 
     int scale = 1; 
     process(d, scale++, mode); 

     d = i + 0.555; 
     process(d, scale++, mode); 

     d = i + 0.5555; 
     process(d, scale++, mode); 

     d = i + 0.55555; 
     process(d, scale++, mode); 
     System.out.println("\n"); 
    } 
} 

private static void process(double d, int scale, RoundingMode roundingMode) { 
    BigDecimal b = new BigDecimal(d).setScale(scale, roundingMode); 
    System.out.println(d + " -> " + b); 
} 

我期望輸出:

10.55 -> 10.6 
10.555 -> 10.56 
10.5555 -> 10.556 
10.55555 -> 10.5556 


11.55 -> 11.6 
11.555 -> 11.56 
11.5555 -> 11.556 
11.55555 -> 11.5556 

..... 

但實際上,它輸出數據,如下面的:

10.55 -> 10.6 
10.555 -> 10.55 
10.5555 -> 10.556 
10.55555 -> 10.5556 


11.55 -> 11.6 
11.555 -> 11.55 
11.5555 -> 11.556 
11.55555 -> 11.5556 
.... 

30.55 -> 30.6 
30.555 -> 30.55 
30.5555 -> 30.555 
30.55555 -> 30.5556 



31.55 -> 31.6 
31.555 -> 31.55 
31.5555 -> 31.555 
31.55555 -> 31.5556 



32.55 -> 32.5 
32.555 -> 32.55 
32.5555 -> 32.556 
32.55555 -> 32.5555 



33.55 -> 33.5 
33.555 -> 33.55 
33.5555 -> 33.556 
33.55555 -> 33.5555 

......... 

62.55 -> 62.5 
62.555 -> 62.55 
62.5555 -> 62.556 
62.55555 -> 62.5555 



63.55 -> 63.5 
63.555 -> 63.55 
63.5555 -> 63.556 
63.55555 -> 63.5555 



64.55 -> 64.5 
64.555 -> 64.56 
64.5555 -> 64.555 
64.55555 -> 64.5555 



65.55 -> 65.5 
65.555 -> 65.56 
65.5555 -> 65.555 
65.55555 -> 65.5555 



66.55 -> 66.5 
66.555 -> 66.56 
66.5555 -> 66.555 
66.55555 -> 66.5555 

有同樣的結果在JDK 1.7 /1.8

這是JDK的bug嗎?

+0

爲什麼你認爲這是一個bug?爲什麼要將BigDecimals加入這個? – Shark

回答

5

令人遺憾的是,這是生活的一部分,並且由於double類型在內部以2進制實現。對於科學編程而言,這是非常聰明的事情。但是這確實意味着當你想查看或者用一個10位基數來指定它們時,你看到的並不一定是你得到的。

例如,字面10.555實際上是一個數比這(10.5549999999999997157829056959599256515502929687...)所以表觀德國舍入稍少實際上將輪這個號碼向下。你的問題包含許多類似的情況。

這不是一個問題,但如果你需要精確舍入,那麼你應該使用一個類來配備任意精度的十進制數。

使用BigDecimal是一個選項,但你是濫用它可怕!。通過使用來自double(即new BigDecimal(d))的構造函數,d中的不精確特徵將僅被複制到BigDecimal。所以你應該使用String的構造函數,或者使用類似的方法。

最後,看看http://www.exploringbinary.com/floating-point-converter。輸入你的十進制數並查看它轉換爲浮點時的值。

+0

非常感謝。 –

1

這不是舍入模式的錯誤,它是您創建BigDecimal的方式。 嘗試以下操作:

BigDecimal b = new BigDecimal(Double.toString(d)).setScale(scale, roundingMode); 

要創建一個BigDecimal提到here你不應該從雙值創建它。

編輯:這是更好地使用的valueOf作爲評價和在api

+0

https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal .html#BigDecimal-double- – Tom

+0

BigDecimal.valueOf(double)將爲你做這件事 – Tobold

-1

你應該改變你的流程的方法來表示:

private static void process(double d, int scale, RoundingMode roundingMode) { 
    BigDecimal unscaled = new BigDecimal(d); 
    BigDecimal scaled = unscaled.setScale(scale, roundingMode); 
    System.out.println(unscaled + " -> " + scaled); 
} 

你縮放前看到確切的價值的方式。

+0

這與問題處理方法相同 –

+0

有一個區別,它打印未縮放的BigDecimal而不是double。 1.5549999999999999378275106209912337362766265869140625 - > 1.55 –

1

改爲使用BigDecimal b = BigDecimal.valueOf(d).setScale(scale, roundingMode);

由於docs說「這通常是將double(或float)轉換爲BigDecimal的首選方法,因爲返回的值等於從使用Double.toString(double)結果構造BigDecimal所產生的值)。「