0. 導讀:本頁學習內容與學習目的
在計算機系統中,數值需要以有限的位元儲存與運算,因此不同資料型別各自擁有固定的可表示範圍與精度。當程式進行運算、資料型別轉換或反覆計算時,便可能出現上溢/下溢、截尾或捨入造成的誤差。另一方面,即使數值表示無誤,程式仍可能因語法、運行時或邏輯問題而輸出錯誤結果。
本頁把「數值誤差」與「程式除錯」放在同一框架內,目標是建立一套由結果檢查到問題定位的思路:先理解數值限制,再配合追蹤表、追蹤變量值與斷點等工具,逐步找出錯誤來源並完成修正。
學習目標
- 計算 n 位元有符號/無符號整數的可表示範圍,並判斷給定運算是否會出現上溢錯誤或下溢錯誤。
- 解釋截尾誤差與捨入誤差的來源,並在比較浮點值時使用容許誤差(tolerance)。
- 分辨語法錯誤、運行時錯誤與邏輯錯誤,能按錯誤訊息與行號進行初步定位。
- 使用追蹤表、追蹤變量值(例如輸出變量或在除錯器監察)及斷點配合單步執行,逐步縮小問題範圍。
為何需要學習本課題
- 在處理金額、感測器數據、統計資料等情境,即使是微小誤差亦可能影響判斷與結果,必須理解其成因與避免方法。
- 溢出與回捲在部分系統中不一定會即時報錯,容易產生「看似合理但實際錯誤」的輸出;掌握邊界概念有助設計更穩健的程式。
- 除錯能力是把「能寫出程式」提升為「能確認程式正確」的關鍵技能;透過工具觀察執行流程與變量狀態,可大幅提高修正效率。
1. 超出數字或變量可代表的範圍
重點
- 電腦以有限的位元儲存整數,因此每種資料型別都有固定的可表示範圍。
- 超出最大值會出現上溢錯誤;低於最小值會出現下溢錯誤(有些情況會「回捲」成另一個數)。
- 有符號整數(常用二補碼表示)與無符號整數的範圍不同,邊界值亦不同。
- 避免方法:選用足夠大範圍的資料型別、在運算前做範圍檢查、以測試資料覆蓋臨界值。
可表示範圍是指某種資料型別(或某個固定 bit 的欄位)能儲存的最小值至最大值。
- 上溢錯誤:運算結果大於可表示的最大值。
- 下溢錯誤:運算結果小於可表示的最小值。
注意:不同系統/語言對溢出處理不一。有些會報錯,有些會回捲(wrap around),令結果「看似合理」但實際錯誤。
若某整數以 n 位元儲存,總共有 2n 個不同的位元組合。
- 無符號整數:範圍通常是 0 至 2n−1。
- 有符號整數(二補碼):範圍通常是 −2n−1 至 2n−1−1。
例:8 位元有符號整數的範圍是 −128 至 127;8 位元無符號整數的範圍是 0 至 255。
- 8 位元無符號整數:255 + 1 → 0(回到最小值)。
- 8 位元有符號整數:127 + 1 → −128(回到最小值)。
這種回捲會令判斷條件出現錯誤,例如本應「越來越大」的計數器突然變成很小甚至負數。
- 不少語言的整數型別位數固定(例如某些情況下的 32 位元整數),較易遇到溢出。
- Python 的整數通常可自動擴展位數,較少因位數不足而溢出。
- 但當數據要存入固定大小欄位(硬件暫存器、檔案格式、網絡封包等),仍必須按固定 bit 規則處理。
- 把「溢出」與「截尾/捨入」混為一談:溢出是超出範圍;截尾/捨入是精度不足或取整造成的誤差。
- 只用一般數值測試,忽略臨界值(例如 127、−128、0、最大值)。
- 混用有符號/無符號概念,導致比較大小時出現錯判。
電腦中的整數並不是「無限大」,而是以固定數量的位元儲存。當程式設計者選用某種資料型別,實際上就是選定了可表示的最大值與最小值。若運算可能超出範圍,就必須在設計階段預先處理,否則結果可能回捲,造成難以察覺的錯誤。
在課堂與考試中,最常見的做法是:先判斷使用多少位元、屬有符號還是無符號,然後計算可表示範圍,再分析某次運算是否會上溢錯誤或下溢錯誤。實務上則常配合「範圍檢查」與「邊界測試」:讓測試資料刻意碰到最大值、最小值、以及臨界點,確保程式在極端情況仍正確。
示例說明:以取餘數(mod)模擬 8 位元整數回捲
下列偽代碼/Python 的重點在於「模擬固定 8 位元儲存」:不少系統會把整數存放在固定大小的欄位(例如 8 位元、16 位元、32 位元)。當數值寫入該欄位時,只有最低位的 n 個位元會被保留,其效果等同於對 2n 取餘數。
- 先求 u8:
u8 = n mod 256,代表「只保留最後 8 個位元」,因此 u8 一定落在 0 至 255。 - 再解讀 s8:若 u8 ≥ 128,表示最高位(符號位)為 1,按二補碼規則應解讀為負數,故
s8 = u8 - 256;否則s8 = u8。 - 觀察回捲:例如 127 + 1 = 128。對 8 位元有符號整數而言,128 會被解讀為 −128,這就是「上溢後回到最小值」的概念示例。
| 輸入 n | u8 = n mod 256 | s8(以 8 位元有符號解讀) |
|---|---|---|
| 127 | 127 | 127 |
| 128 | 128 | −128 |
| 200 | 200 | −56 |
| 300 | 44 | 44 |
| 255 | 255 | −1 |
注意:Python 的整數通常可自動擴展位數,因此不會因位數不足而溢出。本示例屬於「概念模擬」,用來展示固定 bit 儲存時可能出現的回捲行為。
輸入 整數 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),避免直接用
==。
- 截尾誤差:因為直接截去某些位數或部分資料而產生的誤差。
- 誤差累積:每一步的微小誤差在多次計算後逐步擴大。
- 容許誤差:比較浮點值時允許的差距範圍(例如 1×10−9)。
浮點數要在有限位元內同時表示「整數部分」與「小數部分」。不少十進制小數(例如 0.1)在二進制中屬無限循環小數,需要截斷或捨入才能儲存,因此天生會有微小偏差。
- 儲存時:截斷/捨入 → 產生誤差。
- 運算時:誤差參與下一步計算 → 可能累積。
- 在不少環境中,
0.1 + 0.2的結果可能是0.30000000000000004之類的值。 - 若你過早把小數轉為整數(例如
int(19.99)),小數部分會直接消失,令結果偏小。
- 截尾:直接切掉後面的位數(例如 3.149 → 3.14)。
- 捨入:按規則取近似值(例如四捨五入:3.149 → 3.15)。
- 兩者都會引入誤差,但誤差方向與大小可能不同。
- 直接用
==比較浮點結果,造成「看似相等但判斷為不相等」。 - 在迴圈中每一步都截尾/捨入中間結果,誤差更易累積。
- 把格式化顯示(例如只顯示兩位小數)誤以為「內部已精確」。
截尾誤差不一定代表「程式寫錯」。很多時候,它源自資料表示法與精度限制,或是你刻意降低精度(例如只保留兩位小數)。這類操作會令資訊流失,因此結果與真值產生差距。
當運算涉及大量步驟(例如累加、迴圈、模擬),即使每一步的誤差很小,也可能逐步累積,最後的偏差變得明顯。良好的處理方法包括:盡量在最後一步才截尾/捨入、使用足夠精度、以及在比較時使用容許誤差。
溫馨提示:在需要精確處理金額的情況,常見做法是把金額轉換成「最小單位的整數」(例如以「仙」為單位),以避免浮點數誤差。
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 則進位)。
- 捨入誤差:捨入後的值與原值之差。
捨入的本質是用較少位數去近似原數,因此必然會產生誤差。關鍵是:
- 先定規則:上捨/下捨/四捨五入要清楚定義,避免不同人做出不同答案。
- 理解誤差界限:例如四捨五入到 2 位小數,誤差通常不會超過 0.005。
假設要把兩個數相加後顯示兩位小數:
- 做法 A:每個數先四捨五入到兩位,再相加。
- 做法 B:先相加,再把總和四捨五入到兩位。
兩種做法有機會得到不同結果,因此在題目與系統規格中必須寫清楚「何時捨入」。
- 對正數而言,「截尾」與「下捨」結果往往相同(例如 2.9 → 2)。
- 對負數而言,兩者不同:例如 −2.9 截尾會得到 −2,但下捨會得到 −3。
- 因此寫程式或做題時,遇到負數必須特別留意採用的規則。
- 在每一步運算後都捨入:容易把誤差「鎖死」並逐步擴大。
- 把格式化顯示(例如只顯示兩位小數)當作「內部已修正」:格式化只改外觀,不一定改變內部儲存值。
- 沒有處理負數時的規則差異,導致答案偏差。
捨入與截尾的共同點是「降低精度」;不同之處在於規則不同。若在運算過程中多次捨入,誤差會更快累積,因此實務上常先以較高精度完成計算,最後才按題目要求捨入輸出。
另一方面,題目若涉及負數或臨界值(例如剛好 0.5),必須先確認採用的捨入規則與取整方向。只要規則一致,答案就可檢查、可重現,也更易寫成程式。
vals ← [2.4, 2.5, 2.6, -2.5, -2.9]
對每個 x ∈ vals:
輸出 x
輸出「截尾(向 0)」的結果
輸出「下捨」的結果
輸出「上捨」的結果
輸出「四捨五入」的結果
Check Point 3:捨入與捨入誤差(四選一.至少 20 題)
請按「下一題」開始。
4. 語法錯誤、運行時錯誤、邏輯錯誤
重點
- 語法錯誤:寫法不符合語法規則,程式通常無法開始執行(例如漏冒號、括號不成對)。
- 運行時錯誤:程式能開始執行,但執行到某一步才出錯(例如除以 0、索引超界)。
- 邏輯錯誤:程式能執行到底,但輸出不正確;需要靠測試資料與追蹤變量值定位。
- 處理錯誤時要懂得閱讀錯誤訊息:看錯誤類型、行號,並追溯出錯前的變量狀態與輸入。
- 語法錯誤:語法不合規則(例如
if少冒號)。 - 運行時錯誤:執行期間才出錯(例如除以 0、索引超界、讀入格式錯)。
- 邏輯錯誤:程式完成執行但結果不對(例如條件寫反、迴圈範圍差一)。
- 語法錯誤:解析階段已失敗 → 通常無法開始。
- 運行時錯誤:解析無問題 → 但某一步遇到不合法運算或資料 → 即時停止並顯示錯誤訊息。
- 邏輯錯誤:程式流程可走完 → 只是「做錯事」 → 不一定有任何錯誤訊息。
語法錯誤
temp = 38
if temp >= 37.5
print("發燒")運行時錯誤
a = 5 b = 0 print(a / b) # 除以 0
邏輯錯誤
n = 5
total = 0
for i in range(1, n): # 少了 n
total += i
print(total)- 一按執行就出錯:優先懷疑語法錯誤。
- 跑到一半才停:多半是運行時錯誤,查看錯誤類型與行號。
- 跑完但答案不對:邏輯錯誤,改用追蹤表/斷點/測試資料定位。
- 不看錯誤類型與行號,盲目亂改,效率極低。
- 只用一組測試資料:邏輯錯誤往往要多組測試(包含邊界/特殊值)。
- 一次改很多地方:難以判斷哪一個改動真正解決問題。
語法錯誤與運行時錯誤通常較容易處理,因為系統會提供錯誤類型與行號;你只要跟着訊息修正寫法或輸入處理即可。相反,邏輯錯誤最難,因為程式可能完全不報錯,只是答案不正確。
面對邏輯錯誤,建議先用小量且可重現的測試資料(例如 0、1、臨界值、最大值/最小值),再以追蹤表或 print() 觀察關鍵變量值。當你找到「答案開始偏離」的第一步,就能把問題範圍縮小到少數幾行,修正效率會大幅提升。
# 這部分以概念為主: # 語法錯誤:寫法不合規則,程式不能開始 # 運行時錯誤:能開始,但某一步不合法(例如除以 0) # 邏輯錯誤:能完成,但答案不正確 # 實務做法: # 1) 先令程式能開始(修語法) # 2) 再令程式能完成(修運行時錯誤) # 3) 最後用測試+追蹤變量值修邏輯錯誤
Check Point 4:錯誤類型判斷(四選一.至少 20 題)
請按「下一題」開始。
5. 除錯工具:追蹤變量值與斷點
重點
- 追蹤表可把每一步的變量值記錄下來,特別適合在小例子中找邏輯錯誤。
- 斷點可令程式在指定位置暫停,配合單步執行觀察變量如何改變。
- 除錯的核心流程:重現 → 縮小範圍 → 觀察 → 假設 → 驗證。
- 良好習慣:一次只改一個地方、修正後回歸測試、避免留下多餘的除錯輸出。
- 追蹤表:用表格逐步記錄每一步執行後的變量值。
- 追蹤變量值:在關鍵位置輸出變量值(或在 IDE 監察),以掌握程式狀態。
- 斷點:在某行設定暫停點,程式執行到該行時停止,讓你檢查當刻狀態。
- 追蹤表適合把流程拆成「一格一格」:每做一步就把 i、total 等值填入表格。
- 斷點適合在較長程式中使用:你可以讓程式停在關鍵位置,再逐行執行,觀察變量值改變。
- 無論用哪種工具,核心目標都是:找出「數值第一次變得不合理」的那一步。
考慮程式:
n = 5
total = 0
for i in range(1, n):
total += i
print(total)用追蹤表可發現 i 只去到 4,因此少加了 5。修正方向是檢查迴圈範圍(例如改為 range(1, n+1))。
- 追蹤表:最適合小程式與考試推理,清楚顯示每一步。
- print():最快、最通用;適合快速查看關鍵變量,但最後要移除。
- 斷點:適合較大程式,可在不改動輸出的情況下查看多個變量值。
- 未能重現問題就修改:難以證明「真的修好」。
- 修正後不做回歸測試:可能引入新錯誤。
- 保留除錯輸出:會破壞輸出格式(尤其是自動評測)。
除錯工具的目的不是「代你找錯」,而是令你更快掌握程式的真實執行情況。當你遇到邏輯錯誤時,單靠直覺往往不足以定位問題;追蹤表、追蹤變量值與斷點能把流程拆解,讓你看到每一步的變化。
建議的實戰做法是:先用小量測試資料重現問題,再用追蹤表或 print() 找出哪一步開始偏離預期;若你使用 IDE,則可用斷點配合單步執行,把問題範圍縮小到少數幾行,再作修正。每次修正後都要用原本的測試資料重新驗證,並補充邊界測試,確保問題不會再出現。
追蹤表示例(參考)
以下追蹤表對應程式:total = total + i(i 由 1 到 3)。
| 步驟 | i | total(更新後) |
|---|---|---|
| 初始 | — | 0 |
| 迴圈 1 | 1 | 1 |
| 迴圈 2 | 2 | 3 |
| 迴圈 3 | 3 | 6 |
nums ← [8, 10, 15, 7]
count ← 0
對每個 x ∈ nums:
如果 x ≥ 10:
count ← count + 1
輸出「目前 x、count」
輸出「≥10 的個數」, count
互動示範:斷點與單步執行如何追蹤變量值
本示範以一段簡單程式為例,模擬除錯器的「單步執行」與「斷點」行為。學習者可透過點擊行號設定斷點,並觀察每一步的變量值變化。
- 點擊左側行號旁的圓點欄位,以新增/取消斷點。
- 按「單步執行」逐行執行;按「繼續」可執行至下一個斷點(或到程式結束)。
- 觀察右側的「本地變量」及「輸出」,理解變量值如何隨步驟變化。
(尚未有輸出)
提示:本例的目標是計算 1 至 n 的總和,但迴圈使用 range(1, n),因此當 n = 5 時只會計到 4。可在第 4 行設定斷點並反覆按「繼續」,觀察 i 的最大值,從而定位「迴圈範圍差一」的問題。
Check Point 5:除錯工具與策略(四選一.至少 20 題)
請按「下一題」開始。