訂單折扣金額分攤算法|代金券分攤|收銀系統|積分分攤|分攤|精度問題|按比例分配|錢分攤|錢分配

一個金額分攤的算法,將折扣分攤按比例(細單實收在總體的占比)到各個細單中。
此算法需要達到以下要求:

  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.pswp.cn/web/39078.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/39078.shtml
英文地址,請注明出處:http://en.pswp.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;一鍵恢復短信的方…