計算機數學小書1-原碼,反碼和補碼
如果你也是電子,自動化或者計算機專業畢業 ,多數跟我一樣的感觸,記得上大學時,應該有不少於5門課在不停地重複講原碼,反碼和補碼,像大學計算機基礎,C語言程序設計,微機原理,單片機原理和數字邏輯之類的課,中間自己也在看CSAPP(《深入理解計算機系統》),被一堆的推導公式搞懵逼了,始終沒明白為什麼最後計算機選的是補碼這種神奇但又極其詭異的編碼方式,當時就只有一種感覺,一定是發明計算機大師是變態,就為了出題考我們,才有此發明。
最近幾年,趁著給學生上單片機以及嵌入式還有數字邏輯的課程,重新梳理一些底層基礎知識,因為受之前一個學電子的好兄弟影響,更喜歡用所以然和原生態的角度去理解一個東西,所以清閑的這幾年對之前很多模糊的概念重新進行梳理。
您坐好,聽我慢慢嘮。
1. 一段代碼引發的血案
在開始將計算機中整數的編碼前,我們先看一個簡單的C語言代碼
#include <stdio.h>int main(void){ char a,b; char c; a = -100; b = -100; c = a + b; printf("c=%d", c); return 0;}//question:what is printed in console?
這段代碼會列印輸出什麼呢?
-200??
再猜,再猜,
猜對,我請擼串
別急哈
猜不到,還不趕快擼代碼,直接用Dev C++跑一下,不就行啦。
為什麼-100+-100=一個正數56?????
少男,少女,帶著這個疑問,讓我們開始計算機整數的編碼之旅吧!
2. 整數編碼的圖形式演化
無符號整數,像C語言里的unsinged char/int/short我這裡就不過多介紹,簡單點說就是二進位與十進位之間的轉化計算而已。重點介紹一下有符號數char/int的編碼,大家在計算機相關書籍上看的,一般是這樣的一段話:
原碼錶示法是整數的一種簡單的表示法,符號位用0表示正號,用1表示負號,數值一般用二進位形式表示。
整數的反碼可由原碼得到,如果是正數,則反碼與原碼一樣;如果是負數,則反碼是對它的原碼(符號位除外)各位取反而得到的。整數的補碼可由原碼得到。如果是正數,則補碼與原碼一樣;如果是負數,則補碼是在反碼基礎上,末位加1而得到。
我估計每學一遍,大家都會看一遍這幾句話,然後做做練習,基本也就過去了,應付考試嘛,差不多得了。。。。
但是我自己心裡可是始終有一個疑惑,始終想不明白當年那些計算機大師,為什麼這麼設計原碼,反碼和補碼,那些大師又不是傻逼和做題機器,所以這樣設計必然有其巧奪天工之處。
在一次無意翻到的一本STC單片機的書上,我看到了一張圖,就感覺開竅了。
所有編碼系統的設計,都在追求連續性和唯一性。
原碼,反碼和補碼的演化,就在不斷提高整數編碼的這兩方面性能。
不信的話,可以過來,跟我一起用圖形解讀整數的編碼:
原碼:原碼錶示法是整數的一種簡單的表示法,符號位用0表示正號,用1表示負號,數值一般用二進位形式表示。
觀察原碼的二進位碼錶示的整數圖,我們發現兩個bug
bug1:存在重複0,分別為00000000和10000000
bug2:存在兩個間斷點,分別為 011111111->10000000,即127突變為-0 11111111->000000000,即-127突變為+0
為什麼這麼關心間斷點,因為這涉及到整個範圍內的數值連續性,任何的間斷點都會導致溢出問題。所以這個間斷點,越少越好!
然後我們看看反碼做了什麼,這張圖會變成什麼樣???
反碼:整數的反碼可由原碼得到,如果是正數,則反碼與原碼一樣;如果是負數,則反碼是對它的原碼(符號位除外)各位取反而得到的。
對比觀察反碼與原碼的二進位碼錶示的整數圖,我們發現這一次取反,使右半邊的圖形,做了一次翻轉,於是bug2中的間斷點被修復了,也就是說間斷點從2個變成了1個。
bug1:依然存在重複0。
bug2:兩個間斷點減少到1個,不能再優化了。
然後我們繼續看看補碼做了什麼,這張圖又演化變成什麼樣?
補碼:整數的補碼可由原碼得到。如果是正數,則補碼與原碼一樣;如果是負數,則補碼是在反碼基礎上,末位加1而得到。
對比觀察補碼與反碼的二進位碼整數圖,我們發現這一次求補,使右半邊的圖形,向下移動了1,於是bug1中的重複點消失了,然後最小負整數從-127變成了-128。
於是我們可以看到,原碼中的兩個bug,通過兩次操作,求反(反碼)和求補(補碼)全部被解決了。
bug1:重複0(通過求補幹掉了,我認為就是打個補丁而已)。
bug2:兩個間斷點減少到1個(通過取反幹掉了)。
然後我們再回看看和理解一下最開頭那句話,
所有編碼系統的設計,都在追求連續性和唯一性。
反碼去掉間斷點,提高連續性。補碼在反碼基礎上,去掉重複點,保證唯一性。
現在看了這三張圖,是不是思路更清晰了一點呢?、?
3. 開始慢慢破解血案
在破解血案之前,我們先做一個簡單的計算:
在只有加法器的情況下,分別用原碼,反碼和補碼去算一下,下面三個極其弱智的少兒計算題
1+1
1+(-1)
(-1)+(-1)
。。。小心點,有坑
有小夥伴就要問了,為什麼三個運算,只有補碼能保證最終結果正確?????
話說,我也在想,為什麼這麼神奇!!
我們從圖上去看就比較容易理解了
所有的都是二進位求和,那從圖中,無非就是x軸疊加,然後看y軸對應什麼而已。
先看1+1,不管是原碼,反碼還是補碼,都是1的二進位碼橫軸疊加,對應y軸為2
再看1+(-1) 原碼:1對應1,-1對應中間位置,那兩者疊加,肯定是-1向右移1個,也就是-2 反碼:1對應1,-1對應末尾位置,那兩者疊加,肯定是-1向右移1個,也就是-0 補碼:1對應1,-1對應末尾位置,那兩者疊加,肯定是-1向右移1個,回到原點是0再看(-1)+(-1)
原碼:-1對應中間位置偏右1位,那兩者疊加,肯定是越界返回原點錯2個,也就是2 反碼:-1對應末尾位置偏左1位,那兩者疊加,肯定是越界後末尾偏左2個,也就是-3 補碼:1對應1,-1對應末尾位置,那兩者疊加,肯定是-1向右移1個,回到原點是-2
我們現在用補碼圖,去推算前面那個代碼里這個值是多少,首先看-100在圖中哪個位置,中間偏右的位置,那兩個-100疊加的話,肯定是溢出然後跑到中間偏左,也就是正數範圍里,如果仔細計算就會得到結果56。
旅途到此結束,希望能夠幫到你!
推薦閱讀:
※1-10 轉CS的優勢之一:前置條件少
※聊聊前端(二)
※用CDH5搭建hadoop集群
※用 iPad Pro 作為我的主力工作電腦
※《計算機科學及編程導論》部分筆記