tags:

views:

3234

answers:

14
+3  A: 

It depends on your business requirements with regards to rounding. The safest way is to store an integer with the required precision and know when/how to apply rounding.

Douglas Mayle
This will get expensive though in terms of conversion issues. You'll be doing a conversion each time you do anything with the value since it's unlikely every floating point value in the system will be this kind of integer.
Orion Adrian
As in my answer, the precision of value is equal to the precision of the least precise calculation. Integer * Float is going to use float precision. For C++, the entire chain should be long double precision.
Orion Adrian
What you don't seem to realize, Orion is that not all values can be stored in a float. As such, odd little math errors can creep into your calculation if you don't know where and when you are rounding to clean up errors.
Douglas Mayle
+7  A: 

I would suggest that you keep a variable for the number of cents instead of dollars. That should remove the rounding errors. Displaying it in the standards dollars/cents format should be a view concern.

Rontologist
This doesn't actually solve the problem since you often have to do more than just add to these numbers and then you're going to have problems as you're going to be loosing precision. $100.25 translated to 10025 * 0.0745234 APR is going to cause problems.
Orion Adrian
If I recall it correctly, there's a standard somewhere that says you should keep a minimum of 4 digits for common operations - that's why COM's "Currency" gave you 4. If foreign currencies are involved, you would probably need more.
Joe Pineda
I've explained the problem of least precision in precision-based calculations in my answer to this question. Ultimately even if you store the number in integer form you're going to have to do calculations in something else. Whatever that something else is should be the storage mechanism.
Orion Adrian
@Joe: 4 decimal places is the minimum really. I ended using 6 for my calculations to get penny resolution on check operations. But unless you do all your math in integer form, you're going to have issues because if you cast (implicitly or explicitly) you're going to end up in floating point land.
Orion Adrian
+2  A: 

Having dealt with this in actual financial systems, I can tell you you probably want to use a number with at least 6 decimal places of precision (assuming USD). Hopefully since you're talking about currency values you won't go way out of whack here. There are proposals for adding decimal types to C++, but I don't know of any that are actually out there yet.

The best native C++ type to use here would be long double.

The problem with other approaches that simply use an int is that you have to store more than just your cents. Often financial transactions are multiplied by non-integer values and that's going to get you in trouble since $100.25 translated to 10025 * 0.000123523 (e.g. APR) is going cause problems. You're going to eventually end up in floating point land and the conversions are going to cost you a lot.

Now the problem doesn't happen in most simple situations. I'll give you a precise example:

Given several thousand currency values, if you multiply each by a percentage and then add them up, you will end up with a different number than if you had multiplied the total by that percentage if you do not keep enough decimal places. Now this might work in some situations, but you'll often be several pennies off pretty quickly. In my general experience making sure you keep a precision of up to 6 decimal places (making sure that the remaining precision is available for the whole number part).

Also understand that it doesn't matter what type you store it with if you do math in a less precise fashion. If your math is being done in single precision land, then it doesn't matter if you're storing it in double precision. Your precision will be correct to the least precise calculation.


Now that said, if you do no math other than simple addition or subtraction and then store the number then you'll be fine, but as soon as anything more complex than that shows up, you're going to be in trouble.

Orion Adrian
Could you expand your objection to ints, or provide a reference? The sample calculation you provided leads to a result of $0.01 or 1 using ints. It is not obvious to me why this is not the correct answer.
Jeffrey L Whitledge
See above example. I can provide more, but in this situation, it's usually pretty straight forward. I wrote financial forecasting software and you can't get away with integers and rounding. You need to store more than just cents, but also fractional cents. Eventually rounding issues will get you.
Orion Adrian
I've written some point-of-sale software and my solution to this problem (manifested as sum(discounts-per-line-item) != discount-on-order-total) is to make sure you always do the calculation that you mean. The problem space should dictate a summation of small percentages or a percentage of a sum.
Jeffrey L Whitledge
+1  A: 

I would recommend using a long int to store the currency in the smallest denomination (for example, American money would be cents), if a decimal based currency is being used.

Very important: be sure to name all of your currency values according to what they actually contain. (Example: account_balance_cents) This will avoid a lot of problems down the line.

(Another example where this comes up is percentages. Never name a value "XXX_percent" when it actually contains a ratio not multiplied by a hundred.)

