Solved

Currency Total and Rounding Errors with Java Method

Posted on 2012-03-22
15
354 Views
Last Modified: 2012-08-13
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
0
Comment
Question by:capturetheflag
  • 7
  • 5
  • 3
15 Comments
 
LVL 47

Expert Comment

by:for_yan
Comment Utility
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
0
 
LVL 47

Expert Comment

by:for_yan
Comment Utility
0
 
LVL 47

Expert Comment

by:for_yan
Comment Utility
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.
0
 
LVL 1

Author Comment

by:capturetheflag
Comment Utility
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
0
 
LVL 47

Expert Comment

by:for_yan
Comment Utility
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
0
 
LVL 86

Accepted Solution

by:
CEHJ earned 500 total points
Comment Utility
f = Float.valueOf (to.getTaskOrdCombinedCost ());

Open in new window


Your innermost method is returned as a String formatted to 2 dp. That will introduce errors. Only format/round at data presentation time
0
 
LVL 1

Author Comment

by:capturetheflag
Comment Utility
Hello for_van,

I have attached all the classes involded the task order.

Thanks.
FundingSummaryData.txt
FsInvoice.txt
GetFundingSummaryAction.txt
0
Enabling OSINT in Activity Based Intelligence

Activity based intelligence (ABI) requires access to all available sources of data. Recorded Future allows analysts to observe structured data on the open, deep, and dark web.

 
LVL 47

Expert Comment

by:for_yan
Comment Utility
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
0
 
LVL 86

Expert Comment

by:CEHJ
Comment Utility
I have attached all the classes involded the task order.

No need. I've told you the reason for your problem
0
 
LVL 1

Author Comment

by:capturetheflag
Comment Utility
Hello CEHJ,

Thank you for your help.  What is the first step in rounding/formating the data at presentation time?
0
 
LVL 47

Expert Comment

by:for_yan
Comment Utility
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.
0
 
LVL 1

Author Closing Comment

by:capturetheflag
Comment Utility
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.
0
 
LVL 1

Author Comment

by:capturetheflag
Comment Utility
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;
    }
0
 
LVL 86

Expert Comment

by:CEHJ
Comment Utility
:)

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

0
 
LVL 47

Expert Comment

by:for_yan
Comment Utility
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

0

Featured Post

Maximize Your Threat Intelligence Reporting

Reporting is one of the most important and least talked about aspects of a world-class threat intelligence program. Here’s how to do it right.

Join & Write a Comment

For customizing the look of your lightweight component and making it look opaque like it was made of plastic.  This tip assumes your component to be of rectangular shape and completely opaque.   (CODE)
Are you developing a Java application and want to create Excel Spreadsheets? You have come to the right place, this article will describe how you can create Excel Spreadsheets from a Java Application. For the purposes of this article, I will be u…
Viewers learn about the third conditional statement “else if” and use it in an example program. Then additional information about conditional statements is provided, covering the topic thoroughly. Viewers learn about the third conditional statement …
Viewers will learn about if statements in Java and their use The if statement: The condition required to create an if statement: Variations of if statements: An example using if statements:

743 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

15 Experts available now in Live!

Get 1:1 Help Now