Try   HackMD

整數的儲存

tags: Competitive Programming Note

本文已停止更新,新版請至 WiwiHo 的競程筆記

在數學上,表示負數很簡單,就加個負號就好了。那在電腦中該怎麼表示負數?

最簡單的方式是用最高位元來表示正負號,0 代表正,1 代表負,像是如果用 8 個位元來表示一個數,

5 就是 1000 0101
3
就是 1000 0011,但這樣會發生一個問題,就是表示
0
有兩種方式:不管最高位是 10,只要其他位都是 0,這樣它就是表示
0
,所以勢必需要另一個方法來有效利用空間。

補數和二補數

將一個二進位數字的 0、1 互換後,就可以得到它的補數,例如(以 8 位元為例):
0000 0001 的補數是 1111 1110
0101 1010 的補數是 1010 0101
在 C++ 中,~ 這個運算子就是用來做這件事情。

接下來,把補數加

1,如果進位到超出範圍(像剛剛的例子都是 8 位元),稱為溢位,就把超出的部分捨去,這樣就得到了一個數的二補數,例如(以 8 位元為例):
0000 0001 的補數是 1111 1110,二補數是 1111 1111
0101 1010 的補數是 1010 0101,二補數是 1010 0110
0000 0000 的補數是 1111 1111加 1 後是 1 0000 0000,二補數是 0000 0000
1000 0000 的補數是 0111 1111,二補數是 1000 0000

這就是目前最常見的電腦儲存有號(正負)整數的方式,一個數的二補數所表示的就是它的相反數,例如(以 8 位元為例):

3 的二進位是 0000 0011,二補數是 1111 1101,那麼 1111 1101 所表示的值就是
3

顯而易見地,如果我們用

k 位元儲存一個數,那麼我們可以儲存
2k
種數字,然後我們各分一半給正數(含
0
)和負數,所以扣除掉
0
,可以表示
2k11
個正數,因此最大正數就是
2k11
,然後還有
2k1
個負數,因此最小負數是
2k1

所以說,C++ 中常見的整數型態範圍是這樣:
unsigned 開頭的是無號的,也就是只有

0 和正數,因此
k
位元的無號整數最大值是
2k1

型態 byte 數 最大整數 最小整數
char 1 127 -127
short 2 32767 -32768
int 4 2147483647 -2147483648
long long 8 9223372036854775807 -9223372036854775808
unsigned char 1 255 0
unsigned short 2 65535 0
unsigned int 4 4294967295 0
unsigned long long 8 18446744073709551615 0

一般只會用到 intlong long 與它們的 unsigned 版本,我會建議大概記一下這些的範圍,像是 int 的最大值大約是

2×109long long 的最大值大約是
9×1018

接下來,有個特殊狀況,就是有兩個數取二補數後會跟原本一樣,這兩個數分別是

0 和最小整數。

很顯然地,

0 的二進位就是每一位都是 0,而它的補數是每一位都是 1,再加一後顯然會溢位,所以
0
的二補數還是
0
,不過這也很剛好,
0
的相反數本來就是
0

至於最小整數,是除了最高位是 1,其他都是 0,它的補數是除了最高位是 0,其他都是 1,加一後就進位,變回去最高位是 1,其他是 0 了,正好最小整數的絕對值比最大整數的絕對值多

1,所以最小整數的相反數必定會超過最大整數,因此不可能透過取二補數來得到最小整數的相反數。

接下來會有一些證明,先說明一件事,二進位值都可以用一個十進位無號數來表示,即使我們本來想要它是表示一個有號數,它還是可以當作一個無號數,也就是用一般的方式去看它表示的值,例如:八位元 1110 0011 是有號數

29,也可以是無號數
227
,然後這兩個數有一個關係:若這兩個數分別是
a
b
,然後位元數是
k
,那麼
ab(mod2k)
(模除取正數),證明如下(
a0
a=b
,所以不需要證):

證明

k 位元的二進位值所表示之有號數
a
a<0
) 及無號數
b
滿足
ab(mod2k)

a=(2k1b+1)=2k+b

amod2k=b

b<2k
,故
bmod2k=b

ab(mod2k)

再特別說明一下

a=(2k1b+1) 這個部分,這個二進位值的補數表示成無號數是
2k1b
,再加
1
就是二補數了,因為
a
是負數,所以先找到它的相反數,比較好得到
a
。還有一個特例在,如果
a=2k1
,也就是
a
是最小整數的話呢?為什麼取二補數還是可以找到它的相反數?我們知道有號最小整數的二進位值是除了最高位是 1,其他都是 0,也就是說用無號數表示是
2k1
,用剛剛的方法取二補數會得到:
2k12k1+1=2k2k1=2k1
,完全沒變,但最小整數恰好是
2k1
,所以如果用這個邏輯(以無號數的想法來看,剛剛說「不能得到相反數」是以有號數的想法),我們還是可以找出它的相反數的,而且我們得到一個結論,若一個
k
位元二進位值所表示的無號數是
b
,則可以這樣得到它的二補數所表示的無號數
c
c=2kb

接下來來證明一件事:取二補數得到的值可以當作相反數,這必需滿足一個條件,就是對二補數再取二補數要是本來的數。如果接下來沒有特別說明,則「二進位值

a」 的
a
是指該二進位值所表示的無號數。

證明

k 位元的二進位值
a
之二補數為
b
,且
b
之二補數為
a

b=2ka

b
之二補數為
2kb=2k(2ka)=a

如果證明都看不懂也沒關係,記得結論就好:

  • k
    位元可以表示的有號數範圍為
    [2k1,2k11]
  • k
    位元可以表示的無號數範圍為
    [0,2k1]
  • k
    位元的同個二進位值所表示的有號數
    a
    和無號數
    b
    滿足
    ab(mod2k)
  • 0
    和最小整數取二補數後不變
  • k
    位元二進位值
    a
    的二補數是
    2ka

二補數的加減運算

在討論完怎麼儲存一個整數後,來討論怎麼做運算。最基本的運算當然是加和減了,如果是兩個非負整數相加,那顯然很簡單,就加起來,再處理一下進位就好,但是在二補數系統下,會發生一個問題:如果數字太大,進位到最高位甚至超過怎麼辦?一樣用 8 位元來舉例,如果要把 0110 0000

96)和 0100 0000
64
)相加,我們希望得到的結果是
160
,然而這顯然大於最大整數,也就是
271=127
了,實際上把它們的二進位值加起來,會得到 1010 0000
96
),所以這是一件很需要注意的事。

除了正數,還有負數。在數學上,

ab=a+(b),那在二補數系統上也可以這麼做嗎?答案是可以,如果你有兩個整數
a
b
,要計算
ab
時,找出
b
的二補數後再與
a
相加,這樣會是對的,接下來要證明這件事:就像上面的證明一樣,「二進位值
a
」中的
a
是指某一二進位值所表示的無號數,然後,我們有兩個
k
位元的二進位值
a
b
,它們所表示的有號數是
c
d
,且
c
d
可以是正數,也可以是負數,然後我們必需證明它們滿足:
a+bc+d(mod2k)

如果這兩個條件都滿足,就表示我們可以正確得到

c+d 的結果。

已知

k 位二進位值
a
b
所表示之有號數分別為
c
d

ac(mod2k)

bd(mod2k)

根據同餘性質
a+bc+d(mod2k)

至於乘除的運算也很簡單,類似於國小時學的直式乘、除法,只是換成二進位而已。

事實上,無論是幾進位的運算,都可以用這種取補數、取模的方式來做減法。