2013-10-25 70 views
0

編輯: 這個問題涉及兩個主題:雙到位浮動和浮動四捨五入

  • 到位浮動使用雙精度浮點精度的
  • 效率以下四捨五入

是我有什麼理由不應該總是使用Java double而不是float?

我問這個問題,因爲這個測試代碼使用浮動時失敗和不清楚爲什麼,因爲唯一的區別是使用float而不是double。

public class BigDecimalTest { 
@Test public void testDeltaUsingDouble() { //test passes 
    BigDecimal left = new BigDecimal("0.99").setScale(2,BigDecimal.ROUND_DOWN); 
    BigDecimal right = new BigDecimal("0.979").setScale(2,BigDecimal.ROUND_DOWN); 

    Assert.assertEquals(left.doubleValue(), right.doubleValue(), 0.09); 
    Assert.assertEquals(left.doubleValue(), right.doubleValue(), 0.03); 

    Assert.assertNotEquals(left.doubleValue(), right.doubleValue(), 0.02); 
    Assert.assertNotEquals(left.doubleValue(), right.doubleValue(), 0.01); 
    Assert.assertNotEquals(left.doubleValue(), right.doubleValue(), 0.0); 
} 
@Test public void testDeltaUsingFloat() { //test fails on 'failing assert' 

    BigDecimal left = new BigDecimal("0.99").setScale(2,BigDecimal.ROUND_DOWN); 
    BigDecimal right = new BigDecimal("0.979").setScale(2,BigDecimal.ROUND_DOWN); 

    Assert.assertEquals(left.floatValue(), right.floatValue(), 0.09); 
    Assert.assertEquals(left.floatValue(), right.floatValue(), 0.03); 

    /* failing assert */ Assert.assertNotEquals(left.floatValue() + " - " + right.floatValue() + " = " + (left.floatValue() - right.floatValue()),left.floatValue(), right.floatValue(), 0.02); 
    Assert.assertNotEquals(left.floatValue(), right.floatValue(), 0.01); 
    Assert.assertNotEquals(left.floatValue(), right.floatValue(), 0.0); 
}} 

失敗消息:

java.lang.AssertionError: 0.99 - 0.97 = 0.01999998. Actual: 0.9900000095367432 
at org.junit.Assert.fail(Assert.java:88) 
at org.junit.Assert.failEquals(Assert.java:185) 
at org.junit.Assert.assertNotEquals(Assert.java:230) 
at com.icode.common.BigDecimalTest.testDeltaUsingFloat(BigDecimalTest.java:34) 

任何想法,爲什麼這個測試失敗,爲什麼我不應該只是總是使用浮動的雙重呢?當然,雙倍以外的原因比浮動更寬。有趣的是,Assert.assertNotEquals(double,double,delta)在兩種情況下都是雙重的,所以失敗測試中返回的浮動數據變得越來越寬,所以爲什麼測試失敗呢?

編輯: 可能是這樣的其他問題是相關的,不知道雖然: hex not the same

編輯: 從這個問題的答案hex not the same可以得出的結論是科學的表示IEEE 754 0.99浮法對於相同的值而言不同於雙倍。這是由於四捨五入。

因此,我們得到這樣的:

  • 0.99 - 0.97 = 0.01999998 //在浮動情況下
  • 0.99 - 0.97 = 0.020000000000000018 //雙的情況下

由於在最大增量上面的單元測試是0.02和0.01999998(在失敗的測試中)低於德爾塔值,這意味着數字看起來是相同的,但測試聲稱它們並沒有因此失敗。

你認同這一切嗎?

+0

它似乎有浮動減法和舍入的特殊性:http://stackoverflow.com/questions/13263650/float-number-is-not-the-expected-number-after-subtraction – Francis

回答

2

documentation for BigDecimal是沉默如何floatValue()輪。我認爲它使用的是最接近,最接近的關係。

leftright分別被設置爲.99和.97。當這些轉換爲double時,以圓整模式轉換,結果爲0.9899999999999999911182158029987476766109466552734375(十六進制浮點型,0x1.fae147ae147aep-1)和0.9699999999999999733546474089962430298328399658203125(0x1.f0a3d70a3d70ap-1)。當這些被減去時,結果是0.020000000000000017763568394002504646778106689453125,其明顯超過.02。

當.99和.97轉換爲float時,結果爲0.9900000095367431640625(0x1.fae148p-1)和0.9700000286102294921875(0x1.f0a3d8p-1)。當這些被減去時,結果是0.019999980926513671875,明顯小於0.02。

簡而言之,當一個十進制數字轉換爲浮點數時,舍入可能會增加或減少。這取決於相對於最接近的可表示浮點值而言數字所在的位置。如果它沒有被控制或分析,它實際上是隨機的。因此,有時你會得到比預期更大的價值,有時你最終會得到較低的價值。

