2013年3月29日 星期五

為什麼 long 轉成 float 不會有錯誤?

在資料上顯示,Java 的 primitive type 中,long 的長度是 64-bit、float 長度是 32-bit
但是就資料廣度而言,float 卻比 long 來得大,也就是把 long 直接指給 float 變數時,不使用強制轉型也不會產生任何錯誤。
看起來似乎有點不合理,但其實背後的原因是因為資料的表示方法不同。

首先用以下這段程式碼,可以看出兩個有趣的現象:
1、float 表達的資料最大值遠比 long 來得更大。
2、long 可以直接指定給 float。

System.out.println("Max long: " + Long.MAX_VALUE);
System.out.println("Max float: " + Float.MAX_VALUE);
float f = Long.MAX_VALUE;
System.out.println("Convert to float: " + f);

印出來的結果如下:
Max long: 9223372036854775807
Max float: 3.4028235E38
Convert to float: 9.223372E18

可以從結果看出,long 的最大值約是 1018,而 float 的最大值可以到 1038

原理的詳細解釋可以參考 [1]。概略來說,因為 float 是使用科學記號表示法來表示一個數字
32-bit 的 float 有 1 個 bit 用作正負號、1 個 bit 用作 E,剩下的 bits 才用來表達數字。(這部份我沒有查資料,也許需要驗證)
因此因為表達方式不同,才會導致 float 可以表達的範圍似乎比 long 更大。

但由 [1] 的討論其實也可以看出另一個問題:float 真的比 long 更精細嗎?
可以想像用科學記號表示法來表達數字時,可以表達的範圍會比較廣,但科學記號表示法會省略尾數
在長度比較小的狀況下,float 真的能完整表達 long 能表達的所有數字嗎?
以下的簡易程式碼可以直接告訴我們答案:

float f;
long l = Long.MAX_VALUE;
int count = 10;
while(count > 0) {
  f = l;                     // Convert long to float.
  long tempLong = (long) f;  // Convert float back to long.

  System.out.print("long " + l + " -> convert to float " + f + " -> convert back to long " + tempLong + "\t");
  if(tempLong == l)
    System.out.println("equals.");
  else
    System.out.println("NOT equals.");

  --l;
  --count;
}

執行的結果如下:
long 9223372036854775807 -> convert to float 9.223372E18 -> convert back to long 9223372036854775807 equals.
long 9223372036854775806 -> convert to float 9.223372E18 -> convert back to long 9223372036854775807 NOT equals.
long 9223372036854775805 -> convert to float 9.223372E18 -> convert back to long 9223372036854775807 NOT equals.
long 9223372036854775804 -> convert to float 9.223372E18 -> convert back to long 9223372036854775807 NOT equals.
long 9223372036854775803 -> convert to float 9.223372E18 -> convert back to long 9223372036854775807 NOT equals.
long 9223372036854775802 -> convert to float 9.223372E18 -> convert back to long 9223372036854775807 NOT equals.
long 9223372036854775801 -> convert to float 9.223372E18 -> convert back to long 9223372036854775807 NOT equals.
long 9223372036854775800 -> convert to float 9.223372E18 -> convert back to long 9223372036854775807 NOT equals.
long 9223372036854775799 -> convert to float 9.223372E18 -> convert back to long 9223372036854775807 NOT equals.
long 9223372036854775798 -> convert to float 9.223372E18 -> convert back to long 9223372036854775807 NOT equals.

由結果可以看出,long 雖然持續有變化,但轉成 float 再轉回 long 後,表達的都是同一個數字,而且轉換時 Java 沒有回報任何錯誤
這個結果是蠻讓人訝異的,Java 一向給人的感覺是屬於嚴謹的程式語言,但這似乎是個例外,從 long 轉成 float 時造成的誤差會被忽略。
因此如果要使用 long 跟 float 轉換的時候,要仔細注意數字的變化,或者一開始就不要做這種資料轉換了。

參考資料:
1、How does a long fit in a float?

沒有留言: