views:

2771

answers:

14

I write financial applications where I constantly battle the decision to use a double vs using a decimal.

All of my math works on numbers with no more than 5 decimal places and are not larger than ~100,000. I have a feeling that all of these can be represented as doubles anyways without rounding error, but have never been sure.

I would go ahead and make the switch from decimals to doubles for the obvious speed advantage, except that at the end of the day, I still use the ToString method to transmit prices to exchanges, and need to make sure it always outputs the number I expect. (89.99 instead of 89.99000000001)

Questions:

  1. Is the speed advantage really as large as naive tests suggest? (~100 times)
  2. Is there a way to guarantee the output from ToString to be what I want? Is this assured by the fact that my number is always representable?

UPDATE: I have to process ~ 10 billion price updates before my app can run, and I have implemented with decimal right now for the obvious protective reasons, but it takes ~3 hours just to turn on, doubles would dramatically reduce my turn on time. Is there a safe way to do it with doubles?

+24  A: 
  1. Floating point arithmetic will almost always be significantly faster because it is supported directly by the hardware. So far almost no widely used hardware supports decimal arithmetic (although this is changing, see comments).
  2. Financial applications should always use decimal numbers, the number of horror stories stemming from using floating point in financial applications is endless, you should be able to find many such examples with a Google search.
  3. While decimal arithmetic may be significantly slower than floating point arithmetic, unless you are spending a significant amount of time processing decimal data the impact on your program is likely to be negligible. As always, do the appropriate profiling before you start worrying about the difference.
Robert Gamble
+1: Avoid premature optimization. Measure first, optimize only after you have proof.
S.Lott
"no widely-used hardware..." IBM Mainframes (still remarkably popular) have hardware decimal.
S.Lott
New machines supporting the new IEEE 754 floating point standard will have decimal arithmetic support in hardware. IBM Power6 is one such, I believe.
Jonathan Leffler
@S.Lott: "*almost* no widely-used hardware".
Robert Gamble
@Jonathan: I know that IBM recently announced decimal support in Power6 and they are expected to integrate support in other lines as well, others may follow which is a good thing but it will be many years before decimal support in hardware is wide-spread.
Robert Gamble
I did work on a system where Decimal performance was a bottle neck. It was a windows based nightly batch process. In 1996.
sal
+6  A: 

Just one question - is that REALLY the bottleneck of your application?

Vilx-
In many cases it is.
Robert Gamble
In *financial* applications? o_O Well, maybe... but then it has to be best written application I've ever seen, if everything else is already optimized to the max and people are now looking for speedups in this direction.
Vilx-
Applications that have to process large sets of financial data are often limited by the efficiency of the decimal implementation.
Robert Gamble
@Robert Gamble: disagree. My benchmarks showed that RDBMS queries are the worst bottleneck. File I/O is second worst.
S.Lott
@S.Lott: I am not saying that decimal arithmetic is the main, or even a significant, bottleneck in most applications but there are many that would benefit noticeably from decimal hardware support. You can disagree if you like but there is a reason decimal support is finding its way into hardware.
Robert Gamble
There is no way Decimal vs Double is bottleneck in a financial application unless you are doing some serious (ie. atypical) number crunching. I recently converted a huge XNA Game to use floating point math from double and after all was said and done it resulted in a 2.1% speed increase.
Simucal
+6  A: 

Always use Decimal for any financial calculations or you will be forever chasing 1cent rounding errors.

Craig
+1: Don't waste time on performance unless you have proof that it's the math package.
S.Lott
A: 

The number of decimal places does not matter. You will still have rounding errors. For example, try 0.8? It cannot be represented precisely as a floating point number. So you will probably end up with 0.79999 using 5 decimal places with an error of 0.00001. Is that good enough? Given that its financial calculations I suspect not. Always use decimal numbers for financial calculations.

Vincent Ramdhanie
+6  A: 
  1. Yes; software arithmetic really is 100 times slower than hardware. Or, at least, it is a a lot slower, and a factor of 100, give or take an order of magnitude, is about right. Back in the bad old days when you could not assume that every 80386 had an 80387 floating-point co-processor, then you had software simulation of binary floating point too, and that was slow.
  2. No; you are living in a fantasy land if you think that a pure binary floating point can ever exactly represent all decimal numbers. Binary numbers can combine halves, quarters, eighths, etc, but since an exact decimal of 0.01 requires two factors of one fifth and one factor of one quarter (1/100 = (1/4)*(1/5)*(1/5)) and since one fifth has no exact representation in binary, you cannot exactly represent all decimal values with binary values (because 0.01 is a counter-example which cannot be represented exactly, but is representative of a huge class of decimal numbers that cannot be represented exactly).

So, you have to decide whether you can deal with the rounding before you call ToString() or whether you need to find some other mechanism that will deal with rounding your results as they are converted to a string. Or you can continue to use decimal arithmetic since it will remain accurate, and it will get faster once machines are released that support the new IEEE 754 decimal arithmetic in hardware.

Obligatory cross-reference: What Every Computer Scientist Should Know About Floating-Point Arithmetic. That's one of many possible URLs and leads to a PDF file. There's an HTML version at Sun which is apparently an edited version of the same paper.

Information on decimal arithmetic and the new IEEE 754:2008 standard at this Speleotrove site (material previously hosted at IBM).

Jonathan Leffler
I may be confused, but I thought 0.01 was 1/100th, and 0.2 was 1/5th?
Software Monkey
1/100 needs a divisor of 5 as well as a binary fraction - or, more precisely, needs two factors of five: 1/100 = (1/4)*(1/5)*(1/5). The 1/4 can be represented exactly in a binary floating point number; the 1/5 factors cannot.
Jonathan Leffler
A: 

Is it me, or has this question been asked over-and-over in the last week.

Always use fixed-point math when precision must be preserved, no matter how many decimal points you want.

Use floating-point for graphics, sound...

Overflown
If it has been asked over and over, you could probably find the cross-references. I'd not noticed the other questions, but that may merely mean that I wasn't paying enough attention.
Jonathan Leffler
+1  A: 

I always wonder why financial applications do not just calculate in pennies/cents/øre/whatever, and just let the displaying/entering routines change it into/from the usual representation. That way, even the old british system could be calculated without overhead (in farthing).

Svante
5 decimal places is thousandths of pennies, not pennies. Nevertheless, change the basic unit and you could do more or less as you suggest.
Jonathan Leffler
Huh? Who said anything about 5 decimal places?
Svante
One has to be aware that in some financial applications, the precision is finer than the lowest denomination in the currency. Also, there are some rounding rules when calculating with percentages.
Svante
+2  A: 

Decimals should always be used for financial calculations. The size of the numbers isn't important.

The easiest way for me to explain is via some C# code.

double one = 3.05;
double two = 0.05;

System.Console.WriteLine((one + two) == 3.1);

That bit of code will print out False even though 3.1 is equal to 3.1...

Same thing...but using decimal:

decimal one = 3.05m;
decimal two = 0.05m;

System.Console.WriteLine((one + two) == 3.1m);

This will now print out True!

If you want to avoid this sort of issue, I recommend you stick with decimals.

mezoid
What's truly irritating is that the .NET ToString() methods seems to be incapable of displaying this difference, even when asked to display up to 20 decimal figures. If you subtract the numbers, you can see that there is about 4.44E-16 difference between them... but I don't see a 4 anywhere in the string "3.10000000000000000000" output by the ToString method. If you use BitConverter.ToString( BitConverter.GetBytes( one + two )) and the same thing for (3.1), then the bytes are indeed different (CD-CC-CC-CC-CC-CC-08-40 vs.CC-CC-CC-CC-CC-CC-08-40), however double.ToString() does not show this!
Triynko
+6  A: 

There are two separable issues here. One is whether the double has enough precision to hold all the bits you need, and the other is where it can represent your numbers exactly.

As for the exact representation, you are right to be cautious, because an exact decimal fraction like 1/10 has no exact binary counterpart. However, if you know that you only need 5 decimal digits of precision, you can use scaled arithmetic in which you operate on numbers multiplied by 10^5. So for example if you want to represent 23.7205 exactly you represent it as 2372050.

Let's see if there is enough precision: double precision gives you 53 bits of precision. This is equivalent to 15+ decimal digits of precision. So this would allow you five digits after the decimal point and 10 digits before the decimal point, which seems ample for your application.

I would put this C code in a .h file:

typedef double scaled_int;

#define SCALE_FACTOR 1.0e5  /* number of digits needed after decimal point */

static inline scaled_int adds(scaled_int x, scaled_int y) { return x + y; }
static inline scaled_int muls(scaled_int x, scaled_int y) { return x * y / SCALE_FACTOR; }

static inline scaled_int scaled_of_int(int x) { return (scaled_int) x * SCALE_FACTOR; }
static inline int intpart_of_scaled(scaled_int x) { return floor(x / SCALE_FACTOR); }
static inline int fraction_of_scaled(scaled_int x) { return x - SCALE_FACTOR * intpart_of_scaled(x); }

void fprint_scaled(FILE *out, scaled_int x) {
  fprintf(out, "%d.%05d", intpart_of_scaled(x), fraction_of_scaled(x));
}

There are probably a few rough spots but that should be enough to get you started.

No overhead for addition, cost of a multiply or divide doubles.

If you have access to C99, you can also try scaled integer arithmetic using the int64_t 64-bit integer type. Which is faster will depend on your hardware platform.

Norman Ramsey
A: 

I would look for opportunities to run code in parallel, if possible. Take a look at the parallel libraries for .net and see if you can gain speedups that way. I wouldn't risk floating point calculations if little things like, say, accuracy, matter to you. =)

Travis
+1  A: 

I would just like to point out that there are actally hardware decimal floating point units, like IBM's POWER6 chip. Well, obviously, it's not a cheap thing.

That being said, always use decimals instead of binary floating points in financial calculations. If you cannot afford a hardware decimal FPU, consider chopping up and paralellizing your stuff. You could even run it on multiple computers.

DrJokepu
+1  A: 

What's your bottleneck? Exactly where are you burning all your CPU cycles?

Without solid measurements there is a good chacne you are looking at the entirely wrong thing.

Jonathan Allen
+1  A: 

I refer you to my answer given to this question.

Use a long, store the smallest amount you need to track, and display the values accordingly.

gbjbaanb
+1  A: 

Just use a long and multiply by a power of 10. After you're done, divide by the same power of 10.