-
Adding doubles together produces different results in Release vs. Debug mode.
Hi All,
I am running into a problem when adding together three doubles. The result I get when running in Debug mode does not match what I get in Release mode.
The three parts have the same value in both modes
dPart1 = 1237.8599999999999
dPart2 = 2351.9300000000003
dPart3 = 5407.3799999999992
When I add the three parts together in Debug Mode I get
8997.1699999999983
When I add the three parts together in Release Mode I get
8997.1700000000001
The Release Mode value is incorrect and is causing the application to display a value that is one penny too much.
I'm not sure what is causing this to happen.
If anyone has any ideas please let me know.
Thanks so much.
Greg
-
Re: Adding doubles together produces different results in Release vs. Debug mode.
Floating point math is not exact, and should not be relied upon for financial computations.
That isn't to say it's not accurate. It will in most cases give you an answer which is very, very close to correct. However, you can't rely on exactness.
Quote:
causing the application to display a value that is one penny too much
That seems unlikely, since the two values are within $0.00000000001 of each other. More likely, the amount shown for Debug is 1 penny too low, since you probably just truncated the value rather than rounding it.
-
Re: Adding doubles together produces different results in Release vs. Debug mode.
Quote:
Originally Posted by
gageller
The Release Mode value is incorrect and is causing the application to display a value that is one penny too much.
As Lindley pointed out, you shouldn't do financial calcualtions using doubles.
As a matter of fact, in most financial based companies (and I believe, some countries), you can't use floating point for any financial calculations due to round-off error and inexactness (and possible legal action if the calculations lead to short-changing some individual).
Regards,
Paul McKenzie
-
Re: Adding doubles together produces different results in Release vs. Debug mode.
I work for an insurance company. We have an online quoting facility for agents to use to give potential customers a quote.
We have a disclaimer on the reports stating the total quote is not guaranteed. The problem is that the individual pieces do not add up to make the total quote.
After doing the calculation manually, it has been determined that the release mode value is one penny too high. One step is rounding up instead of down.
I do understand what you are saying about the accuracy of the floating point calculations, but still need to provide them with a fix to this problem.
Would it be better to use floats instead of doubles?
Thanks again for your help,
Greg
-
Re: Adding doubles together produces different results in Release vs. Debug mode.
Quote:
Originally Posted by
gageller
Would it be better to use floats instead of doubles?
No, floats have significantly lower precision compared to doubles.
Financial industry must have a very strict and very well defined rules for handling fractions, rounding, etc.
To answer your original question about why debug results are different from release, I guess that floating-point control word is set differently for some reason in these configurations.
You can explicitly set it yourself to ensure that at least both configurations provide consistent result. Then you can implement proper rounding rules.
Check out _control87, particularly _MCW_RC (Rounding control).
-
Re: Adding doubles together produces different results in Release vs. Debug mode.
Quote:
Originally Posted by
gageller
After doing the calculation manually, it has been determined that the release mode value is one penny too high.
Okay, but then you must not have been using the numbers given. Because adding these up:
dPart1 = 1237.8599999999999
dPart2 = 2351.9300000000003
dPart3 = 5407.3799999999992
is best approximated by adding 1237.86 + 2351.93 + 5407.38 = 8997.17.
Especially since (in mathematics, not in floating point in particular), 0.99999 (repeating forever) is equal to 1. Not approximately the same; it's exactly 1 and you can prove that. An intuitive but informal outline of the proof is to look at the decimal patterns representing 4/9, 5/9, 7/9, etc, and then consider what 9/9 would look like.
-
Re: Adding doubles together produces different results in Release vs. Debug mode.
I did the rounding after adding up the individual parts
-
Re: Adding doubles together produces different results in Release vs. Debug mode.
Doesn't matter. Those three values still add up to be a lot closer to 8997.17 than 8997.16, and both the Debug and Release results you posted above are telling you that.
-
Re: Adding doubles together produces different results in Release vs. Debug mode.
Quote:
Originally Posted by
Lindley
Especially since (in mathematics, not in floating point in particular), 0.99999 (repeating forever) is equal to 1. Not approximately the same; it's exactly 1 and you can prove that. An intuitive but informal outline of the proof is to look at the decimal patterns representing 4/9, 5/9, 7/9, etc, and then consider what 9/9 would look like.
Off topic, but the proof is even simpler:
Let x = 0.99999 (repeated forever)
then 10x = 9.99999 (repeated forever)
10x - x = 9
9x = 9
x = 1
-
Re: Adding doubles together produces different results in Release vs. Debug mode.
Quote:
Originally Posted by
gageller
When I add the three parts together in Debug Mode I get
8997.1699999999983
When I add the three parts together in Release Mode I get
8997.1700000000001
The Release Mode value is incorrect and is causing the application to display a value that is one penny too much.
As Lindley pointed above, both of these values should be displayed as 8997.17, so your problem is in formatting the output.
-
Re: Adding doubles together produces different results in Release vs. Debug mode.
Thanks everyone.
I appreciate all of the replies!
-
Re: Adding doubles together produces different results in Release vs. Debug mode.
Quote:
Originally Posted by
gageller
I do understand what you are saying about the accuracy of the floating point calculations, but still need to provide them with a fix to this problem.
Long term solution -- use a library specially made for financial (or exact math) calculations. Googling will reveal some of these libraries, plus more information on this entire topic.
Regards,
Paul McKenzie
-
Re: Adding doubles together produces different results in Release vs. Debug mode.
As someone pointed out already, the precision of the calculations should be defined by the company's policy. I once coded a program for a company that required a 3-digit fractional precision on their financial calculations. This seems to be a norm for most smaller banks these days (I check my statement from my bank and there's always a variation in a penny -- plus or minus -- from my own financial program.) This stuff, I believe is outlined in all that small print that one has to sign before acknowledging the contract, etc.
Now going back to the technical aspect, my solution was to write a small rounding method that could be called by default every time that you had to output a result for a user (on a screen or on paper). Something like this:
Code:
void printAmountForAUser(double fAmt)
{
//For a 3-digit precision
__int64 ulAmt = (__int64)(fAmt * 1000.0);
double fAmtRounded = (double)ulAmt / 1000.0;
_tprintf(_T("%.2f"), fAmtRounded);
}
The important part is to do this "rounding" only on the final result that you output for a human being for viewing, and not on the actual foat/double value that you store back into a database or use for further calculations. This way you will not lose precision of your overall calculations.
-
Re: Adding doubles together produces different results in Release vs. Debug mode.
Thanks Ahmd,
I'll give it a try.
-
Re: Adding doubles together produces different results in Release vs. Debug mode.
Quote:
Originally Posted by
ahmd
...my solution was to write a small rounding method that could be called by default every time that you had to output a result for a user (on a screen or on paper). Something like this:
Code:
void printAmountForAUser(double fAmt)
{
//For a 3-digit precision
__int64 ulAmt = (__int64)(fAmt * 1000.0);
double fAmtRounded = (double)ulAmt / 1000.0;
_tprintf(_T("%.2f"), fAmtRounded);
}
Sorry, but I don't see any rounding here.
First you truncate everything past 3-rd decimal place, then you truncate one more while formatting it.
What was the point of this code? Ans how it differs from:
Code:
_tprintf(_T("%.2f"), fAmt);
-
Re: Adding doubles together produces different results in Release vs. Debug mode.
Quote:
Originally Posted by
VladimirF
What was the point of this code? Ans how it differs from:
Code:
_tprintf(_T("%.2f"), fAmt);
The multiply and then divide by 1000 part ensures that he doesn't lose the 3-rd decimal place, i.e. 1237.8599999999999 becomes 1237.86 and 1237.8549999999999 becomes 1237.85.
-
Re: Adding doubles together produces different results in Release vs. Debug mode.
Quote:
Originally Posted by
VladimirF
... floats have significantly lower precision compared to doubles.
Forgot to comment on this one. Yes, indeed the type double has 15 digits of precision while an older type float has only 7. But is it really making a big difference in the OP's case?
You would normally use double over float when dealing with a very small or very large numbers, or when you need to know the difference between, say, the 6th or 7th decimal digit. This may not be that crucial when dealing with most world currencies. (Again, the company's policy has to come into play here.)
So if the OP's database and/or other storage is coded for the use of float I would not rush to change it over to double. As besides an obvious hassle of converting databases as well as the existing code to a new type (that in itself is fraught with danger) you're also getting a tradeoff of increasing the size of your variables in 2 (i.e. 4 bytes for float and 8 bytes for double). For a large database this can cause a visible increase in its overall size.
I should also point out that if I was to code a brand new application from scratch I'd probably choose type double over float just for the sake of compatibility with the future technology. But I wouldn't worry if the code is already written to use float's.
-
Re: Adding doubles together produces different results in Release vs. Debug mode.
Quote:
Originally Posted by
ahmd
The multiply and then divide by 1000 part ensures that he doesn't lose the 3-rd decimal place...
I don't understand that.
However, I've modified your rounding function to print input parameter (just to compare), and I still see no effect of your transformation:
Code:
void printAmountForAUser(double fAmt)
{
_tprintf(_T("%.2f\n"), fAmt);
//For a 3-digit precision
__int64 ulAmt = (__int64)(fAmt * 1000.0);
double fAmtRounded = (double)ulAmt / 1000.0;
_tprintf(_T("%.2f\n\n"), fAmtRounded);
}
int _tmain(int argc, _TCHAR* argv[])
{
printAmountForAUser(1237.8599999999999);
printAmountForAUser(1237.8549999999999);
return 0;
}
And result:
Quote:
1237.86
1237.86
1237.85
1237.85
-
Re: Adding doubles together produces different results in Release vs. Debug mode.
Quote:
Originally Posted by
VladimirF
However, I've modified your rounding function to print input parameter (just to compare), and I still see no effect of your transformation
Well, the CRT way of handling %.2f switch for the printf function might have been changed to do the rounding internally since I first wrote this method. If so, then multiplying and then dividing would not be necessary. I'd still stick with it thought, to make sure it works on other (possibly older) platforms.
-
Re: Adding doubles together produces different results in Release vs. Debug mode.
Quote:
Originally Posted by
ahmd
Well, the CRT way of handling %.2f switch for the printf function might have been changed to do the rounding internally since I first wrote this method.
Please forgive my persistence. I would really like to get it (if there is something here that I can’t understand). I hope that you would also like to get this right.
Forget printf. Let’s just look at the values in debugger. In this example:
Code:
void printAmountForAUser(double fAmt)
{
//For a 3-digit precision
__int64 ulAmt = (__int64)(fAmt * 1000.0);
double fAmtRounded = (double)ulAmt / 1000.0;
if(fAmt == fAmtRounded)
_tprintf(_T("EQ\n\n"));
}
I got EQ for 1237.8599999999999 (surprised?) and nothing for 1237.8549999999999.
I then noticed that your initial values didn’t quite make it into doubles.
I see for the first value
Code:
fAmt 1237.8599999999999 double
but for the second
Code:
fAmt 1237.8549999999998 double
First value multiplied by 1000 produced
Code:
ulAmt 1237860 __int64
Again, surprised?
But regardless, I am just trying to follow your logic with pen and paper. By multiplying/deviding your doubles you are effectively truncating them. There is NO ROUNDING! What am I missing? Are you using some obscure Intel Processor trick?
But my guess is that your code only appears to work, and only on that small data sample.
The behavior will also depend on the floating-point control flags (there was another thread about it today).
-
Re: Adding doubles together produces different results in Release vs. Debug mode.
:) Sure, it's a valid argument. And no, there's no Intel Processor magic either. You might be right though. I posted the code without giving it a second thought. My original idea was that multiplication and division will take care of the last digit rounding, but I see your point. So let's change it to this:
Code:
void printAmountForAUser(double fAmt)
{
//For a 3-digit precision
__int64 ulAmt = (__int64)(fAmt * 100.0 + 0.5);
double fAmtRounded = (double)ulAmt / 100.0;
_tprintf(_T("%.2f"), fAmtRounded);
}
On the side note.
As comparison goes, I hope OP realizes that he/she should not be checking floats for equality in a simple == statement. The best alternative would be to use a method like this:
Code:
enum AMOUNT_COMPARISON_RESULT{
ACR_AMOUNTS_EQUAL = 0,
ACR_AMOUNT1_GREATER_THAN_AMOUNT2 = 1,
ACR_AMOUNT1_LESS_THAN_AMOUNT2 = -1,
};
AMOUNT_COMPARISON_RESULT compareTwoAmounts(double fAmt1, double fAmt2)
{
__int64 ulAmt1 = (__int64)(fAmt1 * 100.0 + 0.5); //Unbiased rounding of the 3rd decimal digit
__int64 ulAmt2 = (__int64)(fAmt2 * 100.0 + 0.5); //Unbiased rounding of the 3rd decimal digit
if(ulAmt1 > ulAmt2)
return ACR_AMOUNT1_GREATER_THAN_AMOUNT2;
else if(ulAmt1 < ulAmt2)
return ACR_AMOUNT1_LESS_THAN_AMOUNT2;
return ACR_AMOUNTS_EQUAL;
}
-
Re: Adding doubles together produces different results in Release vs. Debug mode.
Quote:
Originally Posted by
ahmd
On the side note.
As comparison goes, I hope OP realizes that he/she should not be checking floats for equality in a simple == statement.
Oh, I agree. I used == specifically to see if the bits match exactly (and they did for the first test case).
-
Re: Adding doubles together produces different results in Release vs. Debug mode.
Quote:
Originally Posted by
gageller
I did the rounding after adding up the individual parts
You should do the rounding on each value as you add them, not once at the end.
Consider adding the following 3 values;
$1.00499999
$2.00499999
$3.00499999
Assuming you want to round and display them to 2 decimal places, and show the total, rounding at the end will give you;
$1.00
+$2.00
+$3.00
= $6.01
($1.00499999 + $2.00499999 + $3.00499999 = 6.01499988)
If you round as you add each line, the result would be
$1.00
+$2.00
+$3.00
= $6.00
$1.00 ($1.00499999 rounded) + $2.00 ($2.00499999 rounded) + $3.00 ($3.00499999 Rounded) = $6.00