二進制那些事
移位運算
移位運算指的是將二進制數值的各數位進行左右移位的運算。左移空出來的低位要進行補0操作,右移空出來的高位要進行怎樣的操作,我們會在後面說明。
我們發現,左移兩位相當於對39乘以4,右移兩位相當於除4,也就是說計算機用移位算法來表示數據的乘除運算。
補數
剛才之所有沒有介紹相關右移的內容,是因為用來填充右移後空出來的高位的數值,有 0 和 1 兩種形式。要想區分什麽時候補0什麽時候補1,只要掌握了用二進制數表示負數的方法即可。
二進制數中表示負數時,一般會把最高位作為符號來使用,也就是說,最高位是符號位。正數的符號位用0表示,負數的符號位用1表示。舉個栗子,1的二進制數是0000 0001 ,那麽,-1的二進制數是多少呢?難道是1000 0001,1000 0001+0000 0001 結果不是0,說明這個結果是錯的。為此,在表示負數時就需要使用補數
邏輯右移和算術右移
在介紹完補數後,讓我們返回到右移這個話題,右移之後在最高位有補0和補1兩種情況。當二進制數的值表示圖形模式而非數值時,移位後在最高位補0,這是邏輯右移。將二進制數值作為帶符號的數值進行運算時,移位後要在最高位填充前符號位的值( 0 或 1 ),這是算術右移。
現在我們來看一個右移的例子。將-8(1111 1000)右移兩位。這時,邏輯右移的情況下結果會是 0011 1110,也就是十進制數62,顯然不是-2,而在算術右移的情況下,結果會變成1111 1110 ,用補數表示就是-2,和真實結果相同。需要註意的是只有在右移時才區分邏輯移位和算術移位。
二進制數表示小數
通過上述介紹,我們對整數的二進制表示方式做了說明。由於計算機內部所有信息都是以二進制數的形式來處理,因此在這一點上,整數和小數並無差別。不過,使用二進制數表示整數和小數的方法卻有很大的不同。
由於二進制數表示的小數的數值範圍是有限的,並不能表示所有的十進制小數。例如:小數點後3位用二進制數表示時的數值範圍為0.000~0.111,但是只能表示有限的十進制小數,如下圖所示。
為了加深大家印象,舉一個更加實際的栗子:將0.1累加100次最終結果是10.000002,不是10。
public class TestBinary { public static void main(String[] args) { float sum=0.0f; for (int i = 0; i < 100; i++) { sum += 0.1; } System.out.println(sum); } }
浮點數
現在,我們應該知道僅僅依靠紙面上的二進制數是表示不了全部小數。那麽,計算機實際上是以什麽樣的表現形式來處理小數的呢?
目前,計算機提供了單精度浮點數和雙精度浮點數來表示小數形式。單精度浮點數用32位表示全體小數,而雙精度浮點數用64位表示。它們都是由符號、尾數和指數組成。
接下來,讓我們一起看一下如果將0.1用單精度浮點數來表示,累加100次的結果是否是10,可以結果又一次出乎意料,結果是10.000002。
public class TestBinary {
public static void main(String[] args) {
float sum=0.0f;
float step = 0.1f;
for (int i = 0; i < 100; i++) {
sum += step;
}
System.out.println(sum);
}
}
如何避免計算機計算出錯
我們知道,無論是用紙面二進制還是浮點數表示小數,都存在計算出錯的可能性,那麽我們該如何避免這種問題呢?
首先是回避策略,即無視這些問題。有時候一些微小的偏差並不會造成什麽問題。其次,把小數轉換成整數來計算。還是以0.1累加100次為例,將0.1擴大10倍後累加100次,最後把結果除以10就可以了。
public class TestBinary {
public static void main(String[] args) {
int sum=0;
int step = 1;
for (int i = 0; i < 100; i++) {
sum += step;
}
System.out.println(sum/10);
}
}
二進制那些事