Jeffrey L Whitledge
+4  A: 

Look in to the relatively recent Intelr Decimal Floating-Point Math Library. It's specifically for finance applications and implements some of the new standards for binary floating point arithmetic (IEEE 754r).

jblocksom
A: 

The GMP library has "bignum" implementations that you can use for arbitrary sized integer calculations needed for dealing with money. See the documentation for mpz_class (warning: this is horribly incomplete though, full range of arithmetic operators are provided).

Greg Rogers
+2  A: 

Whatever type you do decide on, I would recommend wrapping it up in a "typedef" so you can change it at a different time.

j0rd4n
A: 

One option is to store $10.01 as 1001, and do all calculations in pennies, dividing by 100D when you display the values.

Or, use floats, and only round at the last possible moment.

Often the problems can be mitigated by changing order of operations.

Instead of value * .10 for a 10% discount, use (value * 10)/100, which will help significantly. (remember .1 is a repeating binary)

chris
Never use floats. Try representing $0.60 as float. Financial code (AKA code for a bank) is not allowed to have rounding errors => no floats.
Martin York
+1  A: 

Integers, always--store it as cents (or whatever your lowest currency is where you are programming for.) The problem is that no matter what you do with floating point someday you'll find a situation where the calculation will differ if you do it in floating point. Rounding at the last minute is not the answer as real currency calculations are rounded as they go.

You can't avoid the problem by changing the order of operations, either--this fails when you have a percentage that leaves you without a proper binary representation. Accountants will freak if you are off by a single penny.

Loren Pechtel
+1  A: 

Our financial institution uses "double". Since we're a "fixed income" shop, we have lots of nasty complicated algorithms that use double anyway. The trick is to be sure that your end-user presentation does not overstep the precision of double. For example, when we have a list of trades with a total in trillions of dollars, we got to be sure that we don't print garbage due to rounding issues.

Arkadiy
+2  A: 

Don't store it just as cents, since you'll accumulate errors when multiplying for taxes and interest pretty quickly. At the very least, keep an extra two significant digits: $12.45 would be stored as 124,500. If you keep it in a signed 32 bit integer, you'll have $2,000,000 to work with (positive or negative). If you need bigger numbers or more precision, a signed 64 bit integer will likely give you all the space you'll need for a long time.

It might be of some help to wrap this value in a class, to give you one place for creating these values, doing arithmetic on them, and formatting them for display. This would also give you a central place to carry around which currency it being stored (USD, CAD, EURO, etc).

Eclipse
+1 for remembering to explicitly include the currency.
Donal Fellows
+1  A: 

Know YOUR range of data.

A float is only good for 6 to 7 digits of precision, so that means a max of about +-9999.99 without rounding. It is useless for most financial applications.

A double is good for 13 digits, thus: +-99,999,999,999.99, Still be careful when using large numbers. Recognize the subtracting two similar results strips away much of the precision (See a book on Numerical Analysis for potential problems).

32 bit integer is good to +-2Billion (scaling to pennies will drop 2 decimal places)

64 bit integer will handle any money, but again, be careful when converting, and multiplying by various rates in your app that might be floats/doubles.

The key is to understand your problem domain. What legal requirements do you have for accuracy? How will you display the values? How often will conversion take place? Do you need internationalization? Make sure you can answer these questions before you make your decision.

Dan Hewett
A: 

go ahead and write you own money (http://junit.sourceforge.net/doc/testinfected/testing.htm) or currency () class (depending on what you need). and test it.

A: 

The biggest issue is rounding itself!

19% of 42,50 € = 8,075 €. Due to the German rules for rounding this is 8,08 €. The problem is, that (at least on my machine) 8,075 can't be represented as double. Even if I change the variable in the debugger to this value, I end up with 8,0749999....

And this is where my rounding function (and any other on floating point logic that I can think of) fails, since it produces 8,07 €. The significant digit is 4 and so the value is rounded down. And that is plain wrong and you can't do anything about it unless you avoid using floating point values wherever possible.

It works great if you represent 42,50 € as Integer 42500000.

42500000 * 19 / 100 = 8075000. Now you can apply the rounding rule above 8080000. This can easily be transformed to a currency value for display reasons. 8,08 €.

But I would always wrap that up in a class.