2013-03-15 169 views
7

我試過下面的代碼。但在使用BigDecimal進行減法時會得到不同的結果。java bigdecimal subraction失敗

double d1 = 0.1; 
    double d2 = 0.1; 
    System.out.println("double result: "+ (d2-d1)); 

    float f1 = 0.1F; 
    float f2 = 0.1F; 
    System.out.println("float result: "+ (f2-f1)); 

    BigDecimal b1 = new BigDecimal(0.01); 
    BigDecimal b2 = new BigDecimal(0.01); 

    b1 = b1.subtract(b2); 
    System.out.println("BigDecimal result: "+ b1); 

結果:

double result: 0.0 
float result: 0.0 
BigDecimal result: 0E-59 

我還在這方面的工作。任何人都可以請澄清。

+0

0E-59仍然是零,並且規模是從輸入的刻度的。 – EJP 2013-03-16 00:24:05

+0

答案涵蓋了原因,下面是一個簡單的提示:如果您使用BigDecimal,則需要一些有用的縮放比例,因此,當您調用具有某個double值的C-tor時,始終需要包含MathContext。 – bestsss 2013-03-17 11:05:38

回答

4

有意思的是,這些值看起來是相等的,減法確實會給你零,但它似乎只是打印代碼的一個問題。下面的代碼:

import java.math.BigDecimal; 
public class Test { 
    public static void main(String args[]) { 
     BigDecimal b1 = new BigDecimal(0.01); 
     BigDecimal b2 = new BigDecimal(0.01); 
     BigDecimal b3 = new BigDecimal(0); 
     if (b1.compareTo(b2) == 0) System.out.println("equal 1"); 
     b1 = b1.subtract(b2); 
     if (b1.compareTo(b3) == 0) System.out.println("equal 2"); 
     System.out.println("BigDecimal result: "+ b1); 
    }       
} 

輸出equal消息,表明該值相同的,你得到當你減去。

您可以嘗試將其作爲錯誤提示並查看Oracle回來的內容。很可能他們會說0e-59仍然是零,所以不是錯誤,或者BigDecimal documentation page上描述的相當複雜的行爲是按照預期工作的。具體而言,指出:

在可區分的BigDecimal值和此轉換的結果之間存在一對一映射。也就是說,作爲使用toString的結果,每個可區分的BigDecimal值(非標度值和標度)都具有唯一的字符串表示形式。如果使用BigDecimal(String)構造函數將該字符串表示轉換回BigDecimal,則原始值將被恢復。

這一事實,原來的值需要收回意味着toString()需要爲每一個規模,這就是爲什麼你得到0e-59唯一字符串。否則,將字符串轉換回BigDecimal可能會給你一個不同的值(非縮放值/縮放元組)。

如果你真的零顯示爲「0」,無論規模,你可以使用類似:

if (b1.compareTo(BigDecimal.ZERO) == 0) b1 = new BigDecimal(0); 
+1

我不認爲這是一個錯誤; 'BigDecimal.toString'打印標準表示,所以它必須以明確的方式表示比例因子和非標度值。比例因子是59,因爲減法輸入的比例因子是59 ... – 2013-03-15 09:52:19

+0

@Oli,實際上你是對的,這種行爲是每個doco有意識的。 – paxdiablo 2013-03-15 10:05:02

2

你必須得到的返回值:

BigDecimal b3 = b1.subtract(b2); 
System.out.println("BigDecimal result: "+ b3); 
+0

他正在做'b1 = b1.subtract(b2);' – Austin 2013-03-15 09:24:15

+0

剛剛編輯。對不起 – user1514499 2013-03-15 09:24:43

+0

沒問題!在編輯問題後,您遇到了浮點數精度有限的問題,這個答案顯然不能解決問題:) – ghdalum 2013-03-15 09:35:38

7

從字符串中使用構造函數:b1 = new BigDecimal("0.01");

Java loss of precision

(幻燈片23) http://strangeloop2010.com/system/talks/presentations/000/014/450/BlochLee-JavaPuzzlers.pdf

+1

確實。但是這並沒有回答這個問題。爲什麼差異打印奇怪? – 2013-03-15 09:28:19

+0

你是說b1和b2會被設置爲不同的值,儘管它們的創建方式完全一樣嗎? – paxdiablo 2013-03-15 09:28:24

+0

這就是BigDecimal的工作方式。減法的結果是BigDecimal,它存儲0,但它具有從「0.01」構造的「bad」BigDecimal的「繼承」尺度(e-59)。 – 2013-03-15 09:39:36

2

所以真正的問題是:用下面的代碼,

BigDecimal b1 = new BigDecimal(0.01); 
BigDecimal b2 = new BigDecimal(0.01); 
b1 = b1.subtract(b2); 

爲什麼b1.toString()評估爲"0E-59",而不是像"0.0""0E0"或者只是"0"


其原因是toString()打印BigDecimal規範格式。有關更多信息,請參閱BigDecimal.toString()

