知识卡片

货币分配器方法用零误差伪随机方式解决多方分账

支付系统案例卡

内容

针对[[资金分配的舍入误差问题5分钱按比例分账无解]],作者列出几种应对方案并给出明确的优先排序。较差的方案:直接忽略误差(会计对不上账,行不通);把最后一笔分配额定义为”总额减去已分配部分”(避免了总账不平衡,但把误差全部堆积到最后一份,不公平也不透明);让调用方在每次调用时显式声明取整方式(能精确控制,但账户一多、税率计算一多,就会让代码充斥大量逐项声明,变得凌乱不堪)。作者最推崇的方案是给货币类加一个分配器(allocate)方法:调用形式类似aMoney.allocate([7, 3]),传入一组分配比例,分配器直接返回一组分配后的具体金额,并保证这组金额加总起来精确等于原始总额、不多不少;其内部把舍入误差以外部看起来近乎伪随机的方式分摊到各笔分配额中,而不是集中到某一笔或凭直觉四舍五入。这个方法唯一的代价是开发者必须记得主动调用它(而不是顺手写total * 0.7),而且很难对”误差具体分给哪一份”施加精确规则——但换来的是从源头上消除了[[资金分配的舍入误差问题5分钱按比例分账无解]]里描述的账目对不上问题,不需要靠事后对账或人工修正来弥补。可迁移启发:处理”总量必须被精确拆分给多方、且各份之和必须严格等于总量”这类问题时,正确的工程做法是把”分配”本身封装成一个专门的、返回一组结果的操作(而不是让调用方对每一份各自做乘法再各自取整),由这个操作内部统一协调舍入策略,从设计上保证总量守恒这条不变量。

参考来源

- 位置:《企业应用架构模式》第二部分"模式"之"第18章 基本模式"之"18.7.1 运行机制"(源文件:_epub-src/OEBPS/Text/000226.html) - 结论依据:原文说明"我最欣赏的方法是在货币类中添加一个分配器函数。该函数的参数是指明一组分配比例的数字(它的调用形式看起来可能像aMoney.allocate([7,3]))。分配器返回一组分配后的金额,并且保证没有一分钱的误差,所有误差都以伪随机的方式(从外界看起来是如此)分配到每笔资金中。分配器也有其缺点:你必须记得使用它,而且很难对其施加某种误差分配的精确规则",直接支撑本卡结论。 - 原始内容:分配器返回一组分配后的金额,并且保证没有一分钱的误差,所有误差都以伪随机的方式(从外界看起来是如此)分配到每笔资金中。