提案:Beancount 中的舍入与精度

Martin Blais,2014 年 10 月

本文描述了 Beancount 交易中舍入误差的问题及其处理方式,并包含

一项关于改进 Beancount 中精度问题处理的提案。

动机

平衡精度

交易的平衡无法精确完成。这一点此前已在 Ledger 邮件列表中讨论过。必须允许在平衡交易时对金额使用一定的容差。

当考虑到在文本文件中输入数字意味着有限的小数表示时,这一需求就显而易见了。例如,如果你要将单位数量与价格或成本相乘,假设两者均保留两位小数,则结果可能产生四位小数,而你需要将该结果与通常仅保留两位小数的现金金额进行比较,如下例所示:

2014-05-06 * “Buy mutual fund”
  Assets:Investments:RGXGX       4.27 RGAGX {53.21 USD} 
  Assets:Investments:Cash     -227.21 USD

若进行计算,第一笔分录的精确余额为 227.2067 美元,而非 227.21 美元。然而,管理投资账户的经纪公司会对现金提取金额四舍五入至最接近的美分,而该舍入后的金额才是正确的值。此交易必须平衡;我们需要以某种方式允许一定的弹性。

绝大多数涉及数学运算的情况,都是将单位数量与价格或成本转换为相应的现金价值(例如:单位 × 成本 = 总成本)。我们表示交易信息的任务,是复现主要发生在金融机构中的操作。这些操作始终涉及对单位和货币数值的舍入(银行确实采用随机舍入),而从这些机构和政府的角度来看,正确的数值正是这些舍入后的数值本身。这并非数学纯粹性的问题,而是实用性问题,我们的系统应与银行的做法一致。因此,我认为我们应始终将舍入后的数值记入账户。使用有理数在这一点上并非限制,但我们必须谨慎地在关键位置存储舍入后的数值。

自动舍入

另一个相关问题是自动对插值数值进行舍入。让我们再次回顾原始的 problematic 示例:

2014-05-06 * “Buy mutual fund”
  Assets:Investments:RGXGX       4.27 RGAGX {53.21 USD} 
  Assets:Investments:Cash     ;; Interpolated posting = -227.2067 USD

此处,第二笔分录的金额是从第一笔分录的余额推算得出的。理想情况下,我们应该找到一种方式,指定其应舍入至两位小数的精度。

请注意,这同样影响插值价格和成本:

2014-05-06 * “Buy mutual fund”
  Assets:Investments:RGXGX       4.27 RGAGX {USD} 
  Assets:Investments:Cash     -227.21 USD

此处,成本应自动由现金分录计算得出:227.21 / 4.27 = 53.2107728337… 美元。应推断出的正确成本同样是舍入后的 53.21 美元。我们需要一种机制,以便推断出所需的精度。

不幸的是,这种机制不能仅基于商品:不同账户可能以不同的精度追踪货币。作为一个现实中的例子,我有一个零售外汇交易账户,其价格和存款实际使用四位小数精度。

余额断言的精度

余额断言的精度也受此问题影响,例如以下断言:

2014-04-01 balance Assets:Investments:Cash   4526.77 USD

用户并不希望这个余额检查精确地等于 4526.77000000… 美元。然而,如果该现金账户此前曾接收过如上一节示例中那样更高精度的存款,那么我们就遇到了问题。现在,现金金额中包含了由插值产生的零星金额(0.0067 美元)。如果我们能在上一节中为自动舍入分录找到一个良好的解决方案,这将不成问题。但在此期间,我们必须找到一个解决办法。

Beancount 当前的解决方案是一个临时修补:它使用一个用户可配置的容差(0.0150,任意单位)。我们希望改进这一点,使容差能够根据商品、账户,甚至所使用的特定指令动态调整。

其他系统

其他命令行会计系统在选择容差时方式不同:

提案

自动推断容差

Beancount 应为每个交易独立推断其精度,或许可设置一个全局默认值。也就是说,对于每个交易,它将检查具有简单金额(无成本、无价格)的分录,并推断出容差精度为本交易中用户输入的最精确金额的一半。例如:

