views:

2887

answers:

5

AFAIK, Currency type in Delphi Win32 depends on the processor floating point precision. Because of this I'm having rounding problems when comparing two Currency values, returning different results depending on the machine.

For now I'm using the SameValue function passing a Epsilon parameter = 0.009, because I only need 2 decimal digits precision.

Is there any better way to avoid this problem?

A: 

To avoid possible issues with currency rounding in Delphi use 4 decimal places.

This will ensure that you never having rounding issues when doing calcualtions with very small amounts.

"Been there. Done That. Written the unit tests."

Matt Lacey
Wow. did I really write this. Tempted to delete this but it serves as an example of what not to do. ;)
Matt Lacey
+12  A: 

No, Currency is not a floating point type. It is a fixed-precision decimal, implemented with integer storage. It can be compared exactly, and does not have the rounding issues of, say, Double. Therefore, if you are seeing inexact values in your Currency variables, the problem is not the Currency type itself, but what you are putting into it. Most likely, you have a floating-point calculation somewhere else in your code. Since you do not show that code, it's hard to be of more help on this question. But the solution, generally speaking, will be to round your floating point numbers to the correct precision before storing in the Currency variable, rather than doing an inexact comparison on the Currency variables.

Craig Stuntz
From the Delphi Help: The Currency type: An 8-byte (64-bit) Currency number is stored as a scaled and signed 64-bit integer with the four least-significant digits implicitly representing four decimal places.
Ralph Rickenbach
+9  A: 

The Currency type in Delphi is a 64-bit integer scaled by 1/10,000; in other words, its smallest increment is equivalent to 0.0001. It is not susceptible to precision issues in the same way that floating point code is.

However, if you are multiplying your Currency numbers by floating-point types, or dividing your Currency values, the rounding does need to be worked out one way or the other. The FPU controls this mechanism (it's called the "control word"). The Math unit contains some procedures which control this mechanism: SetRoundMode in particular. You can see the effects in this program:

{$APPTYPE CONSOLE}

uses Math;

var
  x: Currency;
  y: Currency;
begin
  SetRoundMode(rmTruncate);
  x := 1;
  x := x / 6;
  SetRoundMode(rmNearest);
  y := 1;
  y := y / 6;
  Writeln(x = y); // false
  Writeln(x - y); // 0.0001; i.e. 0.1666 vs 0.1667
end.

It is possible that a third-party library you are using is setting the control word to a different value. You may want to set the control word (i.e. rounding mode) explicitly at the starting point of your important calculations.

Also, if your calculations ever transfer into plain floating point and then back into Currency, all bets are off - too hard to audit. Make sure all your calculations are in Currency.

Barry Kelly
A: 

If your situation is like mine, you might find this approach helpful. I work mostly in payroll. If a business has say 3 departments and wants to charge the cost of an employee evenly among those three departments, there are a lot of times when there will be rounding issues.

What I have been doing is loop through the departments charging each one a third of the total cost and adding the cost charged to a subtotal (currency) variable. But when the loop variable equals the limit, rather than multiplying by the fraction, I subtract the subtotal variable from the total cost and put that in the last department. Since the journal entries that result from this process always have to balance, I believe that it has always worked.

A: 

See thread:

D7 / DUnit: all CheckEquals(Currency, Currency) tests suddenly fail ...

https://forums.codegear.com/thread.jspa?threadID=16288

It looks like a change on our development workstations caused Currency comparision to fail. We have not found the root cause, but on two computers running Windows 2000 SP4, and independent of the version of gds32.dll (InterBase 7.5.1 or 2007) and Delphi (7 and 2009), this line

TIBDataBase.Create(nil);

changes the value of to 8087 control word from $1372 to $1272 now.

And all Currency comparisions in unit tests will fail with funny messages like

Expected: <12.34> - Found: <12.34>

The gds32.dll has not been modified, so I guess that there is a dependency in this library to a third party dll which modifies the control word.

mjustin