mirror of
https://github.com/XRPLF/rippled.git
synced 2026-04-29 15:37:57 +00:00
At near-zero `periodicRate`, the direct subtraction `power(1 + r, n) - 1` suffers catastrophic cancellation: `(1+r)^n` rounds to a value very close to 1 and the subtraction discards most of Number's 19-digit large-mantissa precision. The resulting amortization factor is inaccurate enough that `loanPrincipalFromPeriodicPayment` returns a principal greater than `periodicPayment * paymentsRemaining`, which propagates into `computeTheoreticalLoanState` as a negative `interestDue` and fires the `interest due delta not greater than outstanding` assertion in `computePaymentComponents` (testBugInterestDueDeltaCrash). The same numerical defect causes systematic underpayment of interest in release builds at the bug regime. On a $1B loan with the test parameters, closed-form charges only $0.0588 of interest versus the mathematically correct $0.3805 (verified independently via 50-digit Decimal arithmetic). Linear scaling: ~$321 underpaid per $1T of principal. Replace `raisedRate - 1` with a hybrid evaluator: - When `r * paymentsRemaining >= 1e-9`, use the closed-form `power(1+r, n) - 1`. At Number's 19-digit mantissa this still retains ~10 sig digits post-subtraction and is ~30-500x faster than the binomial expansion. - Below the threshold, expand `(1+r)^n - 1 = sum C(n,k) r^k` with early termination once terms fall below Number precision. The hybrid takes the closed-form path at every rate covered by existing fixtures, so output is bit-identical to pre-fix code at moderate rates (no fixture drift). Also drops the now-unused `computeRaisedRate` from the lending helpers (its only caller was `computePaymentFactor`). Test coverage: - testComputePowerMinusOne / testComputePowerMinusOneHybrid: direct unit tests for both new helpers, including property checks against `(1+r)^2 = 1 + 2r + r^2` and `(1+r)^3 = 1 + 3r + 3r^2 + r^3`, threshold-boundary verification at exactly r*n = 1e-9, and large-n early-termination. - testLoanPrincipalFromPeriodicPaymentNearZeroRate: regression guard, asserts `principal <= payment * n` at near-zero rate. - testComputeTheoreticalLoanStateNearZeroRate: regression guard, asserts `interestDue >= 0` and `principalOutstanding <= valueOutstanding`. - testBugInterestDueDeltaCrash: end-to-end reproduction of the original assertion abort, now passes cleanly. - testFullLifecycleVaultPnLNearZeroRate: integration test running a $1B loan to completion, verifies the vault collects the economically-correct interest matching the 50-digit Decimal reference within sub-microcent tolerance, plus self-consistency (vault gain == TVO - principal at LoanSet) and conservation (borrower outflow == vault gain + broker gain).