ECCH2:數值誤差與程式除錯

本頁以「數值表示的限制」與「程式除錯方法」為主線,說明溢出、截尾與捨入等常見數值誤差,以及語法錯誤、運行時錯誤與邏輯錯誤的分別,並介紹追蹤表、追蹤變量值及斷點等除錯工具。

重點詞彙:可表示範圍、上溢錯誤、下溢錯誤、截尾誤差、捨入誤差、語法錯誤、運行時錯誤、邏輯錯誤、追蹤表、斷點、追蹤變量值

0. 導讀:本頁學習內容與學習目的

在計算機系統中,數值需要以有限的位元儲存與運算,因此不同資料型別各自擁有固定的可表示範圍精度。當程式進行運算、資料型別轉換或反覆計算時,便可能出現上溢/下溢、截尾或捨入造成的誤差。另一方面,即使數值表示無誤,程式仍可能因語法、運行時或邏輯問題而輸出錯誤結果。

本頁把「數值誤差」與「程式除錯」放在同一框架內,目標是建立一套由結果檢查問題定位的思路:先理解數值限制,再配合追蹤表、追蹤變量值與斷點等工具,逐步找出錯誤來源並完成修正。

學習目標

為何需要學習本課題

1. 超出數字或變量可代表的範圍

重點

  • 電腦以有限的位元儲存整數,因此每種資料型別都有固定的可表示範圍
  • 超出最大值會出現上溢錯誤;低於最小值會出現下溢錯誤(有些情況會「回捲」成另一個數)。
  • 有符號整數(常用二補碼表示)與無符號整數的範圍不同,邊界值亦不同。
  • 避免方法:選用足夠大範圍的資料型別、在運算前做範圍檢查、以測試資料覆蓋臨界值。

電腦中的整數並不是「無限大」,而是以固定數量的位元儲存。當程式設計者選用某種資料型別,實際上就是選定了可表示的最大值與最小值。若運算可能超出範圍,就必須在設計階段預先處理,否則結果可能回捲,造成難以察覺的錯誤。

在課堂與考試中,最常見的做法是:先判斷使用多少位元、屬有符號還是無符號,然後計算可表示範圍,再分析某次運算是否會上溢錯誤或下溢錯誤。實務上則常配合「範圍檢查」與「邊界測試」:讓測試資料刻意碰到最大值、最小值、以及臨界點,確保程式在極端情況仍正確。

示例說明:以取餘數(mod)模擬 8 位元整數回捲

下列偽代碼/Python 的重點在於「模擬固定 8 位元儲存」:不少系統會把整數存放在固定大小的欄位(例如 8 位元、16 位元、32 位元)。當數值寫入該欄位時,只有最低位的 n 個位元會被保留,其效果等同於對 2n 取餘數。

  1. 先求 u8:u8 = n mod 256,代表「只保留最後 8 個位元」,因此 u8 一定落在 0 至 255。
  2. 再解讀 s8:若 u8 ≥ 128,表示最高位(符號位)為 1,按二補碼規則應解讀為負數,故 s8 = u8 - 256;否則 s8 = u8
  3. 觀察回捲:例如 127 + 1 = 128。對 8 位元有符號整數而言,128 會被解讀為 −128,這就是「上溢後回到最小值」的概念示例。
輸入 n u8 = n mod 256 s8(以 8 位元有符號解讀)
127127127
128128−128
200200−56
3004444
255255−1

注意:Python 的整數通常可自動擴展位數,因此不會因位數不足而溢出。本示例屬於「概念模擬」,用來展示固定 bit 儲存時可能出現的回捲行為。

示例:用 mod 模擬 8 位元有符號/無符號
輸入 整數 n

# 先得到 8 位元無符號結果(0..255)
u8 ← n mod 256

# 再把它解讀為 8 位元有符號結果(-128..127)
如果 u8 ≥ 128:
    s8 ← u8 − 256
否則:
    s8 ← u8

輸出 "u8 =" , u8
輸出 "s8 =" , s8

# 示範:有符號 8 位元的 127 + 1 會回捲
x ← 127 + 1
u8x ← x mod 256
如果 u8x ≥ 128:
    s8x ← u8x − 256
否則:
    s8x ← u8x
輸出 "127 + 1 =" , s8x

Check Point 1:超出可表示範圍(四選一.至少 20 題)

請按「下一題」開始。

2. 數字誤差:截尾誤差

重點

  • 截尾誤差常見於「直接截去」位數(例如只保留兩位小數)或過早把小數轉為整數。
  • 浮點數以有限位元儲存,小數未必能完全精確表示,運算後可能出現微小偏差。
  • 重複運算會令微小誤差逐步放大,形成誤差累積
  • 比較浮點結果時,宜使用容許誤差(tolerance),避免直接用 ==

截尾誤差不一定代表「程式寫錯」。很多時候,它源自資料表示法與精度限制,或是你刻意降低精度(例如只保留兩位小數)。這類操作會令資訊流失,因此結果與真值產生差距。

當運算涉及大量步驟(例如累加、迴圈、模擬),即使每一步的誤差很小,也可能逐步累積,最後的偏差變得明顯。良好的處理方法包括:盡量在最後一步才截尾/捨入、使用足夠精度、以及在比較時使用容許誤差。

溫馨提示:在需要精確處理金額的情況,常見做法是把金額轉換成「最小單位的整數」(例如以「仙」為單位),以避免浮點數誤差。

示例:容許誤差比較
a ← 0.1
b ← 0.2
c ← 0.3

輸出 a + b
輸出 (a + b 是否等於 c)

eps ← 1×10^(-9)
如果 |(a + b) − c| < eps:
    輸出「足夠接近,可視為相等」
否則:
    輸出「差距太大」

