# Aurum 设计笔记

## 分层架构

Aurum 按「金额类型、估算原语、领域模块」组织：

- **money**：以币种最小货币单位为存储基础的整数金额、十进制文本解析、比例运算与分摊原语；整数金额运算避免二进制浮点表示误差，但仍受 32 位范围和舍入策略约束。
- **tvm**：货币时间价值原语（复利、年金、永续年金、名义/有效年利率换算），为上层模块提供折现 / 复利能力。
- **领域模块**：capital（资本预算）和 bond（债券）复用 tvm；currency（币种元数据及换算）使用 money；amortize（摊销）、wear（折旧）、ratio（比率）直接按各自公式计算。

## 数值精度策略

金融计算中的误差有两个来源：二进制浮点表示误差与算法上的抵消误差。

1. **金额用整数**：`Money` 以币种最小单位（分、日元等）存储 32 位 `Int`，加减与整数倍缩放避免 `0.1 + 0.2 != 0.3` 一类表示误差；超出范围时中止。
2. **估算用 Double**：利率、收益率、折现等比率与估算值使用 IEEE-754 `Double`，文档明确其为「估算」，不承诺分级精度。
3. **减少抵消误差**：年金公式、等额本息月供和名义/有效利率换算在利率接近零时用 `ln_1p` / `expm1`，避免 `(1+r)^n - 1` 的灾难性抵消。
4. **按 API 处理失败**：`tvm` 和 `capital` 的已检查参数无效或计算结果非有限时返回 `None`；`amortize`、`wear`、`bond`、`ratio`、`currency` 只实施各自源码所列的校验。`Money::of`、`Money::add/sub`、`round_to` 等基础 API 对非法币种、币种不匹配、舍入位数越界或结果超出 `Int` 范围会中止；新增的文本解析、比例缩放和分摊 API 是显式可失败版本，无效输入或越界结果返回 `None`。
5. **实际货币计划**：常规摊销 API 使用 `Double` 做估算；`amortize` 的 `MoneyPayment` API 则逐期将利息按显式舍入模式转成最小单位，采用整数余额迭代，并在末期清零。它仅支持正本金、非负每期利率和当前 `Money` 的 32 位范围，不含费用、日期或合同规则。

## API 约定

- 金额：`Money` 值类型；除 `Money::of` 外，也可用 `parse` 严格读取十进制文本，或用 `parse_rounded` 显式舍入输入。`to_decimal_string` 生成无分组符的固定精度文本以供存储/往返。支持 `try_add/sub/neg/scale`、`sum`、`try_compare`、`scale_ratio`、等额分摊和最大余数权重分摊；金额算术均受 32 位范围约束。
- 估算与结算计划：多数财务函数返回 `Double?`，估算摊销计划返回 `Array[Payment]?`；按最小单位的贷款计划返回 `Array[MoneyPayment]?`，可用 `summarize_money_schedule` 核验逐期衔接与汇总。等额本息计划支持按期指定额外还本金额。`capital.discounted_cash_flow_schedule` 提供 NPV 逐项现值以便核验。`None` 还可能表示未回收、无可报告的 IRR 或无法括根的 YTM。调用方不能只凭 `None` 判断是哪一种原因。
- 校验：`tvm` 有限期函数要求 `rate > -1`、`periods >= 0`；摊销、折旧、债券要求正期数；永续年金有独立的利率约束；比率只检查零分母与最终比值的有限性，并未逐一验证输入的财务意义。
- 求根：IRR 先要求现金流恰好一次符号变化，再在指定区间二分；YTM 在实现的区间内尝试括根并二分。两者最多二分 256 次，可能返回 `None`，不保证任意数据都有解。
- 币种：仅内置 12 种币种，不提供完整 ISO 4217 数据库。`currency.lookup` 的未知代码返回 `None`，而 `Money::of`、`Money::zero`、`currency_decimals` 遇未知代码会中止；`Money::try_of`、`Money::parse` 对未知币种返回 `None`。`add`/`sub` 币种不匹配、`round_to` 位数越界及基础 `Money` 运算溢出会中止；`scale_ratio` 和分摊函数遇无效参数或结果越界返回 `None`。`round_div` 的非正分母会中止。
- 汇率：`currency.convert` 和 `cross_rate` 只使用调用方传入的汇率，未接入实时行情。`convert` 先用 `Double` 换算，再按 half-up 舍入为目标币种的最小单位；无效汇率、未知目标币种、非有限或舍入后超出 `Int` 范围的结果返回 `None`。不承诺十进制精确汇率换算。

## 关键算法

- **IRR**：`one_sign_change` 保守拒绝多符号变化现金流；区间端点异号后二分。
- **YTM**：以「债券价格 − 市价」为目标函数，从 `[-0.999999999999, 10]` 开始尝试括根，必要时右端点最多翻倍扩展 60 次，然后二分。
- **久期 / 凸性**：按现金流加权平均的解析式直接计算。
- **汇率换算**：`minor × rate × 10^(目标小数位 − 源小数位)` 后半向上取整到目标最小单位。
- **权重分摊**：按绝对金额计算各权重的商和余数，余数从大到小分配剩余最小单位；并列时以输入下标稳定排序，负金额在分配后恢复符号。
- **分位摊销**：每期利息为 `round(balance_minor × rate)`；等额本金保持前期本金相同、将本金除不尽的余数放在最后一期，等额本息从浮点公式得到首个固定月供后舍入到最小单位。所有期次均检查 `payment = principal + interest`，清零后保留零金额行以对应请求期数。
