C语言的本质(3)——整数的本质与运算
C語言的本質(3)——整數的本質與運算
?
計算機存儲的最小單位是字節(Byte),一個字節通常是8個bit。C語言規定char型占一個字節的存儲空間。如果這8個bit按無符號整數來解釋,則取值范圍是0~255,如果按有符號整數來解釋,則取值范圍是-128~127。C語言規定了signed和unsigned兩個關鍵字,unsigned char型表示無符號數,signed char型表示有符號數。
?
對于char類型,編譯器可以定義char型是無符號的,也可以定義char型是有符號的,在該編譯器所對應的體系結構上哪種實現效率高就可以采用哪種實現,x86平臺的gcc定義char是有符號的。
這是C標準的規則之一:優先考慮效率,而可移植性尚在其次。這就要求程序員非常清楚這些規則,如果你要寫可移植的代碼,就必須清楚哪些寫法是不可移植的,應該避免使用。另一方面,寫不可移植的代碼有時候也是必要的,比如Linux內核代碼使用了很多gcc特性以得到最佳的執行效率,在寫的時候就沒打算用別的編譯器編譯,也就沒考慮可移植性的問題。如果要寫不可移植的代碼,你也必須清楚代碼中的哪些部分是不可移植的,以及為什么要這樣寫,如果不是為了效率,一般來說就沒有理由故意寫不可移植的代碼。
C語言與平臺和編譯器是密不可分的,離開了具體的平臺和編譯器討論C語言,就只能討論到本書第一部分的程度了。注意,ASCII碼的取值范圍是0~127,所以不管char型是有符號的還是無符號的,存一個ASCII碼都沒有問題,一般來說,如果用char型存ASCII碼字符,就不必明確寫signed還是unsigned,如果把char型當作8位的整數來用,為了可移植性就必須寫明是signed還是unsigned。
?
ANSI C99標準中定義了兩類(四個)類型修飾符:long/short和unsigned/signed。
C99標準規定,long類型不能比變通類型短,short類型不能比普通類型長。而unsigned與signed的區別在實現上是有無符號的區別,而是使用上是取值范圍的區別,兩者表示范圍相同,但前者全是正數,后者關于0對稱。
說明:
?
long/short可以修飾int,long還可以修飾double。
unsigned/signed可以修飾int, char,不可以修飾浮點型。
int長度是機器的字長,short int是半個字長,long int是一個或兩個字長。
unsigned/signed長度與普通類型一樣,只是表示區間不同。
?
各種整數數據類型的表示和取值范圍:
整型 | [signed]int | -2147483648~+2147483648 |
無符號整型 | unsigned[int] | 0~4294967295 |
短整型 | short [int] | -32768~32768 |
無符號短整型 | unsigned short[int] | 0~65535 |
長整型 | Long int | -2147483648~+2147483648 |
無符號長整型 | unsigned [int] | 0~4294967295 |
字符型 | [signed] char | -128~+127 |
無符號字符型 | unsigned char | 0~255 |
?
我們已經了解了計算機中正整數如何表示,加法如何計算,那么負數如何表示呢?減法又如何計算呢?為了書寫方便,下面所有的例子都用8個bit表示一個數,實際計算機算術運算的操作數可以是8位、16位、32位甚至64位的。
要用8個bit表示正數和負數,一種簡單的思路是把最高位當作符號位(Sign Bit),0表示正1表示負,剩下的七位表示絕對值的大小。例如-1表示成10000001,+1表示成00000001。
計算機要對這樣的兩個數做加法運算需要處理以下邏輯:
1、如果兩數符號位相同,就把它們的低7位相加,符號位不變。如果低7位相加時在最高位產生進位,則結果超出7位所能表示的數值范圍,這稱為溢出(Overflow),通常把計算機中的一個標志位置1表示產生溢出。
2、如果兩數符號位不同,首先比較它們的低7位誰大,然后用大數減小數,結果的符號位和大數相同。
?
減法運算需要處理以下邏輯:
1、如果兩數符號位相同,并且低7位是大數減小數,則符號位不變,如果低7位是小數減大數,則按大數減小數計算,結果要變號。
2、如果兩數符號位不同,把低7位相加,如果是正數減負數則結果為正,如果是負數減正數則結果為負,低7位在相加時可能產生溢出。
?
這其實和手算加減法的邏輯是相同的。算加減法需要處理這么多邏輯:比較符號位,比較絕對值,加法改減法,減法改加法,小數減大數改成大數減小數……這是非常低效率的。還有一個缺點是0的表示不唯一,既可以表示成10000000也可以表示成00000000,進一步增加了邏輯的復雜性,所以我們迫切需要重新設計數的表示方法,以使計算過程更簡單。
有一種方法可以把減法全部轉化成加法來計算,這樣就不必設計加法器和減法器兩套電路了。我們以十進制減法為例來理解一下這種方法。比如:
?
167-52=167+(999-52)-1000+1=167+947-1000+1=1114-1000+1=114+1=115
?
首先把52換成999-52,也就是947,這稱為取9的補碼(9's Complement),雖然這也是減法但它不需要借位,只需要對每一位數字分別取補碼,所以比一般的減法要簡單得多。然后把167和947相加,百位上的進位舍去,得到114,然后再加1得到115[21],這就是最終結果了。一句話概括就是:減去一個數等于加上這個數取9的補碼再加1(忽略最高位的進位)。
?
這種方法也可以類推到二進制加減法:減去一個數等于加上這個數取1的補碼(1's Complement)再加1(忽略MSB的進位)。取1的補碼就是1-1=0,1-0=1,其實相當于把每一位數字取反了,以后將1的補碼簡稱為反碼。比如
?
00001000-00000100->00001000+11111011+1->00000011+1=00000100
?
上式的前兩步不是等價變換,所以沒有用=號而是用->表示,第一步多加了一個100000000,第二步少加了一個100000000,效果相互抵消,所以最終結果正是00001000-00000100的結果。現在我們發現,如果把第一步寫成00001000+(-00000100)->00001000+11111011+1,則11111011+1就可以用來表示負數-00000100。所以,補碼表示法不僅可以把減法轉化為加法,而且合理地規定了負數的表示方法,就是“先取反碼再加1”。負數的這種表示稱為2的補碼(2'sComplement),以后簡稱為補碼。為什么稱為2的補碼呢?因為如果對一位數取補碼,則1的補碼是1-1+1=10-1=1,相當于從2里面減去1。類似地,對00000100取補碼是11111111-00000100+1=100000000-00000100,相當于從100000000(十進制的256)里面減去00000100。
?
將負數全部用補碼表示之后,8個bit可以表示的正數有00000000~01111111(十進制的0~127),負數有10000000~11111111(十進制的-128~-1),合起來是十進制的-128~127,一共256個數,而8個bit最多可以表示28=256個不同的數,所以已經充分利用了這8個bit,每個數都只有一種表示,0也只有一種表示就是00000000。我們還發現,所有正數的最高位是0,所有負數的最高位是1,因此最高位仍然具有符號位的含義,要檢查一個數是正是負只要看最高位就可以了,但在計算時卻可以把符號位和數放在一起做加法運算,而不必像Sign and Magnitude表示法那樣對符號位單獨處理。
?
采用補碼做加減運算時總是忽略MSB的進位,如果在計算過程中忽略進位的效果沒有相互抵消,最后的結果肯定是錯的,這種情況一定是由溢出引起的。只要我們有辦法判斷哪些情況會產生溢出,其它情況下都可以放心地忽略MSB的進位。判斷溢出的辦法是這樣的:在相加過程中最高位產生的進位和次高位產生的進位如果相同則沒有溢出,否則就說明產生了溢出。邏輯電路的實現可以把這兩個進位連接到一個異或門,把異或門的輸出連接到溢出標志位。對于8位二進制數的加減運算來說,當計算結果超出-128~127的范圍時就會溢出,例如:
有符號數加法溢出
最高位產生的進位是1,次高位產生的進位是0,說明溢出了,計算結果換算成十進制是122,這顯然不對,根本原因是(-126)+(-8)=-134超出了8位二進制數能表示的范圍。
用8個bit既表示正數又表示負數,則能夠表示的范圍是-128~127,如果8個bit全部表示正數,則能夠表示的范圍是0~255,前者稱為有符號數(Signed Number),后者稱為無符號數。但是計算機在做加法時并不區分操作數是有符號數還是無符號數,計算過程都是一樣的,所以上面的例子也可以看作無符號數的加法:
無符號數加法進位
?
把兩個操作數看作無符號數分別是130和248,計算結果換算成十進制是122,最高位的一個進位相當于256,122+256這個結果是對的。計算機的加法器在做完計算之后,根據最高位產生的進位設置進位標志,同時根據最高位和次高位產生的進位的異或設置溢出標志。至于這個加法到底是有符號數加法還是無符號數加法則取決于程序怎么理解了,如果程序把它理解成有符號數加法,就去檢查溢出標志,如果程序把它理解成無符號數加法,就去檢查進位標志。通常計算機在做算術運算之后還可能設置另外兩個標志,如果結果為零則設置零標志,如果結果的最高位是1則設置負數標志(只有當理解成有符號數運算時才去檢查這個標志)。
?
轉載于:https://www.cnblogs.com/new0801/p/6177122.html
總結
以上是生活随笔為你收集整理的C语言的本质(3)——整数的本质与运算的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 转移支付属于第几次分配
- 下一篇: 力星股份是什么公司