iT邦幫忙

2026 iThome 鐵人賽

DAY 4
0
AI Engineering

AI 寫的測試,誰來測?系列 第 4

【Day4】那個 (1+r) 是怎麼來的

  • 分享至 

  • xImage
  •  

TL;DR

  • 缺陷 A 的偏差不是隨機的:正確的目標金額正好是系統值的 (1+r) 倍,純用等比級數就推得出來
  • 昨天那筆被藏起來的赤字也有閉式解:− r × 目標金額 × (1+r)^N,分毫不差
  • 偏差乾淨到能寫成閉式解,幾乎就代表它不是誰打錯字,而是兩邊實作了不同的假設

https://ithelp.ithome.com.tw/upload/images/20260904/201038269DRGT9nEA2.jpg


前言

昨天說缺陷 A 是「兩條路的假設對不齊」,然後說偏差正好是一個 (1 + r) 倍。

今天把「正好」這兩個字證明出來。

兩種年金

「年金」在財務數學裡就是一連串固定金額的現金流,但它還是有兩種些微的差異,差別只發生在計算時是發生在每一期的期初還是期末

  • 期末年金:每一期結束時才付款。教科書的預設,公式最漂亮。
  • 期初年金:每一期一開始就付款。

哪個比較符合退休生活?期初。 沒有人能等到退休第一年年底才吃第一頓飯,你是年初就開始花錢的。

而第 321-322 行的目標金額,用的是期末年金。

推導

設每年支出 E、實質報酬率 r、退休後年數 N

期末年金的現值,是把每一年的支出各自折現回今天:

PV_期末 = E/(1+r)^1 + E/(1+r)^2 + ... + E/(1+r)^N

這是首項 E/(1+r)、公比 1/(1+r) 的等比級數,N 項求和:

PV_期末 = E × (1 − (1+r)^(−N)) / r

期初年金呢?第一筆錢在第 0 期期初就付掉,根本不用折現;第二筆折現一期;以此類推:

PV_期初 = E/(1+r)^0 + E/(1+r)^1 + ... + E/(1+r)^(N−1)

把這兩串排在一起看,會發現每一項都差一個折現期

PV_期末 =        E/(1+r)^1 + E/(1+r)^2 + ... + E/(1+r)^N
PV_期初 = E/(1+r)^0 + E/(1+r)^1 + ... + E/(1+r)^(N−1)

第二串的每一項,都是第一串對應項乘上 (1+r)。所以整串也是:

PV_期初 = (1 + r) × PV_期末

推導就結束囉!不需要跑程式,不需要實測,這是恆等式。

系統用期末公式算目標金額,另一邊卻用期初的方式模擬資產消耗。所以:

正確的目標金額 = 系統算出的目標金額 × (1 + r)

刻意不把它寫成百分比。「低估了幾 %」這種說法會逼我們選一個分母——除以正確值和除以系統值,答案不一樣(r = 4% 時是 3.85% 和 4.00%)。倍數不用選分母,也不會被讀成兩種意思。而這也解釋了昨天那件事:r = 0 的時候,(1 + 0) = 1,兩個公式算出來一模一樣。我去年的邊界測試正好踩在這個點上。

但這支工具的現金流沒那麼乖

上面用了等比級數求和,而等比級數的前提是每期金額一樣。但這支工具完全不是那樣,它的年度支出會隨通膨成長。扣掉勞保、勞退之後才是淨支出;勞保還有起領年齡,所以現金流中途會跳一階;另外還可能有一筆 75 歲的大額醫療支出。

(1+r) 還成立嗎?

成立。而且理由比我原本想的更乾脆:等比級數求和公式在這裡根本用不到。

回頭看那兩串算式:

PV_期末 =        E₁/(1+r)^1 + E₂/(1+r)^2 + ... + E_N/(1+r)^N
PV_期初 = E₁/(1+r)^0 + E₂/(1+r)^1 + ... + E_N/(1+r)^(N−1)

E 改寫成 E₁, E₂, ... E_N,這樣每一年可以是完全不同的金額。

配對關係一點都沒變:第 k 項的期初值,就是第 k 項的期末值乘上 (1+r)。不管那一項的分子是 36 萬、是扣掉勞保後的餘額、還是一筆三百萬的醫療支出。整串往前挪一期,總和就乘 (1+r)。**跟現金流長什麼樣完全無關。**求和公式只是讓式子好看,不是證明的關鍵。

不過這種「應該成立」我不太信,所以跑了五種情境:

r = 4%,比值應為 1.04

情境                      期末              期初              比值
純年度支出           15,529,445.84     16,150,623.67    1.0400000000
+勞保(65起2.4萬/月)   8,075,311.83      8,398,324.31    1.0400000000
+勞保晚領(70起)       9,806,227.28     10,198,476.37    1.0400000000
+大筆支出(75歲300萬) 19,425,217.82     20,202,226.53    1.0400000000
全部一起             10,221,602.69     10,630,466.80    1.0400000000

