2017-05-30 102 views
7
System.out.println(2.00-1.10) 

爲什麼投射會在java中產生正確的結果?

兩個輸出相同的結果0.8999999999999999,但

System.out.println((float)(2.00-1.10)) 

輸出0.9

也許默認情況下Java執行雙倍計算,那麼爲什麼向下轉換會更正結果?

如果1.1在雙那麼爲什麼

System.out.println((double)(1.10))輸出1.1只有轉化爲1.100000000000001

編輯:爲了得到這種情況,我們需要了解這兩個答案。首先,規範表示在較低級別上實際上是不同的。接下來如何將toString的返回值更改/四捨五入/與參數的最接近的兩倍相匹配。

+2

閱讀[this](https://stackoverflow.com/questions/588004/is-floating-point-math-broken) –

+0

「也許默認情況下Java執行雙倍計算」,因爲'2.00'和'默認情況下,1.10'被認爲是雙打(除非你明確聲明它們應該是浮動的,就像加上'f'後綴),所以'double + double = double'的結果解釋了爲什麼要投射'(double)(2.00-1.10)不會改變任何東西。 – Pshemo

+0

@biziclop,雖然涉嫌重複與此問題有關;這個版本的問題有所不同。所謂的重複不會解釋這個問題的最基本的方面:爲什麼向下轉換解決了這個問題? –

回答

4

這個文檔沒有特別好的解釋,但是Double.toString(double)本質上是對它產生的輸出進行一些四捨五入。在整個Java SE中都使用了Double.toString算法,包括例如PrintStream.println(double)System.out。文檔說這個:

多少位必須打印爲一個小數部分?必須至少有一位數字來表示小數部分,並且除此之外還必須包含儘可能多的數字,但數量必須與唯一地區分參數值與類型爲double的相鄰值所需的位數相同。也就是說,假設x是由該方法對有限非零參數產生的十進制表示法所表示的確切數學值d。然後d必須是最接近xdouble值;或者如果兩個double值同樣接近X,然後d必須是其中之一,並d的有效數必須0的至少顯著位。

換句話說,它表示toString的返回值不一定是參數的精確十進制表示形式。唯一的保證是(粗略地說)參數比任何其他double值更接近返回值。

因此,當您執行類似System.out.println(1.10)1.10的打印時,這並不意味着傳入的值實際上等於基本10值1.10。相反,本質上會發生以下情況:

  • 首先,在編譯過程中,字面1.10檢查並四捨五入產生最接近double值。 (它說在JLS here此規則是在Double.valueOf(String)例如詳細爲double。)
  • 第二,當程序運行時,Double.toString產生一些十進制值,其在先前的步驟中產生的double值接近的String表示比任何其他double值。

恰巧在第二步轉換爲String經常產生一個String,它與第一步中的文字相同。我會假設這是設計。無論如何,文字例如1.10不會產生一個double的值,它正好等於1.10

您可以使用BigDecimal(double)構造發現一個double的實際值(或float,因爲他們總能適合在double):

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

// 0.899999999999999911182158029987476766109466552734375 
System.out.println(new BigDecimal((double) (2.00 - 1.10))); 
// 0.89999997615814208984375 
System.out.println(new BigDecimal((float) (2.00 - 1.10))); 

你可以看到,無論結果實際上是0.9。在這種情況下,Float.toString碰巧產生0.9Double.toString不會產生或多或少的巧合。


作爲一個附註,(double) (2.00 - 1.10)是一個冗餘的演員。 2.001.10已經是double文字,所以評估表達式的結果已經是double。此外,要減去float,則需要投射兩個操作數,如(float) 2.00 - (float) 1.10或使用float文字,如2.00f - 1.10f(float) (2.00 - 1.10)僅將結果轉換爲float

+0

那些多餘的演員是爲了檢查他們是否有所作爲。沒有,我想。 –

+0

是的,沒有區別。不過,你做這件事的理由非常好。相關的規範是:*「如果浮點型文字後綴爲ASCII字母」F「或」f「,則浮點型文字的類型爲」float「;否則其類型爲」double「,並且可以選擇後綴ASCII字母'D'或'd' *「。 (來自[此處](https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.10.2),緊接着兩個灰色方框,分別描述浮點文字的語法。) – Radiodef

8

0.9不能表示爲double或float。浮點計算的內部細節已經在SO上的各種帖子中得到了回答,例如:Is floating point math broken?

在你的具體的例子,你可以看到雙和浮動是最接近0.9與此代碼:

System.out.println(new BigDecimal(0.9d)); 
System.out.println(new BigDecimal(0.9f)); 

,其輸出的規範double和float的0.9表示:

0.90000000000000002220446049250313080847263336181640625
0.89999997615814208984375

現在,當你計算2.0 - 1.1,結果是:

System.out.println(new BigDecimal(2.0-1.1)); 

0.899999999999999911182158029987476766109466552734375

你可以看到它是不是 0.9規範的表示,因此你會得到不同的結果。

然而漂浮精度不那麼好,並且:

System.out.println(new BigDecimal((float) (2.0-1.1))); 

0.89999997615814208984375

返回相同的數量0.9f的規範表示。

+0

只是一個評論:'BigDecimal'沒有接受'float'參數的構造函數。 Java只是把它重新放回一個'double' ... –

+1

@UsagiMiyamoto是的,但沒關係,當你將一個float賦給double值時,你會得到相同的數字。 – assylias

+0

@Kenster錯字 - 謝謝。 – assylias

1

您正在混淆變量的實際值與如何在屏幕上顯示值。在您的示例中,toString方法用於在顯示值之前將值轉換爲字符串。這對多少個地方使用默認值(這與doublefloat不同)。下面我明確設置多少地方顯示:

double d1 = 2 - 1.1; 
float f1 = (float) d1; 

System.out.println(String.format("d1 = %.10f f1 = %.10f", d1, f1)); 
System.out.println(String.format("d1 = %.20f f1 = %.20f", d1, f1)); 
System.out.println(String.format("Using toString, d1 = %s f1 = %s", "" + d1, "" + f1)); 

給出了輸出:

d1 = 0.9000000000 f1 = 0.8999999762 
d1 = 0.89999999999999990000 f1 = 0.89999997615814210000 
Using toString, d1 = 0.8999999999999999 f1 = 0.9 

這表明float值是不太準確的double之一。

+0

我認爲'toString'圍繞'浮動'的價值,但爲什麼不'雙'? –

+1

它將兩者都舍入,但達到不同的有效數字位數。由於「浮動」不夠準確,所以它更多。這個例子的結果看起來不同,這只是特定值的一個意外。浮點值幾乎總是近似值。值0.8999999999999999和0.9可能看起來不同,但它們非常接近相同的值。幾乎所有的實際目的,它們都是相同的價值。如果字符串表示看起來很重要,則應使用四捨五入爲適當的有效數字的格式。 – pcarter

相關問題