Check Point 2:截尾誤差(四選一.至少 20 題)

請按「下一題」開始。

3. 捨入:上捨、下捨、四捨五入與捨入誤差

重點

  • 上捨(向上取整)、下捨(向下取整)與四捨五入是常見捨入規則。
  • 捨入會引入捨入誤差;若在中途反覆捨入,中間誤差會更容易累積。
  • 一般建議:先用較高精度完成計算,只在最後輸出前捨入
  • 對負數而言,不同取整規則(例如截尾、下捨)結果可能不同,必須先講清楚採用哪一種規則。

捨入與截尾的共同點是「降低精度」;不同之處在於規則不同。若在運算過程中多次捨入,誤差會更快累積,因此實務上常先以較高精度完成計算,最後才按題目要求捨入輸出。

另一方面,題目若涉及負數或臨界值(例如剛好 0.5),必須先確認採用的捨入規則與取整方向。只要規則一致,答案就可檢查、可重現,也更易寫成程式。

示例:不同取整方式的差異(含負數)
vals ← [2.4, 2.5, 2.6, -2.5, -2.9]
對每個 x ∈ vals:
    輸出 x
    輸出「截尾(向 0)」的結果
    輸出「下捨」的結果
    輸出「上捨」的結果
    輸出「四捨五入」的結果

Check Point 3:捨入與捨入誤差(四選一.至少 20 題)

請按「下一題」開始。

4. 語法錯誤、運行時錯誤、邏輯錯誤

重點

  • 語法錯誤:寫法不符合語法規則,程式通常無法開始執行(例如漏冒號、括號不成對)。
  • 運行時錯誤:程式能開始執行,但執行到某一步才出錯(例如除以 0、索引超界)。
  • 邏輯錯誤:程式能執行到底,但輸出不正確;需要靠測試資料與追蹤變量值定位。
  • 處理錯誤時要懂得閱讀錯誤訊息:看錯誤類型、行號,並追溯出錯前的變量狀態與輸入。

語法錯誤與運行時錯誤通常較容易處理,因為系統會提供錯誤類型與行號;你只要跟着訊息修正寫法或輸入處理即可。相反,邏輯錯誤最難,因為程式可能完全不報錯,只是答案不正確。

面對邏輯錯誤,建議先用小量且可重現的測試資料(例如 0、1、臨界值、最大值/最小值),再以追蹤表或 print() 觀察關鍵變量值。當你找到「答案開始偏離」的第一步,就能把問題範圍縮小到少數幾行,修正效率會大幅提升。

示例:三種錯誤(按註解提示自行嘗試)
# 這部分以概念為主:
# 語法錯誤:寫法不合規則,程式不能開始
# 運行時錯誤:能開始,但某一步不合法(例如除以 0)
# 邏輯錯誤:能完成,但答案不正確

# 實務做法:
# 1) 先令程式能開始(修語法)
# 2) 再令程式能完成(修運行時錯誤)
# 3) 最後用測試+追蹤變量值修邏輯錯誤

Check Point 4:錯誤類型判斷(四選一.至少 20 題)

請按「下一題」開始。

5. 除錯工具:追蹤變量值與斷點

重點

  • 追蹤表可把每一步的變量值記錄下來,特別適合在小例子中找邏輯錯誤。
  • 斷點可令程式在指定位置暫停,配合單步執行觀察變量如何改變。
  • 除錯的核心流程:重現 → 縮小範圍 → 觀察 → 假設 → 驗證。
  • 良好習慣:一次只改一個地方、修正後回歸測試、避免留下多餘的除錯輸出。

除錯工具的目的不是「代你找錯」,而是令你更快掌握程式的真實執行情況。當你遇到邏輯錯誤時,單靠直覺往往不足以定位問題;追蹤表、追蹤變量值與斷點能把流程拆解,讓你看到每一步的變化。

建議的實戰做法是:先用小量測試資料重現問題,再用追蹤表或 print() 找出哪一步開始偏離預期;若你使用 IDE,則可用斷點配合單步執行,把問題範圍縮小到少數幾行,再作修正。每次修正後都要用原本的測試資料重新驗證,並補充邊界測試,確保問題不會再出現。

追蹤表示例(參考)

以下追蹤表對應程式:total = total + i(i 由 1 到 3)。

步驟 i total(更新後)
初始0
迴圈 111
迴圈 223
迴圈 336
示例:用 print() 追蹤變量值定位邏輯錯誤
nums ← [8, 10, 15, 7]
count ← 0

對每個 x ∈ nums:
    如果 x ≥ 10:
        count ← count + 1
    輸出「目前 x、count」

輸出「≥10 的個數」, count

互動示範:斷點與單步執行如何追蹤變量值

本示範以一段簡單程式為例,模擬除錯器的「單步執行」與「斷點」行為。學習者可透過點擊行號設定斷點,並觀察每一步的變量值變化。

  1. 點擊左側行號旁的圓點欄位,以新增/取消斷點。
  2. 按「單步執行」逐行執行;按「繼續」可執行至下一個斷點(或到程式結束)。
  3. 觀察右側的「本地變量」及「輸出」,理解變量值如何隨步驟變化。
目前行: 步驟:/
斷點:(無)
示例程式 (點擊行號可設定斷點)
本地變量 尚未開始執行
(尚未開始)
輸出
(尚未有輸出)

提示:本例的目標是計算 1 至 n 的總和,但迴圈使用 range(1, n),因此當 n = 5 時只會計到 4。可在第 4 行設定斷點並反覆按「繼續」,觀察 i 的最大值,從而定位「迴圈範圍差一」的問題。

Check Point 5:除錯工具與策略(四選一.至少 20 題)

請按「下一題」開始。