使用double而不是float而不是保證不會發生類似上述結果。在這種情況下,double的值僅僅是確切的數學值,並且float的值沒有。與其他數字一樣,這可能是相反的。例如,對於double,.09-.07小於.02,但是,對於float,.09f - .07f`大於.02。

關於如何處理浮點運算有很多信息,例如Handbook of Floating-Point Arithmetic。在Stack Overflow問題中,這個主題太大了。有關於它的大學課程。

通常在今天的典型處理器上,使用double而不是float幾乎沒有額外的花費;對於doublefloat,以幾乎相同的速度執行簡單標量浮點運算。當你有太多的數據時,性能上的差異就會產生,傳輸它們的時間(從磁盤到內存或內存到處理器)變得很重要,或者它們佔用磁盤空間變大,或者你的軟件使用處理器的SIMD特性。 (SIMD允許處理器對多個數據並行執行相同的操作,當前的處理器通常提供的SIMD操作的SIMD操作的帶寬約爲SIMD操作的兩倍,或根本不提供double SIMD操作。)

+0

+1您涵蓋了所有被問及的問題。 – Tarik

1

Double可以表示具有較大數字有效數字的數字,具有較大範圍,反之亦然。雙計算在CPU方面成本更高。所以這一切都取決於你的應用程序。 二進制數字不能準確表示一個數字,如1/5。這些數字最終被四捨五入,從而引發錯誤,這些錯誤在您失敗的斷言的起源處是確定的。 查看http://en.m.wikipedia.org/wiki/Floating_point瞭解更多詳情。

[編輯] 如果一切都失敗運行的基準:

package doublefloat; 

/** 
* 
* @author tarik 
*/ 
public class DoubleFloat { 

    /** 
    * @param args the command line arguments 
    */ 
    public static void main(String[] args) { 
     // TODO code application logic here 
     long t1 = System.nanoTime(); 
     double d = 0.0; 
     for (long i=0; i<1000000000;i++) { 
      d = d * 1.01; 
     } 
     long diff1 = System.nanoTime()-t1; 
     System.out.println("Double ticks: " + diff1); 

     t1 = System.nanoTime(); 
     float f = 0.0f; 
     for (long i=0; i<1000000000;i++) { 
      f = f * 1.01f; 
     } 
     long diff2 = System.nanoTime()-t1; 
     System.out.println("Float ticks: " + diff2); 
     System.out.println("Difference %: " + (diff1 - diff2) * 100.0/diff1);  
    } 
} 

輸出:

Double ticks: 3694029247 
Float ticks: 3355071337 
Difference %: 9.175831790592209 

該測試跑採用了Intel Core 2 Duo處理器的PC上。請注意,由於我們只處理緊密循環中的單個變量,因此無法壓倒可用內存帶寬。實際上,其中一個核心在每次運行期間始終顯示100%的CPU。 結論:差異是9%,可能被認爲是微不足道的。

第二測試涉及相同的試驗,但分別使用相對大量的存儲器140MB和280MB的浮法和雙:

package doublefloat; 

/** 
* 
* @author tarik 
*/ 
public class DoubleFloat { 

    /** 
    * @param args the command line arguments 
    */ 
    public static void main(String[] args) { 
     final int LOOPS = 70000000; 
     long t1 = System.nanoTime(); 
     double d[] = new double[LOOPS]; 
     d[0] = 1.0; 
     for (int i=1; i<LOOPS;i++) { 
      d[i] = d[i-1] * 1.01; 
     } 
     long diff1 = System.nanoTime()-t1; 
     System.out.println("Double ticks: " + diff1); 

     t1 = System.nanoTime(); 
     float f[] = new float[LOOPS]; 
     f[0] = 1.0f; 
     for (int i=1; i<LOOPS;i++) { 
      f[i] = f[i-1] * 1.01f; 
     } 
     long diff2 = System.nanoTime()-t1; 
     System.out.println("Float ticks: " + diff2); 
     System.out.println("Difference %: " + (diff1 - diff2) * 100.0/diff1);  
    } 
} 

輸出:

Double ticks: 667919011 
Float ticks: 349700405 
Difference %: 47.64329218950769 

存儲器帶寬被淹沒,但我仍然可以在短時間內看到CPU達到100%的峯值。

結論: 這個基準測試有些證實,使用double需要多花費9%的時間在CPU密集型應用程序上,而在數據密集型應用程序中需要花費50%的時間。它還確認Eric Postpischil請注意,與有限的內存帶寬的性能影響相比,CPU開銷相對可以忽略不計(9%)。

+0

+1關於CPU使用率,讀長需要兩個操作,我認爲同樣適用於雙倍,因爲它也是64位。謝謝 – jakstack

+0

不能確定在正確分配的內存位置上的64位計算機上的情況。 – Tarik

+0

看起來四捨五入只發生在float情況下,而不是double,但爲什麼這是因爲使用的數字對於兩種數據類型都足夠小,除非當然是左 - 右=需要四捨五入的非常大的小數。實際上可能是這樣的答案,因爲在雙重情況下,數據類型足夠寬以保持結果而不捨入。 – jakstack