Home
Explore Uedu
Student Console
Register as Member/Login
(2) In future presentations of the research findings, in addition to the course project website and public presentations, your real name and personal information will not appear in this research report. If you are interested in the research results, we can provide you with an executive summary after the study is completed.
問卷中心
Teacher Console
Course Setup
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

0

Local AI

--

CISOSE26 本地 AI UG26
政治大學 AQI 19 29°C

AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

數字系統與進位

數字系統與進位:電腦如何用 0 與 1 表達世界

從位值制出發,理解二進位、進位轉換、小數誤差,以及 2 的補數如何讓硬體用一個加法器搞定所有整數運算。

為什麼按下計算機,1 加 1 永遠不會等於 10?

如果你曾經把一張紙條密碼寫成「01001000 01101001」,或在網路上看過有人開玩笑說「世界上只有 10 種人,懂二進位的和不懂的」,那你已經碰到了數字系統(number system)的核心趣味。同一串符號「10」,在十進位裡是「十」,在二進位裡卻是「二」。一個符號要表示什麼數,從來不是天經地義,而是取決於我們約定好的「進位規則」。

電腦內部沒有十個手指頭可以數,它只有兩種狀態:有電壓、沒電壓;導通、不導通。於是這台機器選擇了二進位作為它的母語。要真正理解計算機如何儲存與運算,第一步就是搞懂:數字其實是一種「位置決定價值」的編碼遊戲。

計算的基礎概念示意圖

位值制:每一格都有自己的權重

我們從小用的十進位(decimal),本質是一套「位值制(positional notation)」。一個數字串,每一位的價值不只看它的符號,還要乘上它所在位置的「權重」。權重是基底(base/radix)的次方。

以十進位的 $3705$ 為例:

$$3705 = 3 \times 10^3 + 7 \times 10^2 + 0 \times 10^1 + 5 \times 10^0$$

換成基底 $b$,一個整數可以寫成:

$$N = \sum_{i=0}^{n-1} d_i \times b^{i}$$

其中 $d_i$ 是第 $i$ 位的數字,且每個 $d_i$ 必須滿足 $0 \le d_i < b$。這就是為什麼二進位只能用 0 和 1(基底 2,數字最大到 1);十六進位則需要額外的符號 A 到 F 來表示 10 到 15。

常見的四種進位制與其符號集合如下:

進位制 基底 可用符號
二進位(binary) 2 0, 1
八進位(octal) 8 0–7
十進位(decimal) 10 0–9
十六進位(hexadecimal) 16 0–9, A–F

小數的部分只是把指數延伸到負數。例如二進位的 $101.11_2$:

$$1 \times 2^2 + 0 \times 2^1 + 1 \times 2^0 + 1 \times 2^{-1} + 1 \times 2^{-2} = 4 + 0 + 1 + 0.5 + 0.25 = 5.75_{10}$$

理解了「位值 = 數字 × 基底的次方」這一條原則,所有進位轉換就只是同一個公式的反覆套用。

為什麼電腦選二進位,而不是十進位?

一個很自然的疑問:既然人類用十進位這麼順手,為什麼不乾脆造一台用十進位的電腦?答案在於「可靠性」與「成本」。

電子元件最容易、最穩定地分辨的是「兩種狀態」:高電位與低電位、開與關。如果要讓單一電路穩定地分辨十個不同的電壓階層(例如 0V、0.5V、1V…),雜訊只要讓電壓飄移一點點,就可能把「3」誤判成「4」。而二進位只需要分辨「夠高」或「夠低」,容錯區間極大,極為抗雜訊。

此外,二進位天然對應布林邏輯(Boolean logic)的真與假,使得加法、比較、邏輯運算都能用一致的邏輯閘(logic gate)實現。歷史上確曾有十進位電腦(如早期的 ENIAC),但二進位在硬體簡潔度與可靠度上的壓倒性優勢,讓它成為現代計算的標準。