2014-05-06 * “Buy mutual fund”
  Assets:Investments:RGXGX       4.278  RGAGX {53.21 USD} 
  Assets:Investments:Cash     -227.6324 USD
  Expenses:Commissions           9.95   USD

此处使用的精度位数是第二和第三条分录的最大值,即 max(4, 2) = 4。第一条分录被忽略,因为其金额是数学运算的结果。容差值应为最精确位数的一半,即 0.00005 美元。这将允许用户通过在输入中添加更多小数位,自由使用任意精度。

仅使用小数位来推断精度……整数应表示精确匹配。用户可通过添加一个尾随句点来表示低于一美元的精度。例如,以下交易应因计算金额为 999.999455 美元而无法平衡:

2014-05-06 * “Buy mutual fund”
  Assets:Investments:RGXGX       23.45 RGAGX {42.6439 USD} 
  Assets:Investments:Cash        -1000 USD

相反,用户应显式允许使用一定的容差:

2014-05-06 * “Buy mutual fund”
  Assets:Investments:RGXGX       23.45 RGAGX {42.6439 USD} 
  Assets:Investments:Cash       -1000. USD

或者更好,使用 1000.00 美元。这样做的缺点是阻止了用户指定更简单的整数金额。我不确定这是否是个大问题。

最后,交易不会产生任何全局影响。任何交易都不应影响其他交易的平衡上下文。

基于成本持有的金额推断

Matthew Harris(此处)提出的一个想法是,我们还可以使用单位数量的最小小数位乘以成本所得的数值,作为确定交易平衡容差的依据。例如,在以下交易中:

2014-05-06 * “Buy mutual fund”
  Assets:Investments:RGXGX       23.45 RGAGX {42.6439 USD} 
  ...

可推导出的容差值为

0.01 RGAGX × 42.6439 美元 = 0.426439 美元

Matthew 提出的原始用例是一个不包含简单金额、仅包含双方均按成本计价的转换交易:

  2011-01-25 * "Transfer of Assets, 3467.90 USD"
    * Assets:RothIRA:Vanguard:VTIVX  250.752 VTIVX {18.35 USD} @ 13.83 USD  
    * Assets:RothIRA:DodgeCox:DODGX  -30.892 DODGX {148.93 USD} @ 112.26 USD

我实际上尝试过实现这一方案,但得出的容差要么过大,要么过小。实践中效果不佳,因此我已放弃该想法。

自动四舍五入

对于自动计算的数值(例如在自动生成分录中,剩余金额由系统自动推导),我们应考虑对这些数值进行四舍五入。目前尚未对此进行任何工作;这些数值目前未被四舍五入。

修正余额断言

为修正余额断言,我们将根据余额金额本身使用的位数推导所需精度:观察最精确的小数位,并取该位值的一半作为容差:

2014-04-01 balance Assets:Investments:Cash   4526.7702 USD

此余额检查意味着精度为 0.00005 美元。

如果你使用整数单位,则不允许任何容差。精确数值必须完全匹配:

2014-04-01 balance Assets:Investments:Cash   4526 USD

如果你想允许低于一美元的偏差,请使用一个逗号:

2014-04-01 balance Assets:Investments:Cash   4526. USD

此余额检查意味着精度为 0.50 美元。

近似断言

另一个建议来自 Ledger 的此工单,提出了一种显式的近似断言。

我们可以这样实现(仅作设想):

2014-04-01 balance Assets:Investments:Cash   4526.00 +/- 0.05 USD

累积与报告残差

为了明确呈现和监控 Ledger 中发生的四舍五入误差,我们应将其累积至权益账户,例如“Equity:Rounding”。此功能应可选启用。用户应能指定用于累积误差的账户。每当交易无法精确平衡时,残差或四舍五入误差将作为一笔分录插入到权益账户中。

默认情况下,此累积功能应关闭。目前尚不清楚这些额外分录是否会带来干扰(如果不会,或许应默认开启;实践将告诉我们答案)。

实现

本提案的实现在此处记录