簡體   English   中英

失去從 java BigDecimal 轉換為 double 的精度

[英]losing precision converting from java BigDecimal to double

我正在使用一個完全基於雙精度的應用程序,並且在將字符串解析為雙精度的一種實用方法中遇到了問題。 我找到了一個修復程序,其中使用 BigDecimal 進行轉換解決了這個問題,但是當我將 BigDecimal 轉換回雙精度時會引發另一個問題:我失去了幾個精度。 例如:

import java.math.BigDecimal;
import java.text.DecimalFormat;

public class test {
    public static void main(String [] args){
        String num = "299792.457999999984";
        BigDecimal val = new BigDecimal(num);
        System.out.println("big decimal: " + val.toString());
        DecimalFormat nf = new DecimalFormat("#.0000000000");
        System.out.println("double: "+val.doubleValue());
        System.out.println("double formatted: "+nf.format(val.doubleValue()));
    }
}

這會產生以下輸出:

$ java test
big decimal: 299792.457999999984
double: 299792.458
double formatted: 299792.4580000000

格式化的 double 表明它在第三位之后失去了精度(應用程序需要那些較低的精度位)。

我怎樣才能讓 BigDecimal 保留那些額外的精度位置?

謝謝!


趕上這個帖子后更新。 有幾個人提到這超出了 double 數據類型的精度。 除非我錯誤地閱讀了這個參考: http : //java.sun.com/docs/books/jls/third_edition/html/typesValues.html#4.2.3那么雙原語的最大指數值為 E max = 2 K-1 -1,標准實現有K=11。 所以,最大指數應該是 511,不是嗎?

您已達到具有該數字的double精度的最大精度。 做不到。 在這種情況下,該值會四舍五入。 BigDecimal的轉換是無關的,並且精度問題是相同的。 例如,請參閱此內容:

System.out.println(Double.parseDouble("299792.4579999984"));
System.out.println(Double.parseDouble("299792.45799999984"));
System.out.println(Double.parseDouble("299792.457999999984"));

輸出是:

299792.4579999984
299792.45799999987
299792.458

對於這些情況, double的小數點后精度超過 3 位。 它們恰好是您的數字的零,這是您可以放入double的最接近的表示。 在這種情況下,它更接近於四舍五入,因此您的 9 似乎消失了。 如果你試試這個:

System.out.println(Double.parseDouble("299792.457999999924"));

你會注意到它保留了你的 9,因為它更接近四舍五入:

299792.4579999999

如果您要求保留號碼中的所有數字,則必須更改對double操作的代碼。 您可以使用BigDecimal代替它們。 如果您需要性能,那么您可能想要探索BCD作為一種選擇,盡管我不知道任何庫。


響應您的更新:雙精度浮點數的最大指數實際上是 1023。不過,這不是您的限制因素。 您的數字超過了表示有效數的 52 個小數位的精度,請參閱IEEE 754-1985

使用此浮點轉換以二進制形式查看您的數字。 指數是 18,因為 262144 (2^18) 最接近。 如果您取小數位並在二進制中增加或減少一位,您會發現沒有足夠的精度來表示您的數字:

299792.457999999900 // 0010010011000100000111010100111111011111001110110101
299792.457999999984 // here's your number that doesn't fit into a double
299792.458000000000 // 0010010011000100000111010100111111011111001110110110
299792.458000000040 // 0010010011000100000111010100111111011111001110110111

問題是double可以容納 15 位數字,而BigDecimal可以容納任意數字。 當您調用toDouble() ,它會嘗試應用舍入模式來刪除多余的數字。 但是,由於輸出中有很多 9,這意味着它們會不斷向上取整為 0,並進位到下一個最高位。

為了盡可能保持精度,您需要更改 BigDecimal 的舍入模式,以便截斷:

BigDecimal bd1 = new BigDecimal("12345.1234599999998");
System.out.println(bd1.doubleValue());

BigDecimal bd2 = new BigDecimal("12345.1234599999998", new MathContext(15, RoundingMode.FLOOR));
System.out.println(bd2.doubleValue());

僅打印那么多數字,以便在將字符串解析回雙精度時,將產生完全相同的值。

可以在Double#toString的 javadoc 中找到一些細節

m 或 a 的小數部分必須打印多少位? 必須至少有一個數字來表示小數部分,並且超過這個數字,但只有盡可能多的數字,以唯一地將參數值與雙精度類型的相鄰值區分開來。 也就是說,假設 x 是由此方法為有限非零參數 d 生成的十進制表示所表示的精確數學值。 那么 d 必須是最接近 x 的雙精度值; 或者如果兩個 double 值同樣接近 x,則 d 必須是其中之一,並且 d 的有效數的最低有效位必須為 0。

如果它完全基於雙打......你為什么使用BigDecimal Double不是更有意義嗎? 如果它的值太大(或精度太高),那么......你不能轉換它; 這就是首先使用 BigDecimal 的原因。

至於為什么它會失去精度,來自javadoc

將此 BigDecimal 轉換為雙精度數。 這種轉換類似於 Java 語言規范中定義的從 double 到 float 的縮小原語轉換:如果這個 BigDecimal 有太大的幅度表示為 double,它將被適當地轉換為 Double.NEGATIVE_INFINITY 或 Double.POSITIVE_INFINITY。 請注意,即使返回值是有限的,此轉換也會丟失有關 BigDecimal 值精度的信息。

您已達到雙精度的最大可能精度。 如果您仍想將值存儲在原語中...一種可能的方法是將小數點前的部分存儲在 long 中

long l = 299792;
double d = 0.457999999984;

由於您沒有用完(這是一個糟糕的單詞選擇)存儲小數部分的精度,因此您可以為小數部分保留更多的精度位數。 這應該很容易做一些舍入等。

暫無
暫無

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

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