型態轉換

May 21, 2022

型態與變數看似簡單,但每種程式語言可能都有其不同的細節。

浮點數

首先,如果你寫了這個程式片段:

double PI = 3.14;

這個片段編譯時沒有問題,但如果你寫了個程式片段:

float PI = 3.14;

就會得到了 possible loss of precision 的編譯錯誤?這是因為在程式中寫下一個浮點數時,編譯器預設會使用 double 型態。就上圖而言,s要將 double 長度的資料指定給 float 型態變數,編譯器就會很貼心地告訴你 double 型態放到 float 變數,會因為 8 個位元組要放到 4 個位元組而遺失 4 個位元組的資料。

你有兩種方式可以避免這個錯誤,第一個方式是在 3.14 後加上 F,這會告訴編譯器,請用 float 來儲存 3.14 這個值。例如:

float PI = 3.14F;

另一個方式是,明確告訴編譯器,你就是要將 double 型態的 3.14 丟(Cast)到 float 變數中,請編譯器住嘴:

float PI = (float) 3.14;

編譯器看到 double 型態的 3.14 要指定給 float 變數,本來囉嗦地告訴你會遺失精度,但你使用 (float) 語法告訴編譯器,就是要將 double 型態的 3.14 指定給 float 變數,別再囉嗦了,於是編譯器就住嘴不講話了,於是編譯通過,既然你不要編譯器囉嗦了,那執行時期出錯,那後果請自負,也就是如果真的因為遺失精度而發生程式錯誤了,那絕不是編譯器的問題。

整數

再來看整數的部份。如果你寫下:

int number = 10;

這沒有問題。如果你寫下:

int number = 2147483648;

編譯時會得到 integer number too large 的錯誤?也許你以為原因是 int 變數 number 裝不下 2147483648,因為int型態最大值是 2147483647,認為這樣可以解決問題:

long number = 2147483648;

編譯時還是會得到 integer number too large 的錯誤,事實上,並非是 number 裝不下 2147483648(如果是的話,編譯錯誤訊息應該是 possible loss of precision),而是程式中寫下一個整數時,預設是使用不超過int型態長度。2147483648 超出了 int 型態的長度,你要直接告訴編譯器,用 long 來配置整數的長度,也就是在數字後加上個 L

long number = 2147483648L;

如上就可以通過編譯了,方才談到,程式中寫下一個整數時,預設是使用不超過 int 型態的長度,下面的程式可以通過編譯:

byte number = 10;

因為 10 是在 byte 可儲存的範圍中,不過這樣不行:

byte number = 128;

128 超過 byte 可儲存的範圍,於是會使用 int 儲存 128,你要將 int 型態儲存至 byte 變數,就會出現possible loss of precision的編譯錯誤。

提昇(Promote)

再來看運算,如果運算式中包括不同型態數值,則運算時以長度最長的型態為主,其他數值自動提昇(Promote)型態。例如:

int a = 10;
double b = a * 3.14;

在這個程式片段中,aint 型態,而寫下的 3.14 預設是 doublea 的值被提至 double 空間進行運算。

如果運算元都是不大於 int 的整數,自動全部提昇為 int 型態進行運算。下面這個片段通不過編譯:

short a = 1;
short b = 2;
short c = a + b; // possible loss of precision

雖然 ab 都是 short 型態,但 Java 在運算整數時,如果全部的運算元都是不大於 int,那麼一律在 int 的空間中運算,int的運算結果要放到 short,編譯器就又會囉嗦遺失精度的問題,所以你要告訴編譯器,就是要將 int 的運算結果丟到 short,請它住嘴:

short a = 1;
short b = 2;
short c = (short) (a + b);

類似地,以下的程式片段通不過編譯:

short a = 1;
long b = 2;
int c = a + b;  // possible loss of precision

記得之前說過嗎?如果運算式中包括不同型態,則運算時會以最長的型態為主,以上面的程式而言,blong 型態,於是 a 也被提至 long 空間中作運算,long 的運算結果要放到 int 變數c,自然就會被編譯器囉嗦精度遺失了。如果這真的是你想要的,那就叫編譯器住嘴吧!

short a = 1;
long b = 2;
int c = (int) (a + b);

那麼以下你覺得會顯示多少?

System.out.println(10 / 3);

答案是3,而不是 3.333333….,因為 10 與 3 會在 int 長度的空間中作運算,因此不會作浮點數表示,如果想得到 3.333333…的結果,那麼必須有一個運算元是浮點數。例如:

System.out.println(10.0 / 3);

很無聊對吧!好像只是在玩弄語法似地!那麼,稍微看看底下的程式片段有沒有問題?

int count = 0;
while(someCondition) {
    if(count + 1 > Integer.MAX_VALUE) {
        count = 0;
    }
    else {
        count++;
    }
    ...
}

這個程式片段想作的是,在某些情況下,不斷遞增 count 的值,如果 count 超過上限就歸零,在這邊以 int 型態的最大值為上限。程式邏輯看似沒錯,但 count + 1 > Integer.MAX_VALUE 永遠不會是 true,如果 count 已經到了 2147483647,也就是 int 的最大值,此時記憶體中的位元組會是:

01111111 11111111 11111111 11111111

count + 1 會變為:

10000000 00000000 00000000 00000000

位元組第一個位元是 1,在 Java 表示一個負數,上例也就是表示 -2147483648,簡單來講,最後 count + 1 會因為超出了 int 可儲存範圍而溢值,count + 1 > Integer.MAX_VALUE 永遠不會成立。

以下這個例子,你可以思考一下結果為何,並實際編譯並執行,看看結果與你想的是否相同:

public class Main {
    public static void main(String[] args) {
        System.out.println(Integer.MIN_VALUE == -Integer.MIN_VALUE);
    }
}

分享到 LinkedIn 分享到 Facebook 分享到 Twitter