Link to home
Start Free TrialLog in
Avatar of capturetheflag
capturetheflag

asked on

Currency Total and Rounding Errors with Java Method

Hello,

I am having rounding (total) issues with the following method, that is posted below.  Specificly with the tTotalTC variable.  The total cost for Task Orders is off.  The Total Quantity for Task Orders is fine.  I have checked what value is coming back from the database procedure called and it does match up with the value when I debug through this method.
The totals for tTotalTC in debug mode is 657954.4375 and the value in the database is
657954.46  

Thank you for help and guidance with this problem.


CODE BEGINS
  public FundingSummaryData  buildFundingSummaryData (String projNam, Collection<FsInvoice> invCollection, Collection<FsTaskOrd> toCollection) {

        double iTotalQ = 0;
        double iTotalUC = 0;
        double iTotalGC = 0;
        double iTotalRW = 0;
        double iTotalRR = 0;
        double iTotalAD = 0;

        //Task Ord Totals
        double tTotalQ = 0;
        double tTotalTC = 0;                
       
        double f = 0;
        String strValue = "";
        String ctrNam = null;
       
        FundingSummaryData fsd = new FundingSummaryData();  
        DecimalFormat twoPlaces = new DecimalFormat("0.00");
       
        // Invoices
        Iterator<FsInvoice> invIterator = invCollection.iterator();
        while (invIterator.hasNext ()) {
            FsInvoice inv = invIterator.next();
            if (inv != null) {
                double amtDue = 0;
                if (ctrNam == null) {
                    ctrNam = inv.getCtrNam ();
                }
                if (!inv.getSvcTypeNam ().equalsIgnoreCase ("Retainage Release")) {
                    if ((inv.getInvQty () != null) && (inv.getInvQty ().matches("([1-9]\\d*)"))) {
                        f = Double.valueOf (inv.getInvQty ());
                        iTotalQ = iTotalQ + f;
                    }
                    if (inv.getInvGrossCost ().matches("([1-9]\\d*)?\\d(\\.\\d{2})")) {
                        f = Double.valueOf (inv.getInvGrossCost ());
                        if (f > 0) {
                            iTotalGC = iTotalGC + f;
                            amtDue = amtDue + f;   //add to the amount due
                        }
                    }
                    f = Double.valueOf (inv.getRetainageWithheld ());
                    if (f > 0) {
                        iTotalRW = iTotalRW + f;
                        amtDue = amtDue - f;   //subtract the retainage amount from the amount due
                    }
                    inv.setAmountDue (Double.toString (amtDue));
                    if (amtDue > 0) {
                        iTotalAD = iTotalAD + amtDue;
                    }
                }
                else {
                    inv.setRetainageWithheld ("0");
                    if (inv.getRetainageReleased () != null) {
                        f = Double.valueOf (inv.getRetainageReleased ());
                        if (f > 0) {
                            iTotalRR = iTotalRR + f;
                            amtDue = amtDue + f;   //add the retainage released to the amount due
                        }
                    }
                    inv.setAmountDue (Double.toString (amtDue));
                    if (amtDue > 0) {
                        iTotalAD = iTotalAD + amtDue;
                    }
                }
            }
        } // End of while
       
        // Task Orders
        f=0;
        Iterator<FsTaskOrd> toIterator = toCollection.iterator();
        while (toIterator.hasNext ()) {
            FsTaskOrd to = toIterator.next();
            if (to != null) {
                if (ctrNam == null) {
                    ctrNam = to.getCtrNam ();
                }
                f=0;
                if (to.getTaskOrdQty()!=null) {
                    f = Float.valueOf (to.getTaskOrdQty ());
                }
                tTotalQ = tTotalQ + f;
               
                f=0;
                if ((to.getTaskOrdCombinedCost ()) != null) {
                    f = Float.valueOf (to.getTaskOrdCombinedCost ());
                }
                tTotalTC = tTotalTC + f;
            }
        } // End of while
       
        // populate fsd
        fsd.addInvoices ((List)invCollection);
        fsd.addTaskOrds ((List)toCollection);
        fsd.setITotalQ  (iTotalQ);
        fsd.setITotalUC (iTotalUC);
        fsd.setITotalGC (iTotalGC);
        fsd.setITotalRW (iTotalRW);
        fsd.setITotalRR (iTotalRR);
        fsd.setITotalAD (iTotalAD);

        //Task Ord Totals
        fsd.settTotalQ (tTotalQ);
        fsd.settTotalC (tTotalTC);
       
        fsd.setForContractor (ctrNam);
        fsd.setProjNam (projNam);
       
        return fsd;
    }
CODE ENDS
Avatar of for_yan
for_yan
Flag of United States of America image

In general when doing financial calculation it is recommended to use BigDecimal instead of double,
see for example this page:

http://epramono.blogspot.com/2005/01/double-vs-bigdecimal.html
If you want us to go through your code - post self-sfficient class, which we can try to compile and execute;
there is no FsTaskOrd class, there is only a method not a class in your excerpt, etc.

