原碼、反碼和補碼
原碼
原碼是最直觀的表示方法,用第一位表示符號(0為正,1為負),其餘位表示數值本身。
例如:
+5 的原碼是 0101(假設我們使用4位二進位制數)
-5 的原碼是 1101
反碼
反碼用於簡化計算機中的減法操作。對於正數的反碼與其原碼相同;對於負數,其反碼是將原碼中除符號位外的所有位取反(0變1,1變0)。
例如:
+5 的反碼是 0101
-5 的反碼是 1010
補碼
補碼也是用於簡化計算機中的減法操作。正數的補碼與其原碼相同;負數的補碼是其反碼加1。
例如:
+5 的補碼是 0101
-5 的補碼是 1011
Java中的表示
Java中的整數預設是採用補碼形式表示的。
示例程式
以下是一個Java程式,它將執行位操作並顯示結果,我們可以用這些結果來驗證Java中整數的表示方式。
public class BitwiseOperations {
public static void main(String[] args) {
int positiveNumber = 5;
int negativeNumber = -5;
// 輸出原碼、反碼和補碼
System.out.println("正數 +5 的二進位制表示(補碼形式,Java中的實際表示): " + Integer.toBinaryString(positiveNumber));
System.out.println("負數 -5 的二進位制表示(補碼形式,Java中的實際表示): " + Integer.toBinaryString(negativeNumber));
// 手動計算反碼和補碼進行驗證
String positiveBinary = Integer.toBinaryString(positiveNumber);
String negativeBinary = Integer.toBinaryString(negativeNumber);
// 正數的原碼、反碼、補碼相同
System.out.println("正數 +5 的原碼: " + positiveBinary);
System.out.println("正數 +5 的反碼: " + positiveBinary);
System.out.println("正數 +5 的補碼: " + positiveBinary);
// 負數的原碼
String negativeOriginal = "1" + positiveBinary.substring(1);
System.out.println("負數 -5 的原碼: " + negativeOriginal);
// 負數的反碼(除符號位外取反)
String negativeOnesComplement = negativeOriginal.chars()
.mapToObj(c -> c == '0' ? "1" : "0")
.collect(Collectors.joining());
System.out.println("負數 -5 的反碼: " + negativeOnesComplement);
// 負數的補碼(反碼+1)
int negativeTwosComplement = Integer.parseInt(negativeOnesComplement, 2) + 1;
System.out.println("負數 -5 的補碼: " + Integer.toBinaryString(negativeTwosComplement));
// 驗證Java中的表示
System.out.println("Java中的負數 -5 補碼錶示(預期與上面計算得到的補碼相同): " + negativeBinary);
}
}
手工計算與程式輸出比對
當你執行上述程式時,觀察到輸出結果,並將其與手工計算的結果進行比對。對於正數,原碼、反碼、補碼都是相同的,而對於負數,Java中的表示(即補碼)應該與你手工計算得到的補碼一致。
這表明Java確實使用的是補碼來表示整數。
Java中的變數具有特定的作用域
它決定了變數的可見性和生命週期。下面我將編寫一個Java示例程式,在不同的作用域中定義同名的變數,並觀察輸出的值。
public class ScopeExample {
// 類變數
static int variable = 10;
public static void main(String[] args) {
// 方法中的區域性變數,遮蔽了類變數
int variable = 20;
System.out.println("方法中的區域性變數: " + variable); // 輸出區域性變數的值
// 在方法內部定義一個程式碼塊,建立一個新的作用域
{
// 在程式碼塊中定義一個同名的區域性變數,遮蔽了外層的區域性變數
int variable = 30;
System.out.println("程式碼塊中的區域性變數: " + variable); // 輸出程式碼塊中的區域性變數
}
// 程式碼塊結束後,這裡的變數引用的是最外層的區域性變數
System.out.println("程式碼塊外的區域性變數: " + variable); // 輸出方法中的區域性變數
// 使用類名呼叫類變數,避免遮蔽問題
System.out.println("類變數: " + ScopeExample.variable); // 輸出類變數的值
}
}
輸出結果:
方法中的區域性變數: 20
程式碼塊中的區域性變數: 30
程式碼塊外的區域性變數: 20
類變數: 10
類變數variable被初始化為10。
在main方法中,定義了一個同名的區域性變數variable,其值為20,這遮蔽了類變數。
在main方法內部的一個程式碼塊中,定義了另一個同名的區域性變數variable,其值為30,這遮蔽了外層的區域性變數。
當程式碼塊結束時,程式碼塊外的variable仍然引用的是main方法中的區域性變數,因此輸出20。
為了訪問被遮蔽的類變數,我們使用了ScopeExample.variable,輸出類變數的值,即10。
示例:
java中的型別轉換
看著這個圖,再查查Java中每個資料型別所佔的位數,和表示數值的範圍,你能得出什麼結論
可以總結出以下關於 Java 中基本資料型別的位寬、取值範圍及其轉換關係的結論:
byte:
位寬: 8 位(1 位元組)
取值範圍: -128 到 127
short:
位寬: 16 位(2 位元組)
取值範圍: -32768 到 32767
int:
位寬: 32 位(4 位元組)
取值範圍: -2147483648 到 2147483647
long:
位寬: 64 位(8 位元組)
取值範圍: -9223372036854775808 到 9223372036854775807
float:
位寬: 32 位(4 位元組)
取值範圍: 約 ±3.40282347E38F(有效數字約6~7位)
double:
位寬: 64 位(8 位元組)
取值範圍: 約 ±1.79769313486231570E308(有效數字約15位)
從圖中可以看出,不同資料型別之間的轉換關係如下:
無精度損失轉換:
byte -> short -> int -> long
char -> int -> long
這些轉換不會導致數值精度的丟失,因為目標型別的位寬至少與源型別相同或更大。
有精度損失轉換:
int -> float
long -> double
float -> double
這些轉換可能會導致數值精度的丟失,特別是當從一個整數型別轉換為浮點數型別時,可能會出現舍入誤差。
浮點數的表示方法
計算機使用 IEEE 754 標準來表示浮點數,這種表示方法包括三個部分:符號位、指數位和小數位(也稱為尾數位或有效數字位)。以下是 IEEE 754 標準的一些關鍵點:
符號位:決定數值的正負,0 表示正數,1 表示負數。
指數位:表示 2 的冪次,用於縮放尾數。
尾數位:表示實際的數字資訊,通常是 2 的冪次的分數。
精度限制
尾數位的限制:由於儲存空間的限制,double 型別通常有 52 位用於尾數(加上一個隱含的前導 1,總共 53 位精度),這意味著它不能精確表示所有的十進位制小數。例如,0.1 在二進位制中是一個無限迴圈小數,因此不能完全精確地表示為 double。
指數位的限制:指數位決定了數值的範圍,但是也有最大和最小的限制。超出這個範圍,數值就會變成無窮大或者非常接近零。
舍入誤差
當一個數不能精確表示時,必須進行舍入。IEEE 754 標準定義了幾種舍入模式,例如向最接近的值舍入、向上舍入或向下舍入。這種舍入過程會導致誤差。
運算誤差累積
在進行多次浮點運算時,舍入誤差會累積,導致最終結果與數學上的精確結果有較大偏差。
示例
public class FloatingPointPrecision {
public static void main(String[] args) {
double a = 0.1;
double b = 0.2;
double sum = a + b;
System.out.println("The sum of 0.1 and 0.2 is: " + sum); // 輸出可能不是 0.3
}
}
a + b 的結果可能不會是精確的 0.3,因為 0.1 和 0.2 在二進位制表示中都不是精確的,它們的和自然也不會是精確的。
在需要高精度計算的應用中,通常會使用 BigDecimal 類或其他專門的數值計算庫來避免這些問題。