簡體   English   中英

代替Float和Float取整

[英]Double in place of Float and Float rounding

編輯:這個問題包括兩個主題:

  • 代替浮子使用的效率
  • 舍入后的浮點精度

有什么原因為什么我不應該總是使用Java double而不是float?

我問這個問題是因為使用浮點數時此測試代碼失敗,並且不清楚為什么,因為唯一的區別是使用浮點數而不是雙精度數。

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)

知道為什么這個測試失敗了,為什么我不應該總是使用double而不是float? 當然,除了double之外,還有一個原因比float寬。

編輯:有趣的是,在兩種情況下Assert.assertNotEquals(double,double,delta)都需要double,因此失敗測試中返回的浮點數無論如何都會擴大為double,那么為什么測試失敗呢?

編輯:可能與此其他問題有關,但不確定: 十六進制不相同

編輯:從這個問題的答案十六進制不相同 ,可以得出結論,對於相同值,.99的float的科學表示形式IEEE 754與double有所不同。 這是由於四舍五入。

因此我們得到:

  • 0.99-0.97 = 0.01999998 //在浮點情況下
  • 0.99-0.97 = 0.020000000000000018 //雙精度

由於上述單元測試中的最大增量為0.02,而0.01999998(在失敗的測試中)低於該增量值,這意味着數字被視為相同,但是測試斷言它們沒有因此失敗。

你們都同意這一切嗎?

BigDecimal文檔未提及floatValue()如何取floatValue() 我認為它使用的是從最近到最近的關系,甚至是到平均的關系。

leftright分別設置為.99和.97。 將這些值在最近舍入模式下轉換為double時,結果為0.98999999999999999999911182158029987476766109466552734375(以十六進制浮點數表示,0x1.fae147ae147aep-1)和0.9699999999999999733546474962430298328399658203125(0x1.f0a3d70a3d70ap-1)。 減去這些后,結果為0.020000000000000017763568394002504646778106106453453,明顯超過.02。

將.99和.97轉換為float ,結果為0.9900000095367431640625(0x1.fae148p-1)和0.9700000286102294921875(0x1.f0a3d8p-1)。 減去這些后,結果為0.019999980926513671875,顯然小於.02。

簡而言之,當十進制數字轉換為浮點數時,舍入可能會向上或向下。 這取決於相對於最接近的可表示浮點值而言,數字恰好位於何處。 如果不對其進行控制或分析,則實際上是隨機的。 因此,有時您最終獲得的價值比您預期的要大,而有時您最終獲得的價值卻更低。

使用double而不是float 不能保證不會發生與上述類似的結果。 碰巧的是,在這種情況下, double float值超出了精確的數學值,而float值沒有超過。 對於其他數字,情況可能恰恰相反。 例如,使用double.09-.07小於.02,但是使用float ,.09f-.07f`大於.02。

. 關於如何處理浮點算術,有很多信息,例如 這是一個太大的主題,無法在Stack Overflow問題中涵蓋。 有關於它的大學課程。

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

Double可以代表具有更大有效數字的數字,具有更大的范圍,反之亦然。 就CPU而言,雙重計算的成本更高。 因此,這完全取決於您的應用程序。 二進制數字不能完全代表1/5等數字。 這些數字最終會被四舍五入,從而引入錯誤,這些錯誤可在斷言失敗的根源上確定。 有關更多詳細信息,請參見http://en.m.wikipedia.org/wiki/Floating_point

[EDIT]如果所有其他方法均失敗,請運行基准測試:

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分別用於float和double:

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花費的時間比CPU密集型應用程序的浮動時間多9%,而對數據密集型應用程序的浮動時間則多50%。 它還證實了Eric Postpischil的注釋,與有限的內存帶寬對性能的影響相比,CPU開銷相對可以忽略不計(9%)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM