1. 程式人生 > >淺析為什麽 char 類型的範圍是 : -128~+127

淺析為什麽 char 類型的範圍是 : -128~+127

ref 什麽 的人 是我 col article 關於 即使 問題:

在 C 語言中。 signed char 類型的範圍為 -128~127,每本教科書上也這麽寫。可是沒有哪一本書上(包含老師)也不會給你為什麽是 -128~127,這個問題貌似看起來也非常easyeasy。 以至於不用去思考為什麽,不是有一個整型範圍的公式嗎: -2^(n-1)~2^(n-1)-1 (n為整型的內存占用位數)。所以 int 類型 32 位那麽就是 -(2^31) ~ 2^31-1 即 -2147483648~2147483647,可是為什麽最小負數絕對值總比最大正數多 1 。這個問題甚至有的工作幾年的程序猿都模棱兩可。由於沒有深入思考過,僅僅知道書上這麽寫。

於是。我不得不深入思考一下這個被很多人忽視的問題。

對於無符號整數,非常easy,所有位都表示數值,比方 char 型,8位。用二進制表示為 0000 0000 ~ 1111 1111,1111 1111 最大即為十進制255。所以 unsigned char 的範圍為 0~ 255,在這裏普及一下 2 進制轉十進制的方法。 二進制每一位的數值乘以它的位權(2^(n-1),n為自右向左的位),再相加,可得到十進制數。比方 :1111 1111 = 1*2^7 + 1*2^6 + 1*2^5 + 1*2^4 + 1*2^3 + 1*2^2 + 1*2^1 + 1*2^0 = 255 。


可是對於有符號整數,二進制的最高位表示正負,不表示數值。最高位為 0 時表示正數,為 1 時表示負數。這樣一來,能表示數值的就剩下( n-1 )位了,比方 char a = -1; 那麽二進制表示就為 1 0000001。1 表示為 0 0000001,所以 signed char 型除去符號位剩下的 7 位最大為 1111 111 = 127,再把符號加上,0 1111111 = 127,1 1111111 = -127。範圍應該為 -127~127 。同理 int 類型也一樣,可是問題出來了,教科書上是 -128~127 啊,以下就剖析一下這個驚人的奇葩。


再普及一下計算機內部整數存儲形式。大家都知道計算機內部是以二進制來存貯數值的,無符號整數會用所有為來存儲,有符號的整數。最高位當做符號位

,其余為表示數值


這樣貌似合理, 卻帶來一個麻煩。當進行加法時,1+1 :

0000 0001

+ 0000 0001

—————————

0000 0010 ……………… 2


當相減時 1-1=?

因為計算機僅僅會加法不會減法,它會轉化為 1 + (-1) ,因此:

0000 0001

+ 1000 0001

____________________

1000 0010 …………… -2


1-1 = -2?

這顯然是不正確了,所以為了避免減法運算錯誤。計算機大神們發明出了反碼,直接用最高位表示符號位的叫做原碼, 上面提到的二進制都是原碼形式,反碼是原碼除最高位其余位取反,規定:正數的反碼和原碼同樣,負數的反碼是原碼除了符號位,其余為都取反

,因此 -1 的源代碼為 1 0000001 ,反碼為 1 1111110。


如今再用反碼來計算 1+(-1) :

0000 0001

+ 1111 1110

————————

1111 1111 ………… 再轉化為原碼就是 1000 0000 = -0


盡管反碼攻克了相減的問題,卻又帶來一個問題:-0 。既然 0000 0000 表示 0。那麽就沒有 -0 的必要, 出現 +0 = -0 = 0 ,一個 0 就夠了。為了避免兩個 0 的問題。計算機大師們又發明了補碼。補碼規定: 整數的補碼是其本身。負數的補碼為其反碼加一 。所以。負數轉化為反碼需兩個步驟, 第一,先轉化為反碼,第二: 把反碼加一。


這樣 -1 的補碼為 1111 1111 ,1+(-1) :

0000 0001

+ 1111 1111

________________

1 0000 0000 …………………… 因為 char 為 8 位。最高位 1 被丟棄結果為 0 ,運算正確。

-0 :原碼 1000 0000 的補碼為 1 0000 0000 。因為 char 是 八位 ,所以取低八位 00000000。
+0 :原碼 0000 0000 ,補碼為也為 0000 0000 ,盡管補碼 0 都是同樣的,可是有兩個 0 。既然有兩個 0 ,況且 0 既不是正數,也不是負數, 用原碼為 0000 0000 表示即可了。


