0

I am trying to work on a program for work with multiple BigDecimal calculations necessary. I am at quite a confusing point because the BigDecimal is not cooperating how I would like. Let me explain what I need it to do:

First it takes a monetary amount which can have up to two decimal places. Then it takes the "allocations" which is basically how many accounts that amount will be spread up through.

The program should now divide the amount between the accounts. Of course in some situations the amount can not be evenly divided, for example $3.33 divided between 2 accounts. In that case you must either have 1 allocation with a extra cent or round the number. Rounding is not an option, every penny must be accounted for. Here is what I have so far:

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

Later in the class I actually write the values. I have a counter first that is set as the totalAllocations. I then have a loop that will write a bit of information including the dividedAllocationAmount. So say the amountTotal had been 10 and I had two allocations then 5.00 would be written twice.

What I want is for situations where the total can't be evenly divided among the allocations for there to be one extra allocation to hold the remainder as shown below:

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

The adjAmt is just setting an XML field, this is a JAXB project.

The main issue I have here is with numbers with a remainder. For example if a user selects 2 allocations and the amount is $3.33 then the program will fail and give me a rounding error.

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

The same happens if the total amount is $10.51 and there are 3 allocations. In reality what I want is for two allocations to be for $5.25 and the 3rd allocation to be for $0.01. (Since that is the remainder). What do I do?

jesric1029
  • 698
  • 3
  • 10
  • 33
  • 1
    *What do I do* Do write down your requirement clearly/cases & split it into steps will help to understand it well to all including you & start coding. Didn't gone through whole text, found little confusing. – Nayan Wadekar Jan 07 '16 at 16:18
  • @NayanWadekar You should learn to extract the important info from badly written text :) The 2 suggestions here are: a) Read up on the conventions of how to do accounting in Java. BigDecimal is *not* the way to go if you don't have fractional pennies. b) Read up on [how to debug small programs](http://ericlippert.com/2014/03/05/how-to-debug-small-programs/), the error is right there in the stack trace (it will also point you to the relevant place in the Javadoc) – Ordous Jan 07 '16 at 16:22
  • @Ordous It's irony you didn't answered even after going through "badly written text" & suggesting me. I can, but I don't want to, cheers. – Nayan Wadekar Jan 07 '16 at 16:27
  • Thanks for the suggestions but I don't see how the problem is unclear? I want to find out how many times a BigDecimal is divisible by another BigDecimal then put the remainder in it's own variable? What is unclear about that exactly? – jesric1029 Jan 07 '16 at 16:30
  • you want to divide with RoundingMode.FLOOR. – jtahlborn Jan 07 '16 at 17:39

2 Answers2

2

Of course in some situations the amount can not be evenly divided, for example $3.33 divided between 2 accounts. In that case you must either have 1 allocation with a extra cent or round the number

[No optimization, best practices, error handling, data type conversion etc. plain working pseudo code.]

Try this

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

Output for amount 3.33 with 2 allocations.

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

The same happens if the total amount is $10.51 and there are 3 allocations. In reality what I want is for two allocations to be for $5.25 and the 3rd allocation to be for $0.01. (Since that is the remainder). What do I do?

Now this is different from above what you said above, you can have 3.5, 3.5 & 3.51.

But if you want 0.01 separately, then alter above code allocations-1 & set the remainder to the last allocation. So it would be 5.25 for 2 & remainder 0.01 for the 3rd allocation, hope this helps.

Nayan Wadekar
  • 11,444
  • 4
  • 50
  • 73
  • Well I haven't had a chance to test it yet but it looks good. After I posted that answer I discovered that some numbers still got me the exception: Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result. at java.math.BigDecimal.divide(Unknown Source) at mathtest.MathTest.main(MathTest.java:34) So I'm hoping I won't have that problem with yours. Thanks. – jesric1029 Jan 07 '16 at 18:43
  • To avoid the "non-terminating decimal expansion" error you will have to use a divide() function that specifies a precision or a MathContext. – Rudy Velthuis Jan 08 '16 at 00:54
0

I finally discovered the solution to this problem through my own trial and error. It turns out that the key for me was to take out the remainder FIRST and THEN do the division.

Since I know I want to split the big decimal automatically into three parts depending on the situation I did it like this..

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

}
jesric1029
  • 698
  • 3
  • 10
  • 33