Solved

Currency Total and Rounding Errors with Java Method

Posted on 2012-03-22
15
364 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
ID: 37752447
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
ID: 37752461
0
 
LVL 47

Expert Comment

by:for_yan
ID: 37752498
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
Networking for the Cloud Era

Join Microsoft and Riverbed for a discussion and demonstration of enhancements to SteelConnect:
-One-click orchestration and cloud connectivity in Azure environments
-Tight integration of SD-WAN and WAN optimization capabilities
-Scalability and resiliency equal to a data center

 
LVL 1

Author Comment

by:capturetheflag
ID: 37753029
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
ID: 37753132
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
ID: 37753329
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
ID: 37753350
Hello for_van,

I have attached all the classes involded the task order.

Thanks.
FundingSummaryData.txt
FsInvoice.txt
GetFundingSummaryAction.txt
0
 
LVL 47

Expert Comment

by:for_yan
ID: 37753429
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
ID: 37753496
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
ID: 37756811
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
ID: 37756860
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
ID: 37756942
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
ID: 37757002
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
ID: 37757473
:)

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
ID: 37757832
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

Free Tool: Path Explorer

An intuitive utility to help find the CSS path to UI elements on a webpage. These paths are used frequently in a variety of front-end development and QA automation tasks.

One of a set of tools we're offering as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

Title # Comments Views Activity
hibernate example issues from command prompt 10 50
ejb example issues 3 21
Java syntax, or is it Selenium 6 30
java mysql insert application 14 26
For customizing the look of your lightweight component and making it look lucid like it was made of glass. Or: how to make your component more Apple-ish ;) This tip assumes your component to be of rectangular shape and completely opaque. (COD…
After being asked a question last year, I went into one of my moods where I did some research and code just for the fun and learning of it all.  Subsequently, from this journey, I put together this article on "Range Searching Using Visual Basic.NET …
Viewers learn how to read error messages and identify possible mistakes that could cause hours of frustration. Coding is as much about debugging your code as it is about writing it. Define Error Message: Line Numbers: Type of Error: Break Down…
This tutorial covers a step-by-step guide to install VisualVM launcher in eclipse.

839 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