The genetral rule is - don't use float or double when making financial calculations: because of rounding  issues they are not suitable for that - use BigDecimal as I mentioned above.
Avatar of capturetheflag
capturetheflag

ASKER

Hello for_van,

Yes, thanks for reminding me. Here is the FsTaskOrd class.  The rounding error shows up on only to two task orders out over a hundred.  Thanks for going through my code.

CODE BEGINS
/*
 * FsTaskOrd.java
 *
 */

package net.fema.toa.entities;

import java.io.Serializable;
import java.text.DecimalFormat;

/**
 * Entity class FsTaskOrd
 *
 */
public class FsTaskOrd implements Serializable {

    private String taskOrdNum          = "";
    private double taskOrdQty          = 0;
    private double taskOrdCombinedCost = 0;
    private String svcTypeNam          = "";
    private String ctrNam              = "";
    private String recdDt              = "";
   
    DecimalFormat twoPlaces = new DecimalFormat("0.00");
    DecimalFormat noPlaces = new DecimalFormat("0");
   
    public String getTaskOrdNum () {
        return this.taskOrdNum;
    }
    public void   setTaskOrdNum (String strValue) {
        this.taskOrdNum = strValue;
    }
    public String getTaskOrdQty () {
        return noPlaces.format(this.taskOrdQty);
    }
    public void   setTaskOrdQty (String strValue) {
        if (strValue != null) {
            this.taskOrdQty = new Double(strValue);
        }
    }
    public String getTaskOrdCombinedCost () {
        return twoPlaces.format(this.taskOrdCombinedCost);
    }
    public void   setTaskOrdCombinedCost (String strValue) {
        this.taskOrdCombinedCost = new Double(strValue);
    }
    public String getSvcTypeNam () {
        if ((this.svcTypeNam == null) || ("".equals (this.svcTypeNam))) {
            return "N/A";
        }
        else {
            return this.svcTypeNam;
        }
    }
    public void   setSvcTypeNam (String strValue) {
        this.svcTypeNam = strValue;
    }
    public void   setCtrNam (String strValue) {
        this.ctrNam = strValue;
    }
    public String getCtrNam () {
        if ((this.ctrNam == null) || ("".equals (this.ctrNam))) {
            return "";
        }
        else {
            return this.ctrNam;
        }
    }
    public String getRecdDt () {
        if ((this.recdDt == null) || ("".equals (this.recdDt))) {
            return "";
        }
        else {
            return this.recdDt;
        }
    }
    public void   setRecdDt (String strValue) {
        this.recdDt = strValue;
    }

     
    /**
     * Returns a hash code value for the object.  This implementation computes
     * a hash code value based on the id fields in this object.
     * @return a hash code value for this object.
     */
    @Override
    public int hashCode () {
        int hash = 0;
        hash += (this.taskOrdNum != null ? this.taskOrdNum.hashCode() : 0);
        return hash;
    }

    /**
     * Determines whether another object is equal to this FsTaskOrd.  The result is
     * <code>true</code> if and only if the argument is not null and is a FsTaskOrd object that
     * has the same id field values as this object.
     * @param object the reference object with which to compare
     * @return <code>true</code> if this object is the same as the argument;
     * <code>false</code> otherwise.
     */
    @Override
    public boolean equals (Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof FsTaskOrd)) {
            return false;
        }
        FsTaskOrd other = (FsTaskOrd)object;
        if (this.taskOrdNum != other.taskOrdNum && (this.taskOrdNum == null || !this.taskOrdNum.equals(other.taskOrdNum))) return false;
        return true;
    }

    /**
     * Returns a string representation of the object.  This implementation constructs
     * that representation based on the id fields.
     * @return a string representation of the object.
     */
    @Override
    public String toString () {
        return "fundingsummary.entity.FsTaskOrd[taskOrdNum=" + taskOrdNum + "]";
    }
   
}

CODE ENDS
No, I still don;t think I would be able to do anything - I don;t see any input bnumbers other than zeroes - so I can hardly do anything reasonable - but think about BigDecimal's
ASKER CERTIFIED SOLUTION
Avatar of CEHJ
CEHJ
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Hello for_van,

I have attached all the classes involded the task order.

Thanks.
FundingSummaryData.txt
FsInvoice.txt
GetFundingSummaryAction.txt
Sorry, I can't reproduce any of that - with struts  and without data.
The general idea that you should use everything as numeric - better BigDecimal, worse - double and don't do any intermediate formatting before final output - that is of course true, as every formattting introdeuces arror and they can pile up together
I have attached all the classes involded the task order.

No need. I've told you the reason for your problem
Hello CEHJ,

Thank you for your help.  What is the first step in rounding/formating the data at presentation time?
Before you fially print out you can format your output using DecimalFotrmat class like you do of usinmg printf() formatting


It is not that you are formatting incorrectly, it is that you should not do itfor itermediate results.