以基準情境(42 歲、65 歲退休、85 歲壽命、月支出 3 萬、通膨 2%)換四組報酬率試算,結果都一樣:

   r          受測物(期末)           正確(期初)               比值
----------------------------------------------------------
   1%    12,611,433.14    12,737,547.47     1.0100000000
   4%     9,317,667.50     9,690,374.20     1.0400000000
   7%     7,133,775.12     7,633,139.38     1.0700000000
  10%     5,639,269.86     6,203,196.84     1.1000000000

一件必須講清楚的事:這不是差分測試。 這是同一支 Python 影子模型的兩種模式互比,證明的是「我的推導和我的實作一致」。至於「我的實作和線上那支 JavaScript 一致嗎」,這我們之後實作才會知道結果。

而「受測物確實在用期末公式」這個判斷,依據不是這張表,是昨天貼出來的第 321 行程式碼。表格驗的是數學,程式碼驗的是事實,兩件事不要混。

那筆赤字也有閉式解

昨天說,使用者剛好存到目標金額的話,85 歲會是 −816,646。

那個數字也有閉式解:

最終餘額 = − r × 目標金額 × (1 + r)^N

補充說明:「閉式解(Closed-form solution)」是指可以用一個簡潔的數學公式直接算出答案,完全不需要跑迴圈、也不會有任何雜訊。

跑三組對一下,結果分毫不差:

   r           實跑最終餘額              閉式解          差
----------------------------------------------------
   3%      -556,160.52      -556,160.52      -0.00
   4%      -816,646.28      -816,646.28      -0.00
   5%    -1,126,565.83    -1,126,565.83      -0.00

這就代表:那筆赤字不是某個倒楣使用者的遭遇,是任何一個照系統建議存錢的人都會遇到的、可以事先算出來的金額。報酬率設得愈高,赤字愈大——而報酬率高看起來明明是好事。

回到主線:當一個 bug 的偏差是乾淨的閉式解,它幾乎一定是假設問題,不是實作問題。 如果是打錯字、變數用錯、迴圈少跑一次,偏差會是雜亂的、跟參數無關的、或是只在某些輸入下出現的。而一個在所有參數下都精確等於 (1+r) 的偏差,只可能來自「兩邊各自嚴謹地實作了不同的東西」。

這也是為什麼它躲得過所有單點驗證——每一點都對,錯的是在整體。

順帶補一句昨天的對照組:文章版沒有這個問題,因為它沒有目標金額折現路徑,一路模擬到壽命終點就結束了。沒有第二條路,(1+r) 這個倍數就無處誕生。 這個缺陷的存在條件不是「算錯」,是「有兩條路」。

為什麼要把 A 和 A' 分開編號

昨天把 A 和 A' 放在一起講完了,但沒解釋為什麼要編兩個號。

缺陷 A 單獨存在的話,只是一個算少了的公式。使用者會存不夠,但如果他有別的方式檢查(例如自己拉個試算表、或問另一個工具),他有機會發現這缺陷。

那行 Math.max(0, ...) 做的事,是把發現的機會拿掉。

它不製造錯誤,它隱藏錯誤。而且它隱藏的方式特別有說服力:曲線平滑落地,比任何「你還缺 81 萬」的紅字都更讓人安心。所以我把它另外編號。一個「算錯」和一個「讓你看不見算錯」是兩種東西——前者是「精度問題」,後者是「可觀測性問題」。

而可觀測性問題在測試上特別麻煩:你的測試如果也是從圖表資料取值,它會拿到那個被夾過的 0,然後告訴你一切正常。遮羞布會同時騙過使用者和測試。這也是為什麼要建影子模型,因為我需要一個不經過任何顯示層、不做任何美化的數字來源。

後記

我讓 AI 重改,它抓到那張表在繞圈,也去跑了真實數字。那一輪確實有進展,但它從頭到尾沒想過要測勞保、勞退、大筆支出,而那正是這個推導唯一會被質疑的地方。它檢查了它想得到的東西,檢查得很仔細,卻沒有看到重點。那不就是 Day 2 講的那個最根本的盲區嗎:「我測了我挑的那組數字,而且測得很認真」。


只帶走一件事
一個乾淨到可以寫成閉式解的偏差,幾乎一定不是誰打錯字,而是兩邊各自嚴謹地實作了不同的假設。


免責聲明:本文提及之退休試算工具與所有數字皆為假設性試算,不構成任何投資建議。該工具目前已知存在計算偏差,修正版製作中,結果僅供參考。


上一篇
【Day3】四個缺陷的驗屍報告
系列文
AI 寫的測試,誰來測?4
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言