至於八進位與十六進位,它們不是給硬體用的,而是給「人」用的速記法。因為 $8 = 2^3$、$16 = 2^4$,每一個八進位位恰好對應 3 個二進位位,每一個十六進位位恰好對應 4 個二進位位。一長串 0 與 1 寫成十六進位會短得多、也不易看錯,這就是為什麼記憶體位址、色碼(如 #3C938A)幾乎都用十六進位表示。

值得留意的是,這種「人機分工」貫穿整個計算世界:硬體層永遠是二進位,但只要進位制的基底是 2 的次方,二進位與它之間的轉換就完全不需要做乘除運算,只要「分組」即可,幾乎零成本。反觀十進位與二進位之間就沒有這種整齊的對應關係(因為 10 不是 2 的次方),轉換時必須老老實實地做除法或乘法。這也解釋了為何工程師在面對位元層級的資料時,習慣切換到十六進位思考,而非十進位。

動手看一個例子:進位轉換

十進位轉二進位(整數)— 除以 2 取餘數,由下往上讀:

將 45 轉成二進位:
45 ÷ 2 = 22 ... 餘 1   ↑(最後讀,最高位)
22 ÷ 2 = 11 ... 餘 0   │
11 ÷ 2 =  5 ... 餘 1   │
 5 ÷ 2 =  2 ... 餘 1   │
 2 ÷ 2 =  1 ... 餘 0   │
 1 ÷ 2 =  0 ... 餘 1   ↓(最先算,但是最高位,反過來讀)

由下往上:45 = 101101₂
驗證:32 + 8 + 4 + 1 = 45 ✓

十進位小數轉二進位 — 乘以 2 取整數,由上往下讀:

將 0.625 轉成二進位:
0.625 × 2 = 1.25  → 取整數 1
0.25  × 2 = 0.5   → 取整數 0
0.5   × 2 = 1.0   → 取整數 1(小數歸零,結束)

由上往下:0.625 = 0.101₂
驗證:0.5 + 0 + 0.125 = 0.625 ✓

這裡藏著一個重要迷思的解答:許多十進位小數(如 $0.1$)在二進位中是「無限循環」的,無法精確表示。這正是為什麼 0.1 + 0.2 在多數程式語言裡會得到 0.30000000000000004——不是電腦算錯,而是浮點數先天無法精確存下 0.1。

二進位與十六進位互轉 — 每 4 位一組:

二進位      1101 1010 . 0110
              ↓    ↓      ↓
十六進位      D    A   .   6     →  DA.6₁₆

補數:用「加法」來做減法

到目前為止我們只談了正數。但電腦的硬體最好只會做一件事——加法。如果還要為「減法」「負數」另外造一套電路,成本很高。能不能讓「減去一個數」變成「加上另一個數」?這就是補數(complement)登場的地方。

考慮一個生活類比:時鐘是「模 12」的系統。要把時針從 8 點調回 5 點(減 3),你可以倒撥 3 小時,但也可以「正撥 9 小時」——因為 $8 + 9 = 17 \equiv 5 \pmod{12}$。在這個有限的循環系統裡,「減 3」與「加 9」效果完全相同。9 就是 3 在模 12 下的補數。

電腦的暫存器(register)也是有限位元的循環系統。在 $n$ 位元的世界裡,所有運算都是「模 $2^n$」——數值一旦超過上限就會繞回起點,就像時針走過 12 點又回到 1 點。我們可以借用時鐘的智慧,把負數編碼成某個正數,讓減法變成加法。歷史上人們嘗試過幾種編碼方案:最直覺的「符號-數值法」把最高位當純粹的正負號,但運算時要額外判斷符號、且有正負兩個零;「1 的補數」把負數定義為逐位取反,運算規則簡化了一些,卻仍留著正負零的尷尬。最終勝出、被現代 CPU 一致採用的,是2 的補數(two's complement)

深入:2 的補數運算機制與溢位

如何表示一個負數

在 $n$ 位元的 2 的補數系統中,一個負數 $-x$ 的編碼是 $2^n - x$。實務上有一個更好記的「翻轉加一」口訣:

取絕對值的二進位 → 每一位元取反(NOT) → 結果加 1。

以 8 位元表示 $-5$ 為例:

+5 的二進位:         0000 0101
逐位取反(1 的補數): 1111 1010
加 1:               1111 1011   →  這就是 -5

驗證:$1111\,1011_2 = 251_{10}$,而 $256 - 251 = 5$,確實對應 $-5$。

2 的補數的精妙之處,在於最高位(most significant bit)同時扮演「符號位」與「正常的位值」,只是它的權重是負的。對 8 位元而言:

$$\text{value} = -d_7 \times 2^7 + \sum_{i=0}^{6} d_i \times 2^i$$

代入 $1111\,1011$:$-128 + 64 + 32 + 16 + 8 + 0 + 2 + 1 = -5$ ✓。

為什麼減法就變成了加法

來算 $7 - 5$,也就是 $7 + (-5)$:

   0000 0111   (+7)
 + 1111 1011   (-5)
 -----------
 1 0000 0010
 ↑
 進位被丟棄(超出 8 位元)
   → 結果 0000 0010 = +2  ✓

關鍵在於:最高位產生的進位(carry-out)直接被「丟棄」,這正好實現了「模 $2^n$」的效果。整個過程硬體只用了一個加法器(adder),不需要任何專門的減法電路。這就是 2 的補數成為現代 CPU 標準的根本原因。

它還有一個額外優點:零只有一種表示法。相對地,早期使用過的「1 的補數」與「符號-數值法(sign-magnitude)」都存在 +0-0 兩種編碼,徒增比較與運算的複雜度。在 8 位元 2 的補數中,可表示範圍是 $-128$ 到 $+127$(負數比正數多一個,因為 0 佔據了正數那一側)。

溢位(overflow):當結果超出可表示範圍

有限位元意味著「天花板」。當兩數相加的真實結果超出 $[-128, +127]$,結果會「繞回」到錯誤的數值,這就是溢位。

來看 $100 + 50$(真實答案 150,已超過 127):

   0110 0100   (+100)
 + 0011 0010   (+50)
 -----------
   1001 0110   = -106 (在 2 的補數下)

兩個正數相加竟然得到負數!這是典型的有號數溢位。判定規則很俐落:

當兩個同號數相加,結果的符號卻與它們不同,就是溢位。

從硬體角度,CPU 用一條更精確的規則偵測溢位:

$$\text{Overflow} = C_{\text{in (MSB)}} \oplus C_{\text{out (MSB)}}$$

也就是「流入最高位的進位」與「流出最高位的進位」不相等時,溢位旗標(overflow flag)就會被設定。

請特別注意區分兩個容易混淆的概念:

  • 進位(carry):針對「無號數(unsigned)」運算的進位旗標,看最高位是否產生 carry-out。
  • 溢位(overflow):針對「有號數(signed)」運算,看符號是否錯亂。

同一次加法,無號解讀與有號解讀的「是否出錯」可能完全不同。CPU 同時維護 carry flag 與 overflow flag,由編譯器或程式設計師決定該採信哪一個。在 C/C++ 中,有號整數溢位是「未定義行為(undefined behavior)」,可能導致難以預測的安全漏洞——這也提醒我們,理解底層表示法不只是學術問題,更關乎程式的正確性與安全性。

重點回顧

  • 位值制是一切的基礎:任何進位制都遵循 $N = \sum d_i \times b^i$,「數字的價值 = 符號 × 基底的次方」,轉換不過是反覆套用同一公式。
  • 電腦用二進位是為了可靠:兩種狀態最抗雜訊、最易用邏輯閘實現;八進位與十六進位則是給人看的速記法($8=2^3$、$16=2^4$)。
  • 小數轉換可能無限循環:許多十進位小數(如 0.1)在二進位中無法精確表示,這是浮點數誤差的根源,不是電腦算錯。
  • 2 的補數讓減法變加法:用「翻轉加一」編碼負數,丟棄最高位進位即實現模 $2^n$ 運算,零只有一種表示,是現代 CPU 的標準。
  • 溢位與進位是兩回事:溢位看有號數的符號是否錯亂(同號相加得異號),進位看無號數的最高位 carry;CPU 兩個旗標分開維護。

深入探討(研究所視角)

從計算機結構的角度,2 的補數之所以勝出,本質是它把整數運算嵌入了一個有限交換環(finite commutative ring) $\mathbb{Z}/2^n\mathbb{Z}$。在這個環中,加法與乘法天然封閉、結合、分配,而「丟棄超出 $n$ 位元的高位」恰好就是環的模運算 $\bmod\ 2^n$。因此同一套硬體加法器既能正確處理無號數,也能正確處理有號數——有號與無號的差異只存在於「人類如何解讀那串位元」,而非運算本身。這個統一性是 1 的補數與符號-數值法都無法達成的,也是它們被淘汰的根本原因。

延伸到浮點數,IEEE 754 標準揭示了另一層深刻問題:實數是不可數無窮的,而任何有限位元的表示法只能表達可數的離散子集。於是「機器精度(machine epsilon)」$\epsilon$ 定義了相鄰兩個可表示浮點數之間的最小相對間隙,數值分析中誤差的累積、條件數(condition number)、災難性抵消(catastrophic cancellation)等議題都由此而生。前面提到的 0.1 + 0.2 ≠ 0.3 只是冰山一角;在大型科學計算或金融系統中,運算順序與表示誤差的交互作用,可能讓結果產生肉眼可見的偏差,這也是為什麼金額計算往往改用定點數或十進位浮點(decimal floating-point)。

最後值得連結的是資訊理論視角:一個數值需要多少位元,下界由其資訊熵(entropy)決定。$n$ 位元至多能區分 $2^n$ 種狀態,這條 $\log_2$ 的天花板貫穿了從資料壓縮、密碼學金鑰長度到雜湊碰撞機率的所有討論。當我們問「64 位元整數夠不夠用」「256 位元金鑰多安全」時,問的其實都是同一個問題:在有限位元的離散世界裡,我們能編碼多少種可能性。數字系統看似是入門課題,卻是通往計算理論深處的第一道門。

AI 共讀助教正在陪你讀:數字系統與進位:電腦如何用 0 與 1 表達世界
嗨!我是這篇文章的共讀助教,只根據〈數字系統與進位:電腦如何用 0 與 1 表達世界〉的內容回答。可以問我「解釋某段」「舉個例子」「出題考我」,或反白文中段落後點下方「解釋選取段落」。