The most important point is not to do any formatting with the intermediate stages of your calcukations - because with each fomatting operation you are losing some accuracy and they can accumulate together. All intertmediate stages should be in double, or if you are dealing with money - better to have them as BigDecimal, especailly if this is a real application for financial customers, etc.
Thanks for your help.  I am a newbie to java and would like you to show me how to change this method for the better thanks.
Hello,

I am using the DeciamlFormat class in this method,  
'DecimalFormat twoPlaces = new DecimalFormat("0.00");'
However netbeans does state that the variable 'twoPlaces ' is not being used.
Where or how do use this in the code?

Thanks.

    public FundingSummaryData  buildFundingSummaryData (String projNam, Collection<FsInvoice> invCollection, Collection<FsTaskOrd> toCollection) {

        double iTotalQ = 0;
        double iTotalUC = 0;
        double iTotalGC = 0;
        double iTotalRW = 0;
        double iTotalRR = 0;
        double iTotalAD = 0;

        //Task Ord Totals
        double tTotalQ = 0;
        double tTotalTC = 0;
       
        double f = 0;
        String strValue = "";
        String ctrNam = null;
       
       FundingSummaryData fsd = new FundingSummaryData();  
        DecimalFormat twoPlaces = new DecimalFormat("0.00");
       
        // Invoices
        Iterator<FsInvoice> invIterator = invCollection.iterator();
        while (invIterator.hasNext ()) {
            FsInvoice inv = invIterator.next();
            if (inv != null) {
                double amtDue = 0;
                if (ctrNam == null) {
                    ctrNam = inv.getCtrNam ();
                }
                if (!inv.getSvcTypeNam ().equalsIgnoreCase ("Retainage Release")) {
                    if ((inv.getInvQty () != null) && (inv.getInvQty ().matches("([1-9]\\d*)"))) {
                        f = Double.valueOf (inv.getInvQty ());
                        iTotalQ = iTotalQ + f;
                    }
                    if (inv.getInvGrossCost ().matches("([1-9]\\d*)?\\d(\\.\\d{2})")) {
                        f = Double.valueOf (inv.getInvGrossCost ());
                        if (f > 0) {
                            iTotalGC = iTotalGC + f;
                            amtDue = amtDue + f;   //add to the amount due
                        }
                    }
                    f = Double.valueOf (inv.getRetainageWithheld ());
                    if (f > 0) {
                        iTotalRW = iTotalRW + f;
                        amtDue = amtDue - f;   //subtract the retainage amount from the amount due
                    }
                    inv.setAmountDue (Double.toString (amtDue));
                    if (amtDue > 0) {
                        iTotalAD = iTotalAD + amtDue;
                    }
                }
                else {
                    inv.setRetainageWithheld ("0");
                    if (inv.getRetainageReleased () != null) {
                        f = Double.valueOf (inv.getRetainageReleased ());
                        if (f > 0) {
                            iTotalRR = iTotalRR + f;
                            amtDue = amtDue + f;   //add the retainage released to the amount due
                        }
                    }
                    inv.setAmountDue (Double.toString (amtDue));
                    if (amtDue > 0) {
                        iTotalAD = iTotalAD + amtDue;
                    }
                }
            }
        } // End of while
       
        // Task Orders
        f=0;
        Iterator<FsTaskOrd> toIterator = toCollection.iterator();
        while (toIterator.hasNext ()) {
            FsTaskOrd to = toIterator.next();
            if (to != null) {
                if (ctrNam == null) {
                    ctrNam = to.getCtrNam ();
                }
                f=0;
                if (to.getTaskOrdQty()!=null) {
                    f = Float.valueOf (to.getTaskOrdQty ());
                }
                tTotalQ = tTotalQ + f;
               
                f=0;
                if ((to.getTaskOrdCombinedCost ()) != null) {
                    f = Float.valueOf (to.getTaskOrdCombinedCost ());
                }
                tTotalTC = tTotalTC + f;
            }
        } // End of while
       
        // populate fsd
        fsd.addInvoices ((List)invCollection);
        fsd.addTaskOrds ((List)toCollection);
        fsd.setITotalQ  (iTotalQ);
        fsd.setITotalUC (iTotalUC);
        fsd.setITotalGC (iTotalGC);
        fsd.setITotalRW (iTotalRW);
        fsd.setITotalRR (iTotalRR);
        fsd.setITotalAD (iTotalAD);

        //Task Ord Totals
        fsd.settTotalQ (tTotalQ);
        fsd.settTotalC (tTotalTC);
       
        fsd.setForContractor (ctrNam);
        fsd.setProjNam (projNam);
       
        return fsd;
    }
:)

Just do the formatting at the end, using double right up to then. You can format the data to 2 d.p.  for double 'd' when you're ready to show it with

String s = String.format("%.2f", d);

Open in new window

Well,  netbeans is right, you create isntance of DecimalFormat in order to use it to format your number like this:

        DecimalFormat twoPlaces = new DecimalFormat("0.00");

                      double  tTotalc =   657954.4375;

        System.out.println("Total is equal to " + twoPlaces.format(tTotalc));

Open in new window


Output:

Total is equal to 657954.44

Open in new window