[英]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的float
或double
。 由於此表示錯誤,結果與您預期的結果略有不同。
您可以使用幾種方法:
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);
}
您可以使用BigDecimal
類的類來避免此特定問題。 float
和double
,即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,不是因為它快得多,而是通常更短/更容易理解。
如果將符號數作為代碼復雜度的度量
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.