1. 什么是分摊
分摊算法主要适用于需要将 总量(如金额、资源、费用、收益)按某种规则分配给多个部分的场景。
为了更好地理解“分摊”概念,可以通过一个常见的电商购物场景来说明。假设有一个满减优惠活动:满20元减10元。你凑单购买了两件商品, 最终实付20元,每个商品各自的实付金额计算过程如下
- 计算每个商品的支付占比
- 计算实付金额
商品 | 单价 | 数量 | 分摊比例(四舍五入,2位精度) | 实付金额 |
---|---|---|---|---|
商品A | 12 | 1 | 12/30 = 0.4 | $0.4 * 20 =8$ |
商品B | 18 | 1 | 18/30 = 0.6 | $0.6 * 20 =12$ |
如果用户后续在其中一个商品上产生了退款操作,那么退款金额应该是他在这个商品上实际支付的价格,而不应该是原价
2. 使用“分”作为金额单位进行计算
在讨论分摊算法的实现前, 需要先说明一下处理“钱”相关逻辑时,都是以“分”作为最小单位进行处理的。
在各种场景下,比如领取微信红包、 微信支付宝余额展示、 凑单时每单的实付金额、股票账户,都可以清楚得看到,在展示给用户时,金额单位都是“元”,并且精确到两位小数, 即“分”。
但在代码层面处理金额时,很多支付系统、银行系统和电商平台中,都是使用 “分” 作为单位。以上那些展示给用户带小数点的数字,其实都是以整数的形式存在,数据类型为int。
在数据库中存储金额时,使用 BIGINT
类型。
使用分作为金额处理的单位,可以避免使用浮点数类型如 float
或 double
。
浮点数类型在存储和参与计算的过程中,是会产生精度误差的。尤其在多次加减时,这种误差可能累积并产生不精确的结果,影响到数据的正确性。
举个简单的例子,如果我们在计算金额时用浮点数:
1 | 0.1 + 0.2 == 0.3 |
在某些情况下,实际计算结果可能不等于0.3
,而是0.30000000000000004
,这是因为浮点数精度有限。即使这种误差在日常应用中很小,但在金融系统中,这样的误差一旦累计,就可能导致资金的偏差,影响账务的准确性。
同时 分也是处理金额的最小单位,如果在以分为最小单位的处理逻辑中出现了精度更高的小数位,一般都会舍弃掉。
以下内容讨论如何实现一个分摊算法
3. 分摊算法最重要的问题-分摊后的总和必须等于原始值
在分摊算法中,最重要的一点就是确保分摊后各个部分的总和与原始值一致。 不能多算或者少算。
举例依然是满20减10, 最终总计实付20元
商品 | 单价 | 数量 | 分摊比例(四舍五入,2位精度) | 实付金额 |
---|---|---|---|---|
商品A | 10 | 1 | 10/30 = 0.33 | 6.6 |
商品B | 10 | 1 | 10/30 = 0.33 | 6.6 |
商品B | 10 | 1 | 10/30 = 0.33 | 6.6 |
此时分摊后各金额相加之和是 19.8 元, 与用户实际支付的20元有误差。
分摊算法在实现时要避免误差,否则会出现数据不一致的情况。
确保分摊后各个部分的总和与原始值不一致的本质,来源于计算分摊比例时,往往需要进行除法运算,
- 四舍五入:可能产生无限小数(如 1/3 = 0.333…)。为了实际应用,这些小数需要被截断或舍入,从而产生误差。
- 精度限制:即使比例计算结果是有限小数,但由于精度限制(如保留两位小数)会导致部分精度被舍弃,也会产生误差
通过更精确的比例计算(如使用 BigDecimal
),结合合适的舍入规则(如四舍五入)和修正策略,可以有效减少这种误差的发生。
3.1 误差修正的方法
最后一次分摊不按照比例计算
最后一个商品不再按照比例进行分摊,而是直接通过计算已分配的金额与原始总额之间的差值,直接将这个差值分配给最后一项商品。
这是因为在前面的分摊过程中,由于四舍五入和精度限制,可能会有一些小的误差,最后一次分摊就是用来修正这些误差的,确保误差被完全消除,且总和精准。
在这种情况下,最后一个商品充当了“误差调节器”,承担了所有因四舍五入或其他计算原因导致的微小误差。
按比例平衡误差
在分摊过程中,每个商品的金额会按比例计算,所有商品的分摊金额可能会因为舍入造成总和小于或大于原始金额。此时,可以将误差按比例重新分配给所有商品,而不是仅仅分配给最后一个商品。
最大值法(集中修正)
在分摊过程中,如果出现误差,可以选择将误差集中到金额最大的商品上,通常选取商品价格最贵、最具代表性的商品来承担误差。
金额最大的商品指的是商品总价最贵的商品,而不是单价最贵的商品。这是因为分摊逻辑通常与商品的总金额相关,而总金额(单价 × 数量)更能反映商品对整体金额的贡献程度。
4. 实现分摊算法
- 使用
BigDecimal
确保高精度计算 - 使用 最后一次分摊不按照比例计算 来修正误差
- 处理最后一项不够减 的情况
1 |
|