Solved

Currency Total and Rounding Errors with Java Method

Posted on 2012-03-22
15
370 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
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 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: 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
Get 15 Days FREE Full-Featured Trial

Benefit from a mission critical IT monitoring with Monitis Premium or get it FREE for your entry level monitoring needs.
-Over 200,000 users
-More than 300,000 websites monitored
-Used in 197 countries
-Recommended by 98% of users

 
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

Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

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

INTRODUCTION Working with files is a moderately common task in Java.  For most projects hard coding the file names, using parameters in configuration files, or using command-line arguments is sufficient.   However, when your application has vi…
This was posted to the Netbeans forum a Feb, 2010 and I also sent it to Verisign. Who didn't help much in my struggles to get my application signed. ------------------------- Start The idea here is to target your cell phones with the correct…
Viewers learn about the “for” loop and how it works in Java. By comparing it to the while loop learned before, viewers can make the transition easily. You will learn about the formatting of the for loop as we write a program that prints even numbers…
Video by: Michael
Viewers learn about how to reduce the potential repetitiveness of coding in main by developing methods to perform specific tasks for their program. Additionally, objects are introduced for the purpose of learning how to call methods in Java. Define …
Suggested Courses

626 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