(1+r) 只解釋了其中三分之二
昨天攤開了十個沒有人做過決定的地方。今天做決定。
(閘門 1:這系列有五道閘門,都是「AI 產出、人驗收」的交接點。閘門 1 是最前面那道——規格層。它處理的不是程式對不對,是「什麼叫對」還沒有人說過。)
如果現在把那十條丟給 AI 問「每一條該怎麼算」,它會給我十個答案,語氣一致地肯定。再問一次,可能又是另外十個。不是因為它不夠聰明。是因為這些問題本來就沒有標準答案,而它不會告訴你這件事。
十個現場不是同一種東西,混在一起裁會出事。
第一堆:已經表態的,只是還沒寫成文字。
RC-02(折現時點)在 Day 4 就論證完了:期初才對,沒有人能等到年底才吃第一頓飯。這不需要再裁一次,需要的是把它寫得夠精確——精確到年份索引。
第二堆:規則根本缺席,補上就是。
RC-06(大筆支出沒有上界)和 RC-10(輸入防呆),這兩條沒有取捨可言,只有「當初漏了」。95 歲的支出要不要算進 85 歲就結束的規劃?填了倒置的年齡要不要擋?這種問題沒有第二種答案。
第三堆:真的要選邊站。
剩下三個,它們每一個都有兩種以上說得通的做法,而選哪一種會直接改變使用者看到的數字。
(先講結論:其中一個查到一半裂成兩條,所以下面會有四個裁決。)
r/12 到底改不改(RC-03)原本我打算把 RC-03(名目月利率)和 RC-04(同帳戶兩種複利頻率)綁在一起裁,理由是「這是業界通行寫法」。寫到這裡我停下來,問自己一句:「我憑什麼說那是通行寫法?」
所以我去打開六個線上試算器,能看原始碼的看原始碼,看不到的就用黑箱反解。結果如下:
| 試算器 | 月利率寫法 | 年金時點 | 怎麼確認的 |
|---|---|---|---|
| 中國信託・退休試算 | expectedRoi / 12 |
PVIFA 期末 | 原始碼 |
| 中國信託・投資試算 | roi / 100 / 12 |
FVIFA 期初 | 原始碼 |
| 台北富邦・退休計畫試算 | yRate / 1200 |
期初 | 原始碼 |
| 國泰世華・嚮退 Easy | investRatio / 100 / 12 |
Excel pmt/fv |
原始碼 |
| 國泰世華・試算小幫手 | 年複利,不是 r/12 |
期末 ×(1+r)^(1/12) | 黑箱反解 |
| YP-Finance・複利計算機 | (1+r)^(1/12) − 1 |
月底投入(期末) | 頁面自述 |
中國信託的程式碼甚至把註解寫在旁邊:
// r=預期投資報酬率/12
r: parseFloat((expectedRoi / 12).toFixed(4)),
富邦更直接,整段計算就三行:
var rate = yRate / 1200; // 年利率% ÷ 100 ÷ 12
for (i = 0; i < year * 12; i++) { tRate *= (1 + rate) }
var total = totalAmt / (tRate - 1) / (1 + rate) * rate;
裁決:維持 r/12。 六個裡有四個這樣寫,理由從「我的業界認知」變成「我查過」。
但要記兩件事。第一,國泰世華自己的兩個計算機用了兩套不同假設:同一家銀行,一個 r/12、一個年複利;第二,唯一把假設寫在頁面上給使用者看的,是那個獨立部落格做的計算機:
本計算機採用每月複利計算,年利率會轉換為月利率:月利率 = (1+年利率)^(1/12)-1
投資時點假設:採用「月底投入」模式
它用的是我沒選的那個公式,而且它是唯一一個告訴你它選了什麼的。
原本要一起維持現況。查完之後改不了了。受測物是本金按「年複利」、月存按「月複利」(r/12),同一個帳戶、同一個報酬率、相鄰兩行程式碼,兩種頻率。
而上面六個試算器裡,沒有一個這樣做。中信和富邦全程 r/12,國泰試算小幫手全程年複利,YP 全程有效月利率。每一家內部都是一致的。所以「維持現況」的理由當場消失了:它不是市場慣例,它就是我們自己的不一致。
裁決:統一為 r/12,本金也改成 (1 + r/12)^(12n)。
兩種寫法差多少,算給大家看(基準情境:本金 80 萬、月存 19,391、8%、23 年):
本金部分 現況(年複利) 4,697,171
統一 r/12 5,006,566 +6.59%
累積期合計 現況 19,991,456
統一後 20,300,851 +1.55%
選 r/12 這個方向而不是反過來把月存改成年複利,理由是前者跟 RC-03 的裁決一致。我在這裡刻意讓兩條裁決共用同一把尺,因為我在後記會說明為什麼這件事比每條裁決本身更重要。
第 320、363 行的 max(0, 支出 − 收入):某一年收入大於支出時,多出來的錢直接蒸發。
裁決:(a),維持現況。
理由是保守,退休規劃寧可低估晚年資產,也不要高估。選 (b) 會讓那 12 組「目標金額 = 0」的案例變成更樂觀的數字,而那正是我不想給使用者的東西。
裁決:維持全額指數化,但在文件層明確標示它與法規的落差。
昨天說過,現實是累計 CPI 達 5% 才階梯式調整。要把階梯模型放進計算核心,得多一組參數和一段狀態機,而使用者拿不到那些輸入。所以這一條我不是選「哪個對」,是選「哪個做得到」。這種裁決最危險,因為它容易被記成技術限制,而忘了它同時也是一個對使用者的承諾。寫進文件是為了讓它不被忘記。
三堆處理完,十條全部有了條款。從今天起,PRD 沒寫的行為,程式碼不准自己發揮。
| 編號 | 規則 | 內容 | 邊界處置 |
|---|---|---|---|
| PRD-01 | 累積期增值 | 本金與月存統一採名目月利率 r/12,同為月複利 |
r = 0 時退化為線性累加 |
| PRD-02 | 目標金額折現 | 期初年金,第一期對應退休當年 A_r,通膨基準鎖現齡 A_c |
A_d ≤ A_r 於表單層攔截 |
| PRD-03 | 提領餘額軌跡 | 期初扣款,索引與 PRD-02 對齊 | 禁止 Math.max(0, ...) 截斷真實餘額 |
| PRD-04 | 輸入校驗 | 年齡為正整數且 A_c < A_r < A_d;金額非負 |
禁止靜默補零或整筆丟棄,須回傳明確錯誤 |
| PRD-05 | 大筆支出區間 | 僅 A_r ≤ A_e ≤ A_d 計入 |
超出壽命者兩條路徑皆排除,介面標註已忽略 |
| PRD-06 | 勞保年金 | t ≥ 請領年齡 時計入,隨通膨調增 |
未達請領年齡為 0 |
| PRD-07 | 勞退月領 | 同上 | 同上 |
| PRD-08 | 固定年支出 | 與月支出合併,共用同一組通膨指數 | — |
| PRD-09 | 淨支出下限 | max(0, 支出 − 收入),盈餘不滾存 |
保守假設,已知並接受 |
| PRD-10 | 收入指數化 | 退休後收入全額隨通膨調增 | 與法規階梯式調整有落差,已標註 |
十條裡有四條要求修改現況(PRD-02、03、04、05),六條把現況正式化。
完整版在 spec/PRD-v1.md,裡面附了一張「考古現場 → 條款」的對照表。對照不是一對一,PRD-01 同時承載 RC-03、RC-04、RC-09,而 RC-07 一條散到三個條款。這種多對多就是我後面要做需求追溯矩陣的理由:沒有那張表,Day 30 修完之後沒有人說得出 PRD-01 到底是被誰決定的。
PRD-02 看起來只是把期末改成期初,但它其實動了兩件事:折現指數,以及第一年是哪一年。現況的迴圈從 i = 1 起算,第一筆支出落在 66 歲,總共算 20 年。新規格第一筆落在 65 歲,算到 85 歲,共 21 年。
拿基準情境跑:
現況(期末、66–85,20 年) 9,317,668
只改期初(仍 20 年) 9,690,374 +4.00%
新規格(期初、65–85,21 年) 9,885,351 +6.09%
Day 4 花了一整篇推導那個 (1+r),它解釋的是中間那一列:372,707 元,而多算的那一年又加了 194,977 元。合計 567,684(差額由未取整的值算出,拿上面三個整數相減會差 1 元),其中三分之一來自一個從來沒有人討論過的迴圈起始值。我用一整篇文章推導的數學,只佔偏差的三分之二;剩下三分之一藏在 i = 1 裡,而它連缺陷編號都沒有。
裁決做完,我回頭看自己寫的理由,發現一件不太舒服的事。
r/12,理由是「別人都這樣算」四個決定,四種判準。現實正確性、市場相容性、風險保守性、實作可行性,每一個單獨看都站得住,放在一起看就會發現:我是在每一題各自挑一把最順手的尺。
而 RC-02 和歧義 A 這兩把尺是直接互斥的。「沒有人等到年底才吃第一頓飯」用的是現實正確性;(1+r)^(1/12)−1 在同一把尺下也比 r/12 正確,我卻選了市場相容性。同一篇文章裡,我用兩套標準判了兩件同構的事。
我沒有回頭改,因為那兩個裁決我仍然認為結論是對的。但我把矛盾寫在這裡,而不是把理由改得漂亮一點。
真正救了我的是那六個試算器。如果我沒有去查,「這是業界通行寫法」就會成立,RC-04 也會跟著被裁成「維持現況」,用一句我沒驗證過的話,一次過掉兩條規格。
閘門 1 攔的不只是 AI。它也攔我自己那些聽起來很有道理、但從來沒查過的句子。
只帶走一件事
歧義不是 bug。它是還沒有人做的決定,而程式碼已經先替你做了。
今天的 repo:https://github.com/eyelash500/2026_ironman_test_ai/tree/day09