Still celebrating National IT Professionals Day with 3 months of free Premium Membership. Use Code ITDAY17

x
?
Solved

Currency Total and Rounding Errors with Java Method

Posted on 2012-03-22
15
Medium Priority
?
374 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
The top UI technologies you need to be aware of

An important part of the job as a front-end developer is to stay up to date and in contact with new tools, trends and workflows. That’s why you cannot miss this upcoming webinar to explore the latest trends in UI technologies!

 
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 2000 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

Build and deliver software with DevOps

A digital transformation requires faster time to market, shorter software development lifecycles, and the ability to adapt rapidly to changing customer demands. DevOps provides the solution.

Question has a verified solution.

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

Introduction This article is the second of three articles that explain why and how the Experts Exchange QA Team does test automation for our web site. This article covers the basic installation and configuration of the test automation tools used by…
In this post we will learn different types of Android Layout and some basics of an Android App.
Viewers will learn about the regular for loop in Java and how to use it. Definition: Break the for loop down into 3 parts: Syntax when using for loops: Example using a for loop:
This tutorial covers a step-by-step guide to install VisualVM launcher in eclipse.
Suggested Courses

704 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