6

In Delphi6 or in Delphi 2010, declare two variables of type Currency (vtemp1,vtemp2) and feed them a value of 0.09. Embed one of the variables with the ABS function and compare it to the other. You would expect for the comparison to yield a positive result as the compiler watch reveals the same value for abs(vtemp1) and vtemp2. Oddly the if statement fails!!!

Notes: -This problem is experienced only when dealing with the number 0.09 (trying several other near values revealed normal results) -Declaring the variable as Double instead of currency, the problem ceases to exist.

Johny
  • 419
  • 4
  • 15
  • 3
    [How dangerous is it to compare floating point values?](http://stackoverflow.com/questions/10334688/how-dangerous-is-it-to-compare-floating-point-values) – valex Aug 13 '12 at 07:47
  • Use `SameValue` from 'math' or similar. – Sertac Akyuz Aug 13 '12 at 07:55
  • 2
    Currency is not a floating point type – kludg Aug 13 '12 at 07:59
  • 2
    @valexhome According to documentation, Currency is fixed point. At least in current version: http://docwiki.embarcadero.com/Libraries/en/System.Currency – Harriv Aug 13 '12 at 08:01
  • 3
    @Serg - Abs(Currency) probably is, the compiler produces an FCOMPP to compare the values with D2007. I can duplicate it BTW. – Sertac Akyuz Aug 13 '12 at 08:09
  • @Harriv Yes it is stored in 64bit integer and divided or multiplied by 10000 when mixed with real types. But I guess for Currency a REAL type ABS is used so before ABS value divided by 10000 (converted to REAL) and after ABS for REAL it is multiplied by 10000 and stored into currency. So here can be an issue. – valex Aug 13 '12 at 10:01
  • @SertacAkyuz Yes, Abs(Currency) returns a float value. The OP did not says that he compares float values (not Currency values). – kludg Aug 13 '12 at 10:41
  • @valexhome - no, that potentially could be an issue if FPU mode changed; the OP 'issue' is that he compares float values – kludg Aug 13 '12 at 10:43

3 Answers3

17

I think that the reason is type conversions. Abs() function returns real results, so currency variable casts to real. Take a look at documentation:

Currency is a fixed-point data type that minimizes rounding errors in monetary calculations. On the Win32 platform, it is stored as a scaled 64-bit integer with the four last significant digits implicitly representing decimal places. When mixed with other real types in assignments and expressions, Currency values are automatically divided or multiplied by 10000.

so Currency is fixed and real is floating-point. Sample code for your question is :

program Project3;
{$APPTYPE CONSOLE}

const VALUE = 0.09;
var a,b  : currency;
begin
    a := VALUE;
    b := VALUE;

    if a = Abs(b) then writeln('equal')
    else writeln('not equal', a - Abs(b));

    readln;
end.

produces not equal result, because of type conversions;

compiler watch reveals the same value for abs(vtemp1) and vtemp2

Try to add x : real, then call x := abs(b);, add x to watches list, select it and press Edit watch, then select Floating point. X becomes 0.899...967.

not only 0.09 value produces such result. you can try this code to check:

    for i := 0 to 10000 do begin
        a := a + 0.001;
        b := a;
        if a <> abs(b) then writeln('not equal', a);
    end;

so, if you need absolute value of Currency variable - just do it. don't use floating-point abs():

    function Abs(x : Currency):Currency; inline;
    begin
        if x > 0 then result := x
        else result := -x;
    end;
teran
  • 3,214
  • 1
  • 21
  • 30
  • Thanks for the prompt replies, as you've pointed out, i used a similar workaround to avoid this problem. Although the explanation makes sense, isn't this considered a bug in Delphi IDE or in the compiler itself? – Johny Aug 13 '12 at 08:56
  • 2
    @Khatchig - formally that is not a bug but a documented behavior ("as designed"). I agree that is a bad behavior, and Embarcadero could fix it (make `Abs(Currency)` return a Currency type, not a float one). – kludg Aug 13 '12 at 12:14
6

A little clarification. The 'issue' appears if float values are compared:

var
  A: Currency;

begin
  A:= 0.09;
  Assert(A = Abs(A));
end;

That is because Abs(A) returns a float value, and A = Abs(A) is implemented as a float compare.

I could not reproduce it if Currency values are compared:

var
  A, B: Currency;
begin
  A:= 0.09;
  B:= Abs(A);
  Assert(A = B);
end;

But the second sample is also a potential bug because B:= Abs(A) internally is a float division/multiplication by 10000 with rounding to Currency (int64), and depends on FPU rounding mode.


I have created a qc report #107893, it was opened.

kludg
  • 27,213
  • 5
  • 67
  • 118
2

I have just found out the hard way that Delphi XE2 Abs function does not overload the currency type.

See the Delphi XE2 docwiki

these are the only types supported by abs

  • function Abs(X: ): Real; overload;
  • function Abs(X: ): Int64; overload;
  • function Abs(X: ): Integer; overload;
Charles Faiga
  • 11,665
  • 25
  • 102
  • 139