這樣一來,有符號的 char,原碼都用來表示 -127~127 之間的數了。只有剩下原碼 1000 0000 沒實用。用排列組合也能夠算出來,0 ???????,能表示 2^7 = 128 個數,剛好是 0~127。1 ???????。也能表示 128 個數,總共 signed char 有 256 個數,這與 -127~127 中間是兩個 0 剛好吻合。


如今再來探討一下關於剩下的那個 1000 0000。既然 -127 ~ 0 ~ 127 都有相應的原碼與其相應,那麽 1000 0000 表示什麽呢,當然是 -128 了,為什麽是 -128 呢。網上有人說 -0 即 1000 0000 與 128 的補碼同樣。所以用 1000 0000 表示 -128,,這我實在是不敢茍同。或者說 -128 沒有原碼。僅僅有補碼 1000 0000,胡扯,既然沒有原碼何來補碼,還有說 -128 的原碼與 -0(1000 0000) 的原碼同樣,所以能夠用 1000 0000 表示 -128,我僅僅能說。回答得不要那麽牽強, 原碼 1000 0000 與 -128 的原碼實際上是不同的。


但為什麽能用它表示 -128 進行運算。假設不要限制為 char 型(即不要限定是 8 位)。再來看,-128 的原碼:1 1000 0000 ,9位,最高位符號位。再算它的反碼:1 0111 1111。進而。補碼為:1 1000 0000,這是 -128 的補碼,發現和原碼一樣,1 1000 0000 和 1000 0000 同樣?假設說一樣的人真是瞎了眼了,所以,-128 的原碼和 -0(1000 000) 的原碼是不同的,可是在 char 型中,是能夠用 1000 000 表示 -128 的,關鍵在於char 是 8 位,它把 -128 的最高位符號位 1 丟棄了,截斷後 -128 的原碼為 1000 000 和 -0 的原碼同樣。也就是說 1000 0000 和 -128 丟棄最高位後余下的 8 位同樣,所以才幹夠用 -0 表示 -128,這樣。當初剩余的 -0(1000 0000)。被拿來表示截斷後的 -128,由於即使截斷後的 -128 和 char 型範圍的其它 (-127~127) 運算也不會影響結果。 所以才敢這麽表示 -128。


比方 -128+(-1) :

1000 0000 ------------------丟棄最高位的-128

+ 1111 1111 ----------------- -1

________________

10111 1111 ------------------char 取八位,這樣結果不對,只是沒關系 ,結果-129本來就超出char型了,當然不能表示了。


比方 -128+127:

1000 0000

+ 0111 1111

————————

1111 1111 -------------- -1 結果正確, 所以,這就是為什麽能用 1000 0000 表示-128的原因。


從而也是為什麽 char 是 -128~127。而不是 -127~127 。short int 相同如此 -32768~32767 由於在 16 位中,-32768 為原碼為 17 位,丟棄最高位剩下的 16 為 - 0 的原碼相同。



另一個問題,既然 -128 最高位丟棄了。那麽為什麽還能打印出 -128 ?

//在內存中以補碼1 1000 0000 存儲,但因為是 char。所以僅僅存儲 1000 0000
char a= -128;

//既然最高位丟棄了。輸出時應該是 1000 000 的原碼的十進制數-0,但為什麽能輸出-128呢。

printf("%d",a);

我猜想是計算機內部的一個約定。就像 float 一樣 ,能用 23 位表示 24 位的精度 。由於最高位默覺得 1,到時候把 23 位取出再加 1 便可。


-128 也是相同的原理,當數據總線從內存中取出的是 1000 000 。CPU 會給它再添最高一位,變為 1 1000 0000 這樣才幹轉化為 -128 輸出,不然 1000 0000 怎樣輸出?這當然是我的一種判斷,詳細怎麽實現還得問 CPU 的設計者了。


再看一個樣例:

char a=-129;
printf("%d",a); //會輸入多少?? 結果為 127 ,為什麽呢? 
-129 在補碼為 10 0111 1111 僅僅取後八位存儲,即 0111 111 這個值剛好是 127 了。同理 -130 截斷後為 126.....


如此按模輪回,關於模就先不探討了。


那麽:

unsigned  char a=  -1;
if( 1 > a){  
	printf("大於");
}else{
    printf("小於");
}

結果是什麽呢? 出人意料的是:小於。而不是大於。貓膩在你哪呢,還是存儲問題:

a 為 unsigned 無符號。 它的八位都用來存儲數值, 沒有符號位,編譯器把 -1 轉換為補碼為 1111 1111。但因為是無符號。計算機會把 1111 11111 當做是無符號來對待。自然就是 2^8 -1 = 255 了,所以相當於是 if( 1 > 255) 肯定是 printf("小於"); 了。


本文轉自:http://blog.csdn.net/daiyutage

淺析為什麽 char 類型的範圍是 : -128~+127