簡體   English   中英

如何在Java中使用浮點數或雙精度數避免浮點精度錯誤?

[英]How to avoid floating point precision errors with floats or doubles in Java?

我有一個很煩人的問題,Java中的浮點數或雙精度值比較長。 基本上的想法是,如果我執行:

for ( float value = 0.0f; value < 1.0f; value += 0.1f )
    System.out.println( value );

我得到的是:

0.0
0.1
0.2
0.3
0.4
0.5
0.6
0.70000005
0.8000001
0.9000001

我了解到,浮動精度誤差會不斷累積,但是,如何消除這種誤差呢? 我嘗試使用雙精度模式將錯誤減半,但結果仍然相同。

有任何想法嗎?

沒有精確表示為0.1的floatdouble 由於此表示錯誤,結果與您預期的結果略有不同。

您可以使用幾種方法:

  • 使用double型時,僅顯示所需的位數。 在檢查相等性時,可以選擇任一種方式都允許較小的公差。
  • 或者,使用允許您存儲要精確表示的數字的類型,例如BigDecimal可以精確表示0.1。

BigDecimal示例代碼:

BigDecimal step = new BigDecimal("0.1");
for (BigDecimal value = BigDecimal.ZERO;
     value.compareTo(BigDecimal.ONE) < 0;
     value = value.add(step)) {
    System.out.println(value);
}

在線觀看ideone

您可以使用BigDecimal類的類來避免此特定問題。 floatdouble ,即IEEE 754浮點數,並不是為了精確而設計的,而是為了快速而設計的。 但是請注意喬恩的觀點: BigDecimal不能准確表示“三分之一”,多於double可以准確表示“十分之一”。 但是對於(例如)財務計算, BigDecimal和類似的類往往是BigDecimal的方式,因為它們可以用我們人類傾向於思考的方式表示數字。

不要在迭代器中使用float / double,因為這會使您的舍入誤差最大化。 如果您僅使用以下內容

for (int i = 0; i < 10; i++)
    System.out.println(i / 10.0);

它打印

0.0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9

我知道BigDecimal是一個受歡迎的選擇,但我更喜歡double,不是因為它快得多,而是通常更短/更容易理解。

如果將符號數作為代碼復雜度的度量

  • 使用double => 11個符號
  • 使用BigDecimal(來自@Mark Byers示例)=> 21個符號

BTW:除非有很好的理由不使用雙不使用浮動。

只是一個累積誤差(與絕對沒有任何與Java做的)。 1.0f轉換為實際代碼后,其值不為0.1-您已經得到了舍入錯誤。

浮點指南:

我應該怎么做才能避免這個問題?

這取決於您正在執行哪種計算。

  • 如果您確實需要精確地將結果相加,尤其是在使用金錢時,請使用特殊的十進制數據類型。
  • 如果您只是不想看到所有這些多余的小數位:只需在顯示結果時將結果的格式四舍五入為固定的小數位數即可。
  • 如果沒有可用的十進制數據類型,則替代方法是使用整數,例如,完全用美分進行貨幣計算。 但這是更多的工作,並且有一些缺點。

閱讀鏈接到的站點以獲取詳細信息。

為了完整起見,我建議您這樣做:

Shewchuck,“穩健的自適應浮點幾何謂詞”,如果您想要更多有關如何使用浮點執行精確算術的示例,或者至少是受控精度,這是作者的初衷, http://www.cs.berkeley。 edu /〜jrs / papers / robustr.pdf

我曾經遇到過同樣的問題,使用BigDecimal解決了同樣的問題。 以下是幫助我的代碼段。

double[] array = {45.34d, 45000.24d, 15000.12d, 4534.89d, 3444.12d, 12000.00d, 4900.00d, 1800.01d};
double total = 0.00d;
BigDecimal bTotal = new BigDecimal(0.0+"");
for(int i = 0;i < array.length; i++) {
    total += (double)array[i];
    bTotal = bTotal.add(new BigDecimal(array[i] +""));
}
System.out.println(total);
System.out.println(bTotal);

希望對您有幫助。

package loopinamdar;

import java.text.DecimalFormat;

public class loopinam {
    static DecimalFormat valueFormat = new DecimalFormat("0.0");

    public static void main(String[] args) {
        for (float value = 0.0f; value < 1.0f; value += 0.1f)
            System.out.println("" + valueFormat.format(value));
    }
}

您應該使用十進制數據類型,而不是浮點數:

https://docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.html

另一個解決方案是放棄==並檢查兩個值是否足夠接近 (我知道這不是您在體內提出的問題,但我正在回答問題標題。)

首先將其翻倍 永遠不要使用float,否則使用java.lang.Math實用程序會遇到麻煩。

現在,如果您碰巧事先知道所需的精度 ,並且該精度等於或小於15,則很容易告訴您double的行為。 檢查以下內容:

// the magic method:
public final static double makePrecise(double value, int precision) {
    double pow = Math.pow(10, precision);
    long powValue = Math.round(pow * value);
    return powValue / pow;
}

現在,無論何時進行操作,都必須告訴您雙重結果的行為:

for ( double value = 0.0d; value < 1.0d; value += 0.1d )
            System.out.println( makePrecise(value, 1) + " => " + value );

輸出:

0.0 => 0.0
0.1 => 0.1
0.2 => 0.2
0.3 => 0.30000000000000004
0.4 => 0.4
0.5 => 0.5
0.6 => 0.6
0.7 => 0.7
0.8 => 0.7999999999999999
0.9 => 0.8999999999999999
1.0 => 0.9999999999999999

如果您需要超過15的精度,那么您就不走運了:

for ( double value = 0.0d; value < 1.0d; value += 0.1d )
            System.out.println( makePrecise(value, 16) + " => " + value );

輸出:

0.0 => 0.0
0.1 => 0.1
0.2 => 0.2
0.3000000000000001 => 0.30000000000000004
0.4 => 0.4
0.5 => 0.5
0.6 => 0.6
0.7 => 0.7
0.8 => 0.7999999999999999
0.9 => 0.8999999999999999
0.9999999999999998 => 0.9999999999999999

注意1:為了提高性能,您應該將Math.pow操作緩存在一個數組中。 為了清楚起見,這里沒有做。

注意2:這就是為什么我們從不使用double來表示價格,而是使用long來表示最后N個數字(即N <= 15,通常為8)是十進制數字的原因。 然后您就可以忘記我上面寫的內容了:)

如果要繼續使用float並通過重復添加0.1f避免累積錯誤,請嘗試如下操作:

for (int count = 0; count < 10; count++) {
    float value = 0.1f * count;
    System.out.println(value);
}

但是請注意,正如其他人已經解釋的那樣, float不是無限精確的數據類型。

您只需要了解計算所需的精度以及所選數據類型能夠提供的精度,並相應地給出答案。

例如,如果您要處理具有3個有效數字的數字,則使用float (可提供7個有效數字的精度)是合適的。 但是,如果您的起始值僅具有2個有效數字的精度,則不能引用最終答案對7個有效數字的精度。

5.01 + 4.02 = 9.03 (to 3 significant figures)

在您的示例中,您要執行多次加法,每次加法都會對最終精度產生影響。

暫無
暫無

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

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