当前位置: 首页 > news >正文

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

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

  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));}

算法缺点(隐患):在折扣分摊的算法中,在需要保证每一项细单金额大于0的场景下,此算法需要谨慎使用,因为可能会把金额分摊为0元,但其实这也很难避免,因为总价30.01,折扣30,有很多项都会是0,只能说还有改进的空间,可以进行改动(例如向上取整)以保证在概率上出现0的情况少点。而且需要进行从小到大排序。

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

相关文章:

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

一个金额分摊的算法&#xff0c;将折扣分摊按比例&#xff08;细单实收在总体的占比&#xff09;到各个细单中。 此算法需要达到以下要求&#xff1a; 折扣金额接近细单总额&#xff0c;甚至折扣金额等于细单金额&#xff0c;某些时候甚至超过细单总额&#xff0c;要保证实收不…...

Matlab中collectPlaneWave函数的应用

查看文档如下&#xff1a; 可以看出最多5个参数&#xff0c;分别是阵列对象&#xff0c;信号幅度&#xff0c;入射角度&#xff0c;信号频率&#xff0c;光速。 在下面的代码中&#xff0c;我们先创建一个3阵元的阵列&#xff0c;位置为&#xff1a;&#xff08;-1,0,0&#x…...

Linux系统的基础知识和常用命令

1、什么是Linux&#xff1f; 是一种免费使用和自由传播的类UNIX操作系统&#xff0c;其内核由林纳斯本纳第克特托瓦兹于1991年10月5日首次发布&#xff0c;它主要受到Minix和Unix思想的启发&#xff0c;是一个基于POSIX的多用户、多任务、支持多线程和多CPU的操作系统。它能运行…...

三相异步电动机的起动方法

1. 引言 2. 三相笼型异步电动机德起动方法 3. 三相绕线型异步电动机的起动方法 4. 软起动器起动 5. 参考文献 1 引言 三相异步电动机结构简单﹑价格低廉﹑运行可靠﹑维护方便&#xff0c;在工农业生产中得到了广泛应用。为使电动机能够转动起来&#xff0c;并很快达到工作转…...

【LinuxC语言】手撕Http协议之accept_request函数实现(一)

文章目录 前言accept_request函数作用accept_request实现解析方法根据不同方法进行不同操作http服务器响应格式unimplemented函数实现总结前言 在计算机网络中,HTTP协议是一种常见的应用层协议,它定义了客户端和服务器之间如何进行数据交换。在这篇文章中,我们将深入探讨Li…...

Redis Cluster 模式 的具体实施细节是什么样的?

概述 参考&#xff1a;What are Redis Cluster and How to setup Redis Cluster locally ? | by Rajat Pachauri | Medium Redis Cluster 的工作原理是将数据分布在多个节点上&#xff0c;同时确保高可用性和容错能力。以下是 Redis Cluster 运行方式的简要概述&#xff1a; …...

基于大模型的机器人控制

基于大模型的机器人控制是指利用深度学习中的大型神经网络模型来实现对机器人的精确控制。这种方法结合了深度学习的强大表征学习能力和机器人控制的实际需求&#xff0c;旨在提高机器人的自主性、灵活性和智能性。 基本原理 数据收集&#xff1a;首先&#xff0c;需要收集大量…...

在 PostgreSQL 中,如何处理数据的版本控制?

文章目录 一、使用时间戳字段进行版本控制二、使用版本号字段进行版本控制三、使用历史表进行版本控制四、使用 RETURNING 子句获取更新前后的版本五、使用数据库触发器进行版本控制 在 PostgreSQL 中&#xff0c;处理数据的版本控制可以通过多种方式实现&#xff0c;每种方式都…...

Rust 组织管理

Rust 组织管理 Rust 是一种系统编程语言&#xff0c;以其内存安全性、速度和并发性而闻名。它由 Mozilla 开发&#xff0c;并得到了一个庞大而活跃的社区的支持。Rust 的组织管理涉及多个方面&#xff0c;包括项目管理、社区参与、工具和库的维护&#xff0c;以及生态系统的整…...

vb.netcad二开自学笔记1:万里长征第一步Hello CAD!

已入门的朋友请绕行&#xff01; 今天开启自学vb.net 开发autocad&#xff0c;网上相关资料太少了、太老了。花钱买课吧&#xff0c;穷&#xff01;又舍不得&#xff0c;咬牙从小白开始摸索自学吧&#xff0c;虽然注定是踏上了一条艰苦之路&#xff0c;顺便作个自学笔记备忘!积…...

Vue的学习之数据与方法

前段期间&#xff0c;由于入职原因没有学习&#xff0c;现在已经正式入职啦&#xff0c;接下来继续加油学习。 一、数据与方法 文字备注已经在代码中&#xff0c;方便自己学习和理解 <!DOCTYPE html> <html><head><meta charset"utf-8">&l…...

刷题——在二叉树中找到最近公共祖先

