订单折扣金额分摊算法|代金券分摊|收银系统|积分分摊|分摊|精度问题|按比例分配|钱分摊|钱分配

一个金额分摊的算法,将折扣分摊按比例(细单实收在总体的占比)到各个细单中。
此算法需要达到以下要求:

  1. 折扣金额接近细单总额,甚至折扣金额等于细单金额,某些时候甚至超过细单总额,要保证实收不为负数。
  2. 复杂度O(n)


写这个算法的初衷,就是因为现在网上的分摊算法,都没有考虑到最后一项不够减、只循环一次、折扣金额接近总额…

用例:
细单1:8.91
细单2:21.09
细单3:0.01
三个细单总和是 30.01
折扣金额:30
按比例分摊后,应该只有一项是 0.01

废话不多,直接上代码:
细单对象:

	/*** 细单类*/@Datapublic static class Detail {/*** 用来标识记录*/private Long id;/*** 总额*/private BigDecimal money;}
    /*** 分摊** @param detailList    细单* @param discountMoney 折扣* @return 新的细单集合*/public static List<Detail> allocateDiscountMoney(List<Detail> detailList, BigDecimal discountMoney) {// 分摊总金额BigDecimal allocatedAmountTotal = discountMoney;// 剩余分摊金额BigDecimal leftAllocatedAmount = allocatedAmountTotal;// 订单总实收BigDecimal orderTotalAmount = detailList.stream().map(Detail::getMoney).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);// 结果集List<Detail> resultList = new ArrayList<>();for (int i = 0; i < detailList.size(); i++) {// 结果Detail resultDetail = new Detail();BeanUtils.copyProperties(detailList.get(i), resultDetail);BigDecimal money = resultDetail.getMoney();// 占比比例=自身实收/实收总额BigDecimal proportion = money.divide(orderTotalAmount, 10, RoundingMode.UP);// 分摊金额 = 总分摊金额*占比比例BigDecimal allocatedMoney = allocatedAmountTotal.multiply(proportion);// 折扣分摊金额向上取整,将精度差异提前吸收,此举使得最后一项足够吸收剩余折扣金额allocatedMoney = allocatedMoney.setScale(2, RoundingMode.UP);// 是否该订单最后一条商品 或者 已经不够分摊if (i == detailList.size() - 1 || leftAllocatedAmount.subtract(allocatedMoney).compareTo(BigDecimal.ZERO) <= 0) {allocatedMoney = leftAllocatedAmount;}// 防止订单金额负数(若最后一项执行此逻辑,则导致总金额有误)if (money.subtract(allocatedMoney).compareTo(BigDecimal.ZERO) < 0) {allocatedMoney = money;}// 单个商品分摊后的金额BigDecimal goodsActualMoneyAfterAllocated = money.subtract(allocatedMoney);// 累减已分摊金额leftAllocatedAmount = leftAllocatedAmount.subtract(allocatedMoney);resultDetail.setMoney(goodsActualMoneyAfterAllocated);resultList.add(resultDetail);}return resultList;}

测试类:

public static void main1() {List<Detail> detailList = new ArrayList<>();//Detail detail = new Detail();detail.setId(1L);detail.setMoney(new BigDecimal("8.91"));detailList.add(detail);//Detail detail2 = new Detail();detail2.setId(2L);detail2.setMoney(new BigDecimal("21.07"));detailList.add(detail2);//Detail detail3 = new Detail();detail3.setId(3L);detail3.setMoney(new BigDecimal("0.01"));detailList.add(detail3);System.out.println("分摊前:" + JSON.toJSONString(detailList));List<Detail> allocated = allocateDiscountMoney(detailList, new BigDecimal("30"));System.out.println("分摊后:" + JSON.toJSONString(allocated));}

问题:为什么每一项算分摊金额都是向上取整?
答:除最后一项外的每一项的折扣分摊算多了,最后一项就分摊得少,保证最后一项一定够分摊,前面的项在迭代时可以做金额如果不够分摊的兜底处理。而如果这么做,前面的不先兜底,后面的如果不够分摊是需要再往前找项来帮忙分摊的,复杂度就比较高。

~~
折扣金额的分摊,是反向的,其实正向的分摊也一并适用,并且逻辑是等价的。
例如:
细单1:8.91
细单2:21.09
细单3:0.01
三个细单总和是 30.01
折扣金额:30
我们也可以看做最终金额为 0.01,用0.01来分摊。

/*** 分摊** @param detailList    细单* @param tgtTotalMoney 待分摊的目标总金额* @return 新的细单集合*/public static List<Detail> allocateTgtTotalMoney(List<Detail> detailList, BigDecimal tgtTotalMoney) {// 分摊总金额BigDecimal allocatedAmountTotal = tgtTotalMoney;// 剩余分摊金额BigDecimal leftAllocatedAmount = allocatedAmountTotal;// 订单总实收BigDecimal orderTotalAmount = detailList.stream().map(Detail::getMoney).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);// 结果集List<Detail> resultList = new ArrayList<>();for (int i = 0; i < detailList.size(); i++) {// 结果Detail resultDetail = new Detail();BeanUtils.copyProperties(detailList.get(i), resultDetail);BigDecimal money = resultDetail.getMoney();// 占比比例=自身实收/实收总额BigDecimal proportion = money.divide(orderTotalAmount, 10, RoundingMode.UP);// 分摊金额 = 总分摊金额*占比比例BigDecimal allocatedMoney = allocatedAmountTotal.multiply(proportion);// 折扣分摊金额向上取整,将精度差异提前吸收,此举使得最后一项足够吸收剩余折扣金额allocatedMoney = allocatedMoney.setScale(2, RoundingMode.UP);// 是否该订单最后一条商品 或者 已经不够分摊if (i == detailList.size() - 1 || leftAllocatedAmount.subtract(allocatedMoney).compareTo(BigDecimal.ZERO) <= 0) {allocatedMoney = leftAllocatedAmount;}// 累减已分摊金额leftAllocatedAmount = leftAllocatedAmount.subtract(allocatedMoney);resultDetail.setMoney(allocatedMoney);resultList.add(resultDetail);}return resultList;}

测试类:

public static void main2() {List<Detail> detailList = new ArrayList<>();//Detail detail = new Detail();detail.setId(1L);detail.setMoney(new BigDecimal("8.91"));detailList.add(detail);//Detail detail2 = new Detail();detail2.setId(2L);detail2.setMoney(new BigDecimal("21.07"));detailList.add(detail2);//Detail detail3 = new Detail();detail3.setId(3L);detail3.setMoney(new BigDecimal("0.01"));detailList.add(detail3);System.out.println("分摊前:" + JSON.toJSONString(detailList));List<Detail> allocated = allocateTgtTotalMoney(detailList, new BigDecimal("0.1"));System.out.println("分摊后:" + JSON.toJSONString(allocated));}

对你有帮助的话,点赞、收藏、评论、关注,谢谢各位大佬了~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/39078.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

游泳哪个牌子好?6大游泳耳机选购技巧总结分享

游泳耳机作为水上运动爱好者和游泳专业人士的必备装备&#xff0c;不仅要能够抵御水的侵入&#xff0c;还要提供清晰的音质和舒适的佩戴体验。在市面上&#xff0c;不同品牌的游泳耳机琳琅满目&#xff0c;选择起来可能会令人头疼。本文旨在为您提供一份详尽的游泳耳机选购指南…

每日一练 - Routing Policy节点逻辑

01 真题题目 一个 routing-policy 下可以有多个节点,不同节点号用 node 标识,每个节点下可以有多个if-match 和 apply 子句,下面哪些描述是错误的? A. 不同节点之间是“或"的关系 B. 当路由与该节点的任意一个 if-match 条件匹配失败后&#xff0c;系统自动转入下一节点…

Gemma轻量级开放模型在个人PC上释放强大性能,让每个桌面秒变AI工作站

Google DeepMind团队最近推出了Gemma&#xff0c;这是一个基于其先前Gemini模型研究和技术的开放模型家族。这些模型专为语言理解、推理和安全性而设计&#xff0c;具有轻量级和高性能的特点。 Gemma 7B模型在不同能力领域的语言理解和生成性能&#xff0c;与同样规模的开放模型…

名企专访|对抗价格内卷,格行随身WiFi如何持续三年爆火引领潮流

近期要是问网红达人最喜欢带货的单品是什么&#xff1f;那一定有格行随身WiFi的一席之地。能聚集了如此多的明星达人&#xff0c;仅仅是一句带货收益高显然无法说服大家。显然这里面还有着不为人知的秘密&#xff0c;先锋财经特意专访了格行随身WiFi的创始人刘永先先生&#xf…

8.x86游戏实战-OD详解

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 上一个内容&#xff1a;7.x86游戏实战-C实现跨进程读写-跨进程写内存 工具下载&#xff1a;下载 OllyI…

嵌入式Linux之Uboot简介和移植

uboot简介 uboot 的全称是 Universal Boot Loader&#xff0c;uboot 是一个遵循 GPL 协议的开源软件&#xff0c;uboot是一个裸机代码&#xff0c;可以看作是一个裸机综合例程。现在的 uboot 已经支持液晶屏、网络、USB 等高级功能。 也就是说&#xff0c;可以在没有系统的情况…

[我靠升级逆袭成为大师]韩漫日漫无删减完整版,免费在线观看漫画

[我靠升级逆袭成为大师]韩漫日漫无删减完整版&#xff0c;免费在线观看漫画 不能多说&#xff0c;怕审-核不过&#xff0c;自己看图吧。 目前统计【统计日期&#xff1a;2024-07-03】&#xff1a; 完结的有&#xff1a;420部。 连载的有&#xff1a;308部&#xff0c;持续更…

生单链路流程复杂,涉及到上下游商品、库存、营销、风控、拆单、校验、落库等等十多个节点操作,需要保证数据的完整性和正确性

处理复杂的生单链路流程&#xff0c;确保数据的完整性和正确性&#xff0c;需要一个综合的策略&#xff0c;包括但不限于以下几个方面&#xff1a; 1. **流程设计**&#xff1a; - 明确每个节点的职责和输入输出&#xff0c;确保流程的逻辑清晰。 2. **数据校验**&#xf…

python库(1):Nuitka库

1 Nuitka介绍 Nuitka是一个 Python 解释器的替代品&#xff0c;支持CPython提供的代码&#xff0c;可编译 Python 代码到 C 程序&#xff0c;并使用 libpython 来执行这些代码&#xff0c;就像 CPython 一样。 这让你可以在没有安装 Python 的环境中运行 Python 程序&#xf…

AC7801时钟配置流程

一 默认配置 在启动文件中&#xff0c;已经对时钟进行了初始化&#xff0c;默认按外部8M晶振&#xff0c;配置系统时钟为48MHZ&#xff0c;APB为系统时钟的2分频&#xff0c;为24MHZ。在system_ac780x.c文件中&#xff0c;可以找到下面这个系统初始化函数&#xff0c;里面有Se…

前端修改audio背景色

1.查看浏览器设置Show user agent shadow DOM是否打开 2.打开可以查看audio Dom /** 去掉默认的背景颜色 */ audio::-webkit-media-controls-enclosure{background-color:unset; } 3.效果图

Java官网网址及其重要资源

Java是一种广泛应用于开发各种应用程序的编程语言&#xff0c;它具有跨平台、面向对象和高性能等优势。若你想学习Java或深入了解它的最新动态&#xff0c;Java官网是你的首要目的地。在本文中&#xff0c;我们将向你介绍Java官网的网址以及一些重要资源。 Java官网网址&#x…

TCP/IP 网络协议族分层

TCP/IP协议族 TCP/IP不单是TCP和IP两个协议&#xff0c;TCP/IP实际上是一组协议&#xff0c;它包括上百个各种功能的协议&#xff0c;如&#xff1a;远程登录、文件传输和电子邮件等&#xff0c;当然&#xff0c;也包括TCP、IP协议 它将软件通信过程抽象化为四个抽象层&#…

基于SpringBoot校园外卖配送系统设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;…

c++:关键字异常处理机制

模板编程的几个关键字 模(mu)板编程初体验 (1)template和typename (2)模板实际上是一种抽象&#xff0c;C的高级编程特性就是不断向抽象化发展 export (1)用来在cpp文件中定义一个模板类或模板函数&#xff0c;而它的声明在对应的h文件中 (2)export专用于模板&#xff0c;类似…

揭秘电子世界的双雄:模拟电路与数字电路的精彩对决!

数字电路与模拟电路&#xff0c;这两者在电子工程领域可谓是两大基石&#xff0c;各有千秋&#xff0c;各自发挥着不可或缺的作用。下面&#xff0c;我们就来详细探讨一下它们之间的主要区别。 1. 信号类型与处理 模拟电路&#xff1a;处理的是连续变化的信号&#xff0c;就像…

使用阿里云语音服务实现设备异常实时通知

随着物联网的普及,设备异常通知方式也变得多种多样。从传统的后台异常列表,到短信通知,再到微信消息通知等。然而,当设备探测到火警等紧急异常时,需要实时通知到相关人员。本文将介绍如何借助阿里云的语音服务来实现这一功能。 1. 准备工作 1.1 资质申请 首先,登录阿里…

Git中fetch与pull 的区别

一、fetch与pull的基本概念 在Git中&#xff0c;fetch和pull都是用于从远程仓库获取数据的命令。但是&#xff0c;它们在处理方式和结果上有所不同。 1、fetch fetch命令用于从远程仓库下载最新的数据到本地仓库&#xff0c;但它不会自动合并或修改当前的工作。fetch会将远程…

2024年大厂离职当博主成为最拥挤自媒体赛道的现象分析

大厂离职博主在2024年成为最拥挤自媒体赛道的现象分析 1. 行业背景与就业环境变化 降本增效引发的被动离职&#xff1a;近年来&#xff0c;随着各行业的降本增效措施不断推进&#xff0c;即便是知名大厂也在缩减员工规模。腾讯、阿里巴巴等企业的财报显示&#xff0c;从2021年…

一键恢复短信,4个方法,轻松找回iPhone数据!

在日常生活和工作中&#xff0c;短信往往承载着重要的信息和回忆。然而&#xff0c;意外删除、系统故障或手机更换等情况都可能导致短信丢失&#xff0c;这让很多iPhone用户感到困扰。 好消息是&#xff0c;您并不需要担心这些数据无法找回。如今&#xff0c;一键恢复短信的方…