BigDecimal 除以不均匀小数

Posted

技术标签:

【中文标题】BigDecimal 除以不均匀小数【英文标题】:BigDecimal Dividing with uneven decimals 【发布时间】:2016-01-07 16:04:59 【问题描述】:

我正在尝试开发一个程序来处理必要的多个 BigDecimal 计算。我处于相当混乱的状态,因为 BigDecimal 没有按照我的意愿进行合作。让我解释一下我需要它做什么:

首先它需要一个货币金额,最多可以有两位小数。然后它需要“分配”,这基本上是该金额将通过多少个帐户进行分配。

程序现在应该在账户之间分配金额。当然,在某些情况下,金额不能平均分配,例如 3.33 美元在 2 个帐户之间分配。在这种情况下,您必须有 1 个分配额外的美分或四舍五入。四舍五入不是一种选择,每一分钱都必须考虑在内。这是我到目前为止所拥有的:

totalAllocations = TransactionWizard.totalAllocations;//Set in another class, how many accounts total will be spread

    BigDecimal totalAllocationsBD = new BigDecimal(totalAllocations).setScale(2);//Converts to big decimal. 

    amountTotal = (BigDecimal) transInfo.get("amount"); // set total amount

    MathContext mc = new MathContext(2);
    remainderAllocation = amountTotal.remainder(totalAllocationsBD, mc);

    dividedAllocationAmount = amountTotal.divide(totalAllocationsBD, MathContext.DECIMAL32);

    dividedAllocationAmount=dividedAllocationAmount.setScale(2);

在课后,我实际上写了这些值。我首先有一个计数器,它设置为 totalAllocations。然后我有一个循环,它将写入一些信息,包括divideAllocationAmount。所以说总金额为 10,我有两次分配,那么 5.00 将被写入两次。

我想要的是对于总数不能在分配之间平均分配的情况,以便有一个额外的分配来保存剩余部分,如下所示:

if(remainderAllocation.compareTo(BigDecimal.ZERO) >0 && allocationCounter==1)
                adjAmt.setValue(remainderAllocation);
            else
                adjAmt.setValue(dividedAllocationAmount);
            

adjAmt 只是设置一个 XML 字段,这是一个 JAXB 项目。

我在这里遇到的主要问题是带有余数的数字。例如,如果用户选择 2 个分配并且金额为 3.33 美元,那么程序将失败并给我一个舍入错误。

Exception in thread "AWT-EventQueue-0" java.lang.ArithmeticException: Rounding necessary
    at java.math.BigDecimal.commonNeedIncrement(Unknown Source)
    at java.math.BigDecimal.needIncrement(Unknown Source)
    at java.math.BigDecimal.divideAndRound(Unknown Source)
    at java.math.BigDecimal.setScale(Unknown Source)
    at java.math.BigDecimal.setScale(Unknown Source)
    at model.Creator.createTransaction(Creator.java:341)
    at view.TransactionWizard$2.actionPerformed(TransactionWizard.java:333)
    at javax.swing.AbstractButton.fireActionPerformed(Unknown Source)
    at javax.swing.AbstractButton$Handler.actionPerformed(Unknown Source)
    at javax.swing.DefaultButtonModel.fireActionPerformed(Unknown Source)
    at javax.swing.DefaultButtonModel.setPressed(Unknown Source)
    at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(Unknown Source)
    at java.awt.Component.processMouseEvent(Unknown Source)
    at javax.swing.JComponent.processMouseEvent(Unknown Source)
    at java.awt.Component.processEvent(Unknown Source)
    at java.awt.Container.processEvent(Unknown Source)
    at java.awt.Component.dispatchEventImpl(Unknown Source)
    at java.awt.Container.dispatchEventImpl(Unknown Source)
    at java.awt.Component.dispatchEvent(Unknown Source)
    at java.awt.LightweightDispatcher.retargetMouseEvent(Unknown Source)
    at java.awt.LightweightDispatcher.processMouseEvent(Unknown Source)
    at java.awt.LightweightDispatcher.dispatchEvent(Unknown Source)
    at java.awt.Container.dispatchEventImpl(Unknown Source)
    at java.awt.Window.dispatchEventImpl(Unknown Source)
    at java.awt.Component.dispatchEvent(Unknown Source)
    at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
    at java.awt.EventQueue.access$500(Unknown Source)
    at java.awt.EventQueue$3.run(Unknown Source)
    at java.awt.EventQueue$3.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
    at java.awt.EventQueue$4.run(Unknown Source)
    at java.awt.EventQueue$4.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
    at java.awt.EventQueue.dispatchEvent(Unknown Source)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
    at java.awt.WaitDispatchSupport$2.run(Unknown Source)
    at java.awt.WaitDispatchSupport$4.run(Unknown Source)
    at java.awt.WaitDispatchSupport$4.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.awt.WaitDispatchSupport.enter(Unknown Source)
    at java.awt.Dialog.show(Unknown Source)
    at java.awt.Component.show(Unknown Source)
    at java.awt.Component.setVisible(Unknown Source)
    at java.awt.Window.setVisible(Unknown Source)
    at java.awt.Dialog.setVisible(Unknown Source)
    at view.MainView$15$1.run(MainView.java:398)
    at java.awt.event.InvocationEvent.dispatch(Unknown Source)
    at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
    at java.awt.EventQueue.access$500(Unknown Source)
    at java.awt.EventQueue$3.run(Unknown Source)
    at java.awt.EventQueue$3.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
    at java.awt.EventQueue.dispatchEvent(Unknown Source)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.run(Unknown Source)
Local Instrument: WEB