在二叉树中找到两个节点的最近公共祖先_牛客题霸_牛客网 int lowestCommonAncestor(TreeNode* root, int o1, int o2) {if(root NULL) return -1;if((root->val o1) || (root->val o2)) return root->val;int left lowestCommonAncestor(root->left, o1, o2);i…...

nginx(三)—从Nginx配置熟悉Nginx功能

一、 Nginx配置文件结构 ... #全局块events { #events块... }http #http块 {... #http全局块server #server块{ ... #server全局块location [PATTERN] #location块{...}location [PATTERN] {...}}server{...}... #http全局块 …...

Python轮子:文件比较器——filecmp

原文链接&#xff1a;http://www.juzicode.com/python-module-filecmp filecmp模块可以用来比较文件或者目录。 安装和导入 filecmp是Python自带的模块&#xff0c;不需要额外安装&#xff0c;直接导入即可&#xff1a; import filecmp as fc #或者 import filecmp cmp()比较…...

uni-app组件 子组件onLoad、onReady事件无效

文章目录 导文解决方法 导文 突然发现在项目中&#xff0c;组件 子组件的onLoad、onReady事件无效 打印也出不来值 怎么处理呢&#xff1f; 解决方法 mounted() {console.log(onLoad, this.dateList);//有效// this.checkinDetails()},onReady() {console.log(onReady, this.da…...

leetcode力扣_排序问题

215.数组中的第K个最大元素 鉴于已经将之前学的排序算法忘得差不多了&#xff0c;只会一个冒泡排序法了&#xff0c;就写了一个冒牌排序法&#xff0c;将给的数组按照降序排列&#xff0c;然后取nums[k-1]就是题目要求的&#xff0c;但是提交之后对于有的示例显示”超出时间限制…...

在 .NET 8 Web API 中实现弹性

在现代 Web 开发中&#xff0c;构建弹性 API 对于确保可靠性和性能至关重要。本文将指导您使用 Microsoft.Extensions.Http.Resilience 库在 .NET 8 Web API 中实现弹性。我们将介绍如何设置重试策略和超时&#xff0c;以使您的 API 更能抵御瞬时故障。 步骤 1.创建一个新的 .…...

linux下高级IO模型

高级IO 1.高级IO模型基本概念1.1 阻塞IO1.2 非阻塞IO1.3 信号驱动IO1.4 IO多路转接1.5 异步IO 2. 模型代码实现2.1 非阻塞IO2.2 多路转接-selectselect函数介绍什么才叫就绪呢&#xff1f;demoselect特点 2.3 多路转接-pollpoll函数介绍poll优缺点demo 2.4 多路转接-epoll&…...

掌握Mojolicious会话管理:构建安全、持久的Web应用

掌握Mojolicious会话管理&#xff1a;构建安全、持久的Web应用 Mojolicious是一个基于Perl的高性能、异步Web开发框架&#xff0c;它提供了一套完整的工具来构建现代Web应用。会话管理是Web开发中的一个关键组成部分&#xff0c;它允许应用识别和保持用户的登录状态。本文将深…...

24西安电子科技大学马克思主义学院—考研录取情况

01、马克思主义学院各个方向 02、24马克思主义学院近三年复试分数线对比 PS&#xff1a;马院24年院线相对于23年院线增加15分&#xff0c;反映了大家对于马克思主义理论学习与研究的热情高涨&#xff0c;也彰显了学院在人才培养、学科建设及学术研究等方面的不断进步与成就。 6…...

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻

在如今就业市场竞争日益激烈的背景下&#xff0c;越来越多的求职者将目光投向了日本及中日双语岗位。但是&#xff0c;一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧&#xff1f;面对生疏的日语交流环境&#xff0c;即便提前恶补了…...

Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档&#xff09;&#xff0c;如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下&#xff0c;风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...

Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)

引言 在人工智能飞速发展的今天&#xff0c;大语言模型&#xff08;Large Language Models, LLMs&#xff09;已成为技术领域的焦点。从智能写作到代码生成&#xff0c;LLM 的应用场景不断扩展&#xff0c;深刻改变了我们的工作和生活方式。然而&#xff0c;理解这些模型的内部…...

Rust 开发环境搭建

环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行&#xff1a; rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu ​ 2、Hello World fn main() { println…...

保姆级【快数学会Android端“动画“】+ 实现补间动画和逐帧动画!!!

目录 补间动画 1.创建资源文件夹 2.设置文件夹类型 3.创建.xml文件 4.样式设计 5.动画设置 6.动画的实现 内容拓展 7.在原基础上继续添加.xml文件 8.xml代码编写 (1)rotate_anim (2)scale_anim (3)translate_anim 9.MainActivity.java代码汇总 10.效果展示 逐帧…...

Python常用模块:time、os、shutil与flask初探

一、Flask初探 & PyCharm终端配置 目的: 快速搭建小型Web服务器以提供数据。 工具: 第三方Web框架 Flask (需 pip install flask 安装)。 安装 Flask: 建议: 使用 PyCharm 内置的 Terminal (模拟命令行) 进行安装,避免频繁切换。 PyCharm Terminal 配置建议: 打开 Py…...

《Offer来了:Java面试核心知识点精讲》大纲

文章目录 一、《Offer来了:Java面试核心知识点精讲》的典型大纲框架Java基础并发编程JVM原理数据库与缓存分布式架构系统设计二、《Offer来了:Java面试核心知识点精讲(原理篇)》技术文章大纲核心主题:Java基础原理与面试高频考点Java虚拟机(JVM)原理Java并发编程原理Jav…...

AWS vs 阿里云:功能、服务与性能对比指南

在云计算领域&#xff0c;Amazon Web Services (AWS) 和阿里云 (Alibaba Cloud) 是全球领先的提供商&#xff0c;各自在功能范围、服务生态系统、性能表现和适用场景上具有独特优势。基于提供的引用[1]-[5]&#xff0c;我将从功能、服务和性能三个方面进行结构化对比分析&#…...

数据挖掘是什么?数据挖掘技术有哪些?

目录 一、数据挖掘是什么 二、常见的数据挖掘技术 1. 关联规则挖掘 2. 分类算法 3. 聚类分析 4. 回归分析 三、数据挖掘的应用领域 1. 商业领域 2. 医疗领域 3. 金融领域 4. 其他领域 四、数据挖掘面临的挑战和未来趋势 1. 面临的挑战 2. 未来趋势 五、总结 数据…...