tags:

views:

221

answers:

5

I've inherited a project in which monetary amounts use the double type.

Worse, the framework it uses, and the framework's own classes, use double for money.

The framework ORM also handles retrieval of values from (and storage to) the database. In the database money values are type number(19, 7), but the framework ORM maps them to doubles.

Short of entirely bypassing the framework classes and ORM, is there anything I can do to calculate monetary values precisely?

Edit: yeah, I know BigDecimal should be used. The problem is that I am tightly tied to a framework that where, e.g., the class framework.commerce.pricing.ItemPriceInfo has members double mRawTotalPrice; and double mListPrice. My company's application's own code extends, e.g, this ItemPriceInfoClass.

Realistically, I can't say to my company, "scrap two years of work, and hundreds of thousands of dollars spent, basing code on this framework, because of rounding errors"

+5  A: 

I didn't see you mention refactoring. I think that's your best option here. Instead of throwing together some hacks to get things working better for now, why not fix it the right way?

Here's some information on double vs BigDecimal. This post suggests using BigDecimal even though it is slower.

Robert Greiner
[Effective Java](http://java.sun.com/docs/books/effective/) suggests it too (2nd Edition, Item 48).
Péter Török
I should hope it suggests `BigDecimal`... speed doesn't mean much if your numbers and calculations are _incorrect_ because of `double`.
ColinD
From the OP, it seems like he *can't* refactor as the framework (which I assume he doesn't have the source code for) uses FP arithmetic.
gustafc
+8  A: 

If tolerable, treat the monetary type as integral. In other words, if you're working in the US, track cents instead of dollars, if cents provides the granularity you need. Doubles can accurately represent integers up to a very large value (2^53) (no rounding errors up to that value).

But really, the right thing to do is bypass the framework entirely and use something more reasonable. That's such an amateur mistake for the framework to make - who knows what else is lurking?

Michael Petrotta
+1  A: 

Well, you don't have that many options in reality:

You can refactor the project to use e.g. BigDecimal (or something that better suits its needs) to represent money.

Be extremely careful for overflow/underflow and loss of precision, which means adding tons of checks, and refactoring even larger proportion of the system in an unnecessary way. Not to mention how much research would be necessary if you are to do that.

Keep things the way they are and hope nobody notices (this is a joke).

IMHO, the best solution would be to simply refactor this out. It might be some heavy refactoring, but the evil is already done and I believe that it should be your best option.

Best, Vassil

P.S. Oh and you can treat money as integers (counting cents), but that doesn't sound like a good idea if you are going to have currency conversions, calculating interest, etc.

vstoyanov
Loss of precision maybe, but "overflow/underflow" are you serious?
Peter Lawrey
double overflows for values over 10^308 and underflows for values less than 10^-308. Which currencies have this sort order?
Peter Lawrey
Ok, overflow in highly improbable, but you cannot possibly know what operations would be performed on these numbers and underflow is a possibility in some scenarios.
vstoyanov
Also software engineering is also about managing the risk, and if you can swallow the performance hit, you should use BigDecimal, simply because it is less error-prone. Oh, and hunting floating-point arithmetic bugs rivals only synchronization issues in nastiness.
vstoyanov
+2  A: 

Plenty of people will suggest using BigDecimal and if you don't know how to use rounding in your project, that is what you should do.

If you know how to use decimal rounding correctly, use double. Its many orders of magnitude faster, much clear and simpler and thus less error prone IMHO. If you use dollars and cents (or need two decimal places), you can get an accurate result for values up to 70 trillion dollars.

Basically, you won't get round errors if you correct for it using approriate rounding.

BTW: The thought of rounding errors strikes terror into the heart of many developers, but they are not random errors and you can manage them fairly easily.

EDIT: consider this simple example of a rounding error.

    double a = 100000000.01;
    double b = 100000000.09;
    System.out.println(a+b); // prints 2.0000000010000002E8

There are a number of possible rounding strategies. You can either round the result when printing/displaying. e.g.

    System.out.printf("%.2f%n", a+b); // prints 200000000.10

or round the result mathematically

    double c = a + b;
    double r= (double)((long)(c * 100 + 0.5))/100;
    System.out.println(r); // prints 2.000000001E8

In my case, I round the result when sending from the server (writing to a socket and a file), but use my own routine to avoid any object creation.

A more general round function is as follows, but if you can use printf or DecimalFormat, can be simpler.

private static long TENS[] = new long[19]; static { 
    TENS[0] = 1; 
    for (int i = 1; i < TENS.length; i++) TENS[i] = 10 * TENS[i - 1]; 
} 

public static double round(double v, int precision) { 
    assert precision >= 0 && precision < TENS.length; 
    double unscaled = v * TENS[precision]; 
    assert unscaled > Long.MIN_VALUE && unscaled < Long.MAX_VALUE; 
    long unscaledLong = (long) (unscaled + (v < 0 ? -0.5 : 0.5)); 
    return (double) unscaledLong / TENS[precision]; 
}

note: you could use BigDecimal to perform the final rounding. esp if you need a specifc round method.

Peter Lawrey
I didn't downvote, but without actually providing some guidance about how to manage these rounding errors, the answer is just not helpful.
Yishai
If you are dealing with large sums of money, this is not practical (think millions and billions of dollars being added/subtracted, etc). Additionally, depending on hardware, the rounding errors could be different, which would provide potential future pain points and definitely could cause unpredictable behavior. If precision is necessary, you do not use floating point estimated data structures (double/float) and instead focus on precision instead.
aperkins
It is worth mentioning that I didn't downvote either.
vstoyanov
Java performs the same rounding and precision on all JSE platforms, you might find JME platforms are difference, I don't have any experience of those.
Peter Lawrey
A: 

I think this situation is at least minimally salvageable for your code. You get the value as a double via the ORM framework. You can then convert it to BigDecimal using the static valueOf method (see here for why) before doing any math/calculations on it, and then convert it back to double only for storing it.

Since you are extending these classes anyway, you can add getters for your double value that gets them as BigDecimal when you need it.

This may not cover 100% of the cases (I would be especially worried about what the ORM or JDBC driver is doing to convert the double back to a Number type), but it is so much better than just doing the math on the raw doubles.

However, I am far from convinced that this approach is actually cheaper for the company in the long run.

Yishai