[#95] Stable amortization for tiny loan rates: expm1/log1p, +Inf books impossible #96
No reviewers
Labels
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Winnie/bankwars!96
Loading…
Reference in a new issue
No description provided.
Delete branch "94-amort"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #95.
The bug
amortPayment(P, r, n)usedP*r / (1 - (1+r)^-n). For a legal but tiny positive loan rate (empire's clearing-spread bid landed at exactly 2^-62 in duel seed 87113, inside the [0, cap] clamp),(1+r)rounds to exactly 1.0 in float64, the denominator becomes 0, and every payment on that loan becomes +Inf. The poisoned cash/equity then propagated into AI inputs, the #91 guard failed the other banks (bond-offer-size, bankruptcy-bid), the auction winner booked +Inf bargain gain, and iterations.json silently dropped the run's record (json: unsupported value: +Inf).Full trace and cascade in #95. Repro: duel preset seed 87113, roster super/empire/mill, master
8ec3eca.The fix
Rewrite the formula with
math.Expm1/math.Log1p:den = -expm1(-n*log1p(r)). For tiny r this is exactly n*r (no cancellation), so the payment lands on P/n instead of +Inf. Belt-and-suspenders: zero/negative denominator and any non-finite result fall back to P/n; r <= 0 keeps the old P/n path; n <= 0 returns P.Verification
TestAmortPaymentTinyRateStable(r = 2^-62, the exact repro value),TestAmortPaymentZeroRate,TestAmortPaymentOrdinaryRate(classic 5% amortization value pinned).go build+go test -count=1 .+go vet ./...all green.[aierrors, 0 DIVERGENCE, 0 NON-FINITE, 0 stack overflow.Files: game.go (amortPayment) + amort_test.go. Nothing else touched.