2011-12-06 101 views
4

下無法編譯:的Math.random()與精度損失好奇心

int result = Math.random() + 1; 

error: possible loss of precision 
    int result = Math.random() + 1; 
          ^
    required: int 
    found: double 

但以下確實編譯:

int result = 0; 
result += Math.random() + 1; 

爲什麼?

將可編譯代碼放入嵌套循環中,每次迭代都會使結果增加1,因爲Math.random()始終返回值小於1的double值,並且在將小數部分添加到整數時由於精確度損失而丟失。運行下面的代碼,看到了意想不到的結果:

public class MathRandomCuriosity 
{ 
    public static void main(String[] args) 
    { 
    int result = 0; 
    for (int i = 0; i < 10; i++) 
    { 
     // System.out.println(result); 
     for (int j = 0; j < 20; j++) 
     { 
     // System.out.println(result); 
     for (int k = 0; k < 300; k++) 
     { 
      // System.out.println(result); 
      for (int m = 0; m < 7000; m++) 
      { 
      result += Math.random() + 1; 
      } 
     } 
     } 
    } 
    System.out.println(result); 
    } 
} 

隨着10 * 20 * 300 * 7000 = 42000000次的迭代的結果應該是42000000。但它不是!結果變化,即42,000,007與42,000,006與42,000,010等相關。

爲什麼?

順便說一句...這不是任何地方正在使用的代碼,它來自我在簡報中收到的測驗。嵌套循環的原因是我可以間隔地查看結果的值。

+0

在一般情況下,這個問題已經被多次解答。 – Woot4Moo

+10

編寫'for(int i = 0; i <42000000; i ++)的奇怪方法' –

+0

嘗試將Math.random()調用先轉換爲整數。目前,代碼中的'1'字面意味着被隱式轉換爲double,這意味着某處會丟失精度。 –

回答

12

+=這樣的指定運算符做了隱式轉換。

注意:在這種情況下,Math.random()將每次都舍入爲0,這是嚴重的精度損失。 ;)

但是Math.random() + 1有一個很小的機率被舍入到2。 1.999999將四捨五入爲1,但1.9999999999999999將四捨五入爲2(但運營商double +而非投至int)。

long l = Double.doubleToLongBits(1.0); 
double d0_999etc = Double.longBitsToDouble(l -1); 
System.out.println("The value before 1 is " +d0_999etc+" cast to (int) is "+ (int) d0_999etc); 
System.out.println("The value before 1, plus 1 is " +(1+d0_999etc)+" cast to (int) is "+(int)(1 +d0_999etc)); 

打印

The value before 1 is 0.9999999999999999 cast to (int) is 0 
The value before 1, plus 1 is 2.0 cast to (int) is 2 
+2

非常小補充:並不是Math.random()被截斷爲0,而是「Math.random()+ 1」被截斷爲1. 1被提升爲double,然後被添加,然後被截斷。不是隨機數 –

+0

當我發現'System.out.println(0.99999999999999999);'是'1.0'。+1是因爲速度太快而回答的時候 – Jomoos

+0

@SeanOwen,我的觀點是有一個錯誤的假設,因爲'Math。隨機的()'總是四捨五入爲0,'Math.random()+ 1'將總是舍入爲1. –

1

一個IEEE數學實現的細節指出的精度和不可靠的結果損失從雙/浮到整數轉換。比如有一次,我發現代碼,比較浮點數:

int x = 0; 
if (a <= b) 
{ 
    x = y; 
} 
if (a > b) 
{ 
    x = z; 
} 

有時結果x == 0例如數由既非if語句抓住,我不得不重寫代碼:

int x = 0; 
if (a <= b) 
{ 
    x = y; 
} 
else 
{ 
    x = z; 
} 
+0

我猜這是因爲零簽名。 (即存在一個「+0」和「-0」,它們在浮點系統中有不同的表示)有趣的是看看這些數字如何與比較運算符交互。 – Alderath

+0

是的,甚至有方法可以調整零接近零。最初的代碼是由一位科學家撰寫的,所以我必須向他解釋IEEE數學如何違反代數表達式的一些基本租戶。 –

+0

雖然這不能回答我的兩個問題,但它非常有趣且有用!謝謝! –

-1

由定義Math.random()返回double結果從0.0到1.0。操作Math.random() + 1創建了雙重結果,然後將其分配給int變量,從而生成整數結果。在每次迭代中,結果爲1,除非Math.random()剛好返回1.0。發生的可能性非常低,但仍然存在。它在統計上似乎是1/6000。這是一些循環迭代將2添加到結果中的原因。

因此,這裏不會失去精度。一切都按規格進行。

+2

這個答案是錯誤的。根據規範,Math.random返回一個double值,例如'0 <= value <1'。因此,它不能完全返回1.0。 – Alderath