在末端,0E-590.0 - 它是0*10^59其數學計算結果爲0,所以,意想不到的結果是BigDecimal的內部表示的問題。

要獲得float或double值,使用

b1.floatValue()); 

b1.doubleValue()); 

兩個評估爲0.0

+0

但是,我希望b1和b2完全一樣,都不是0.01。所以減法應該完全產生0. – paxdiablo 2013-03-15 09:29:25

+0

正確 - 我承認我沒有回答這個部分... – 2013-03-15 09:30:00

1

這是一個已知問題BigDecimal(double val)API 此構造函數的結果可能有點不可預知。雖然它看起來真的很奇怪,在這種interpertation。實際原因是新的BigDecimal(0.01)產生一個BigDecimal與大約值

0.01000000000000000020816681711721685132943093776702880859375 

具有長精度,所以減法的結果有很長的精度也。

無論如何,我們可以解決的 「問題」 這種方式

BigDecimal b1 = new BigDecimal("0.01"); 
BigDecimal b2 = new BigDecimal("0.01"); 

,或者我們可以使用一個構造函數設置精度

BigDecimal b1 = new BigDecimal(0.01, new MathContext(1)); 
BigDecimal b2 = new BigDecimal(0.01, new MathContext(1)); 
+0

什麼是已知問題? – 2013-03-15 09:29:44

+0

BigDecimal(double val)API這個構造函數的結果可能會有些不可預知.. – 2013-03-15 09:32:58

+1

通過「不可預測」,我希望它們不意味着「非確定性」... – 2013-03-15 09:33:41

2

BigDecimal(double val)

1.結果這個構造函數可能有點不可預測。有人可能會認爲在Java中編寫新的BigDecimal(0.1)會創建一個大小等於0.1的012DecimalDecimal(非縮放值爲1,其中 的比例爲1),但實際上它等於 0.1000000000000000055511151231257827021181583404541015625。這是因爲0.1不能完全表示爲雙精度(或者對於任何有限長度的二元分數)。因此,傳遞給構造函數的值 並不完全等於 0.1,儘管如此。另一方面,字符串構造函數是完全可預測的:編寫新的BigDecimal(「0.1」)會創建一個BigDecimal,正如我們所期望的那樣,它等於0.1,即 。因此,一般建議 使用String構造函數而不是此 之一。

3.當double必須用作BigDecimal的源時,請注意,此構造函數提供了精確的轉換;它不會給出與使用 Double.toString(double)方法,然後使用BigDecimal(String) 構造函數將double轉換爲字符串相同的結果 。要獲得該結果,請使用靜態valueOf(double) 方法。

+2

確實。但是這並沒有回答這個問題。爲什麼差異打印奇怪? – 2013-03-15 09:33:00

11

[有很多答案在這裏告訴你,二進制浮點不能完全代表0.01,並暗示你所看到的結果是有點不精確。雖然第一部分是真的,但這不是真正的核心問題。]


答案是 「0E-59」 等於0。回想一下,一個BigDecimal是未測量的值和小數比例因子的組合:

System.out.println(b1.unscaledValue()); 
System.out.println(b1.scale()); 

顯示:

0 
59 

的非標度值 0,如預期。該「奇」刻度值是簡單地爲0.01非精確浮點表示的十進制擴展的僞影:

System.out.println(b2.unscaledValue()); 
System.out.println(b2.scale()); 

顯示:

1000000000000000020816681711721685132943093776702880859375 
59 

下一個明顯的問題是,爲什麼沒有按't BigDecimal.toString只是爲了方便而將b1顯示爲「0」?答案是字符串表示必須是明確的。從Javadoc for toString

在可區分的BigDecimal值與此轉換的結果之間存在一對一映射。也就是說,作爲使用toString的結果,每個可區分的BigDecimal值(非標度值和標度)都具有唯一的字符串表示形式。如果該字符串表示使用BigDecimal(String)構造函數轉換回BigDecimal,則原始值將被恢復。

如果它剛剛顯示「0」,那麼你將無法回到這個確切的BigDecimal對象。

+0

我試着用BigDecimal b1 = new BigDecimal(0.05); \t \t BigDecimal b2 = new BigDecimal(0.01);獲得BigDecimal結果1:4000000000000000256739074444567449972964823246002197265625 BigDecimal結果2:59.與您的相似... – user1514499 2013-03-15 09:47:30

+0

@ user1514499:好的,那是一個不同的問題!但答案是相關的;您無法在二進制浮點中精確地表示「0.01」或「0.05」。他們近似。 – 2013-03-15 09:49:16

+0

該死的,因爲我正在編輯最後一個片段(雙向轉換的東西)到我的答案中,我想出了空氣,發現你已經釘了它:-) – paxdiablo 2013-03-15 10:13:40

1

使用這樣的:

BigDecimal b1 = BigDecimal.valueOf(0.01); 
BigDecimal b2 = BigDecimal.valueOf(0.01); 

b1 = b1.subtract(b2); 
System.out.println("BigDecimal result: "+ b1);