如果总金额为 10.51 美元并且有 3 个分配,也会发生同样的情况。实际上,我想要的是两次分配为 5.25 美元,第三次分配为 0.01 美元。 (因为那是余数)。我该怎么办?

【问题讨论】:

我该怎么做清楚地写下您的需求/案例并将其拆分为步骤将有助于包括您在内的所有人很好地理解它并开始编码。没有通读全文,发现有点混乱。 @NayanWadekar 你应该学会从写得不好的文本中提取重要信息 :) 这里的 2 个建议是: a) 阅读有关如何在 Java 中进行会计处理的约定。如果您没有小数便士,BigDecimal 不是的方法。 b) 阅读how to debug small programs,错误就在堆栈跟踪中(它还会将您指向 Javadoc 中的相关位置) @Ordous 具有讽刺意味的是,即使在阅读了“写得不好的文字”并建议我之后,你也没有回答。我可以,但我不想,干杯。 感谢您的建议,但我看不出问题是如何不清楚的?我想知道一个 BigDecimal 可以被另一个 BigDecimal 整除多少次,然后将余数放入它自己的变量中?究竟有什么不清楚的地方? 你想用 RoundingMode.FLOOR 分割。 【参考方案1】:

当然在某些情况下金额不能平分, 例如 3.33 美元在 2 个帐户之间分配。在这种情况下,您必须 要么有 1 个分配额外的美分或四舍五入

[没有优化、最佳实践、错误处理、数据类型转换等普通工作伪代码。]

试试这个

        float amount = 3.33f;

        int allocations = 2;

        double average = amount/allocations;

        System.out.println("ACTUAL AVERAGE "+average);

        double rounded = Math.round(average * 100.0) / 100.0;

        System.out.println("ROUNDED VALUE: "+rounded);

        double adjustment = average - rounded;

        adjustment*=allocations; //-- FOR EACH ALLOCATION

        for(int i=1; i<allocations; i++)
            System.out.println("Allocation :" +i + " = "+rounded);
        

        //-- ADDING ADJUSTED ROUNDING AMOUNT TO LAST ONE
        double adjustedAmount = Math.round((rounded+adjustment) * 100.0) / 100.0;

        System.out.println("Allocation :" +allocations +" = " + adjustedAmount);

数量为 3.33 的输出,有 2 个分配。

ACTUAL AVERAGE 1.6649999618530273
ROUNDED VALUE: 1.66
Allocation :1 = 1.66
Allocation :2 = 1.67 //-- EXTRA CENT

如果总金额为 10.51 美元并且有 3 个 分配。实际上,我想要的是两次分配 5.25 美元,第三次分配为 0.01 美元。 (因为那是 余)。我该怎么办?

现在这与你上面所说的不同,你可以有 3.5、3.5 和 3.51。

但是如果你想要单独的 0.01,那么改变上面的代码 allocations-1 并将余数设置为最后的分配。所以第 2 次分配为 5.25,第 3 次分配为 0.01,希望对您有所帮助。

【讨论】:

好吧,我还没有机会测试它,但它看起来不错。发布该答案后,我发现某些数字仍然让我遇到异常:线程“main”中的异常 java.lang.ArithmeticException:非终止十进制扩展;没有精确可表示的十进制结果。在 java.math.BigDecimal.divide(Unknown Source) 在 mathtest.MathTest.main(MathTest.java:34) 所以我希望我不会有你的问题。谢谢。 为避免“非终止十进制扩展”错误,您必须使用指定精度或 MathContext 的 divide() 函数。【参考方案2】:

通过自己的反复试验,我终于找到了解决这个问题的方法。事实证明,对我来说,关键是先取出余数,然后进行除法。

因为我知道我想根据情况自动将大十进制分成三部分,所以我这样做了..

package mathtest;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;

public class MathTest 



    public static BigDecimal totalAmount;
    public static BigDecimal totalAllocations; 
    public static BigDecimal divisibleAmount;
    public static BigDecimal remainderAmount;
    public static BigDecimal subtractDecimal;


    public static void main(String args[])

        MathContext mc = new MathContext(2);

        totalAmount = new BigDecimal(10.00).setScale(2,RoundingMode.HALF_UP);//Sets the total monetary amount. Standard monetary rounding. 
        totalAllocations = new BigDecimal(2);//The number of accounts the total amount will be split between. 
        subtractDecimal = new BigDecimal(1.00);//Used to remove one from the total allocations (to account for the remainder). 

        remainderAmount = totalAmount.remainder(totalAllocations, mc);//Gets the remainder assuming you tried to divide total/allocations. 

        totalAmount=totalAmount.subtract(remainderAmount);//Subtracts the remainder from the total. 


        if(remainderAmount.compareTo(BigDecimal.ZERO) >0)//If there is a remainder. 

        //The divisible amount is the total amount divided by the total allocations minus 1 (to account for remainder). 
        divisibleAmount=totalAmount.divide(totalAllocations.subtract(subtractDecimal));

        else//If there is no remainder 

            divisibleAmount=totalAmount.divide(totalAllocations);//The divisible amount is the total amount divided by the total allocations. 
        
        if(remainderAmount.compareTo(BigDecimal.ZERO)>0)
        System.out.println(remainderAmount);    
        


        //The below would be printed once for each allocation. 

        System.out.println(divisibleAmount);
    


【讨论】:

以上是关于BigDecimal 除以不均匀小数的主要内容,如果未能解决你的问题,请参考以下文章

duboo类型保留2位小数

Java中BigDecimal详解及应用

Java中BigDecimal详解及应用

BigDecimal类的用法

BigDecimal用法总结

bigDecimal保留后两位小数