型態轉換
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;
在這個程式片段中,a
是 int
型態,而寫下的 3.14 預設是 double
,a
的值被提至 double
空間進行運算。
如果運算元都是不大於 int
的整數,自動全部提昇為 int
型態進行運算。下面這個片段通不過編譯:
short a = 1;
short b = 2;
short c = a + b; // possible loss of precision
雖然 a
與 b
都是 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
記得之前說過嗎?如果運算式中包括不同型態,則運算時會以最長的型態為主,以上面的程式而言,b
是 long
型態,於是 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);
}
}