數值類型轉換
數值類型間的轉換,可以分成 2 類: 寬轉換 (Widening Conversion)和 窄轉換 (Narrowing Conversion)。
寬轉換指往表示范圍更廣的類型轉換,比如從 int 到 long、從 long 到 float;窄轉換則相反。
整型間轉換
(1)寬轉換
整型間的寬轉換不會產生溢出,無符號整數場景,高位補零;有符號整數場景,高位補符號位。
// C++
int main() {
int8_t i1 = 100;
cout << "int8_t i1: " << bitset<8>(i1) << endl;
cout << "int16_t i1: " << bitset<16>((int16_t) i1) << endl;
int8_t i2 = -100;
cout << "int8_t i2: " << bitset<8>(i2) << endl;
cout << "int16_t i2: " << bitset<16>((int16_t) i2) << endl;
uint8_t i3 = 200;
cout << "uint8_t i3: " << bitset<8>(i3) << endl;
cout << "uint16_t i3: " << bitset<16>((uint16_t) i3) << endl;
return 0;
}
// 輸出結果
int8_t i1: 01100100
int16_t i1: 0000000001100100
int8_t i2: 10011100
int16_t i2: 1111111110011100
uint8_t i3: 11001000
uint16_t i3: 0000000011001000
(2)窄轉換
整型間的窄轉換直接進行高位截斷,只保留低 n 位。比如 16 位的 int16
轉換為 8 位的 int8
,直接保留 int16
類型值的低 8 位作為轉換結果。
// C++
int main() {
int16_t i1 = 200;
cout << "int16_t i1: " << bitset<16>(i1) << endl;
cout << "int8_t i1: " << bitset<8>((int8_t) i1) << endl;
int16_t i2 = -200;
cout << "int16_t i2: " << bitset<16>(i2) << endl;
cout << "int8_t i2: " << bitset<8>((int8_t) i2) << endl;
uint16_t i3 = 300;
cout << "uint16_t i3: " << bitset<16>(i3) << endl;
cout << "uint8_t i3: " << bitset<8>((uint8_t) i3) << endl;
return 0;
}
// 輸出結果
int16_t i1: 0000000011001000
int8_t i1: 11001000
int16_t i2: 1111111100111000
int8_t i2: 00111000
uint16_t i3: 0000000100101100
uint8_t i3: 00101100
(3)無符號整數與有符號整數間的轉換
無符號整數與有符號整數間的轉換規則是:
- 如果兩者二進制位數一致,比如
int8
到uint8
的轉換,則二進制數值不變,只是改變編碼方式; - 如果位數不一致,比如
int16
到uint8
的轉換,則二進制數值,先按照寬轉換或窄轉換規則轉換,再改變編碼方式。
// C++
int main() {
uint8_t i1 = 200;
cout << "uint8_t i1, decimal: " << +i1 << ", binary: " << bitset<8>(i1) << endl;
cout << "int8_t i1, decimal: " << +(int8_t) i1 << ", binary: " << bitset<8>((int8_t) i1) << endl;
int16_t i2 = -300;
cout << "int16_t i2, decimal: " << +i2 << ", binary: " << bitset<16>(i2) << endl;
cout << "uint8_t i2, decimal: " << +(uint8_t) i2 << ", binary: " << bitset<8>((uint8_t) i2) << endl;
return 0;
}
// 輸出結果
uint8_t i1, decimal: 200, binary: 11001000
int8_t i1, decimal: -56, binary: 11001000
int16_t i2, decimal: -300, binary: 1111111011010100
uint8_t i2, decimal: 212, binary: 11010100
整數與浮點數間轉型
(1)寬轉換
整型到浮點數類型的轉換這一方向,為寬轉換:
- 如果浮點數的精度,能夠表示整數,則正常轉換。
- 如果浮點數精度,無法表示整數,則需要近似,會導致精度丟失。
// Java
public static void main(String[] args) {
int i1 = 1234567;
System.out.printf("int i1: %d, float i1: ", i1);
System.out.println((float) i1);
int i2 = 123456789;
System.out.printf("int i2: %d, float i2: ", i2);
System.out.println((float) i2);
}
// 輸出結果
int i1: 1234567, float i1: 1234567.0
int i2: 123456789, float i2: 1.23456792E8
上述例子中,i2=123456789
超過 float
類型能夠表示的精度,所以為近似后的結果 1.23456792E8
。
那么,為什么 123456789
會近似為 1.23456792E8
?
要解釋該問題,首先要把它們轉換成二進制表示:
public static void main(String[] args) {
...
System.out.println("int i2: " + int2BinaryStr(i2));
System.out.println("float i2: " + float2BinaryStr((float) i2));
}
// 輸出結果
int i2: 00000111010110111100110100010101
float i2: 01001100111010110111100110100011
接下來,我們根據 IEEE 浮點數的編碼規則,嘗試將 int i2
轉換成 float i2
:
int i2
的二進制 ,可以寫成 ,對應到 的形式,可以確認 s = 0,E = 26,M = 1.11010110111100110100010101。- float 類型中 k = 8,有,,得出 e = 153,按 k 位無符號編碼表示為 。
- 同理,由 ,但由于 float 類型的 n = 23,而 m 一共有 26 位,因此需要按照 round-to-even 規則,對 0.11010110111100110100010101進行近似,保留 23 位小數,得到 0.11010110111100110100011,所以 m 為
- 最后,將 s、e、m 按照 float 單精度的編碼格式組合起來,就是 ,轉換成十進制,就是
1.23456792E8
。
(2)窄轉換
浮點數類型到整型的轉換這一方向,為窄轉換:
- 如果浮點數的整數部分,能夠用整型表示,則直接舍去小數,保留整數部分。
- 如果超出了整型范圍,則結果為該整型的最大/最小值。
// Java
public static void main(String[] args) {
float f1 = 12345.123F;
System.out.print("float f1: ");
System.out.print(f1);
System.out.printf(", int f1: %d\\n", (int) f1);
float f2 = 1.2345E20F;
System.out.print("float f2: ");
System.out.print(f2);
System.out.printf(", int f2: %d\\n", (int) f2);
float f3 = -1.2345E20F;
System.out.print("float f3: ");
System.out.print(f3);
System.out.printf(", int f3: %d\\n", (int) f3);
}
// 輸出結果
float f1: 12345.123, int f1: 12345
float f2: 1.2345E20, int f2: 2147483647
float f3: -1.2345E20, int f3: -2147483648
浮點數間轉型
(1)寬轉換
單精度 float
到 雙精度 double
為寬轉換,不會出現精度丟失的問題。
對于 ,規則如下:
- s 保持不變。
- 在 E 保持不變的前提下,因為
float
的 k = 8,而double
的 k = 11,所以兩者的 e 會有所不同。 - 在 M 保持不變的前提下,
float
的 n = 23,而double
的 n =52,所以 m 需要低位補 52 - 23 = 29 個 0。
// Java
public static void main(String[] args) {
float f1 = 1.2345E20F;
System.out.print("float f1: ");
System.out.print(f1);
System.out.print(", double f1: ");
System.out.println((double) f1);
System.out.println("float f1: " + float2BinaryStr(f1));
System.out.println("double f1: " + double2BinaryStr((double) f1));
}
// 輸出結果
float f1: 1.2345E20, double f1: 1.2344999897320129E20
float f1: 01100000110101100010011011010000
double f1: 0100010000011010110001001101101000000000000000000000000000000000
(2)窄轉換
double
到 float
為窄轉換,會存在精度丟失問題。
如果 double
值超出了 float
的表示范圍,則轉換結果為 Infinity
:
// Java
public static void main(String[] args) {
double d1 = 1E200;
System.out.print("double d1: ");
System.out.println(d1);
System.out.print("float d1: ");
System.out.println((float) d1);
double d2 = -1E200;
System.out.print("double d2: ");
System.out.println(d2);
System.out.print("float d2: ");
System.out.println((float) d2);
}
// 輸出結果
double d1: 1.0E200
float d1: Infinity
double d2: -1.0E200
float d2: -Infinity
如果 double
值還在 float
的表示范圍內,則按照如下轉換規則:
- s 保持不變。
- 在 E 保持不變的前提下,因為
float
的 k = 8,而double
的 k = 11,所以兩者的 e 會有所不同。 - 對于 M,因為
float
的 n = 23,而double
的 n = 52,所以轉換到 float 之后,需要進行截斷,只保留高 23 位。
// Java
public static void main(String[] args) {
double d1 = 3.267393471324506;
System.out.print("double d1: ");
System.out.println(d1);
System.out.print("float d1: ");
System.out.println((float) d1);
System.out.println("double d1: " + double2BinaryStr(d1));
System.out.println("float d1: " + float2BinaryStr((float) d1));
}
// 輸出結果
double d1: 3.267393471324506
float d1: 3.2673936
double d1: 0100000000001010001000111001111100110000001101000000010101110110
float d1: 01000000010100010001110011111010
最后
本文花了很長的篇幅,深入介紹了計算機系統對數值類型的編碼、運算、轉換的底層原理。
數值類型間的轉換是最容易出現隱藏 bug 的地方 ,特別是無符號整數與有符號整數之間的轉換。所以,很多現代的編程語言,如 Java、Go 等都不再支持無符號整數,根除了該隱患。
另外,浮點數的編碼方式,注定它只能精確表示一小部分的數值范圍,大部分都是近似,所以才有了不能用等號來比較兩個浮點數的說法。
數值類型雖然很基礎,但使用時一定要多加小心。希望本文能夠加深你對數值類型的理解,讓你寫出更健壯的程序。
-
二進制
+關注
關注
2文章
794瀏覽量
41600 -
計算機
+關注
關注
19文章
7425瀏覽量
87722 -
編程
+關注
關注
88文章
3595瀏覽量
93604
發布評論請先 登錄
相關推薦
評論