1

I have found a strange behavior of Delphi's FormatFloat function. Let me show the case study.

value to be converted : 129809.495
formatted output desired : 129,809.50

Case 1 : Converting from a string

var str: string;
str := '129809.495';  
str := FormatFloat(',0.00', StrToFloat(str));  
// output is 129,809.50 = CORRECT  

Case 2 : Converting from a double variable

var number: double;
str: string;
val := 129809.495;  
str := FormatFloat(',0.00', val);  
// output is 129,809.50 = CORRECT

Case 3 : Converting from a dataset's field

*it's too complex to write here, let me just explain*  
Basically the formatted output of FormatFloat(',0.00', Dataset.Field[0].AsFloat);
always resulted in 129,809.49 == WRONG

I've been testing this behavior using SQL Server 2008 & Firebird 1.5.
Component used are ADO Components and UniDAC components (by DevArt), and all have the same behavior.

I've tried to do these :

  • FormatFloat(',0.00', Dataset.Field[0].AsFloat);
  • FormatFloat(',0.00', StrToFloat(Dataset.Field[0].AsString));
  • val := Dataset.Field[0].AsFloat; FormatFloat(',0.00', val);
  • str := Dataset.Field[0].AsString; FormatFloat(',0.00', StrToFloat(str));
  • val := StrToFloat(Dataset.Field[0].AsString); FormatFloat(',0.00', val);

But all resulted in same wrong conversion .49 instead of .50

1 way that works is

val := StrToFloat(Dataset.Field[0].AsString); 
FormatFloat(',0.00', val);  

Has anyone got a solution for this behavior? Because it'd be too much work to force convert StrToFloat and then reformat the variable/output. And this workaround can't be applied to 3rd party components that were using FormatFloat

Any help appreciated. Thanks

saintfalcon
  • 107
  • 2
  • 15

2 Answers2

1

It looks like it is related to some precision differences between Double and Extended. Actually I cannot confirm your observation about case 2 being correct. Perhaps because you declare a variable number as double, but then use a variable val of unknown type.

Anyway, the following code

var
  d: Double;
  e: Extended;
begin
  d := 129809.495;
  e := 129809.495;
  Writeln(FormatFloat(',0.00', d));
  Writeln(FormatFloat(',0.00', e));
  e := d;
  Writeln(FormatFloat(',0.00', e));
  e := 129809.495;
  d := e;
  Writeln(FormatFloat(',0.00', d));
end;

compiled with XE6 produces the following output:

129,809.49
129,809.50
129,809.49
129,809.49

This leads to the conclusion that a double is not able to hold the correct value to be rounded as expected, while an extended is more suitable. In addition, when the value is once stored in double format, simply converting it to extended (that is what happens with FormatFloat) doesn't heal the rounding error.

Uwe Raabe
  • 45,288
  • 3
  • 82
  • 130
  • Wow, now I got it. But how to convert the Dataset.Field.AsFloat to extended? – saintfalcon Jul 27 '14 at 03:18
  • As stated in http://docwiki.embarcadero.com/Libraries/XE2/en/Data.DB.TField.AsFloat that .AsFloat returns double :( – saintfalcon Jul 27 '14 at 03:23
  • 1
    Did some more research and found http://www.experts-exchange.com/Programming/Languages/Pascal/Delphi/Q_20539549.html therefore I need to do Field.AsCurrency to produce a correct precision rounding – saintfalcon Jul 27 '14 at 03:35
-1

Make an unit MyUnit like this one here:

unit MyUnit;

interface
uses
  System.SysUtils;

function FormatFloat(const Format: string; Value: Extended): string;

implementation

function FormatFloat(const Format: string; Value: Extended): string;
begin
  Value := StrToFloat(Value.ToString);  
  Result:=System.SysUtils.FormatFloat(',0.00', Value);
end;

end.

and insert it in the last place of the Uses clause. Any call to FormatFloat function will call your function.

By the way, this behaviour can be result of rounding or insufficient accuracy. I tried this:

var
  val: Single;

begin
  val:=129809.495;
  ShowMessage(FormatFloat(',0.00', val));

with my function and the rseult was val 129809.49 because the variable passed to the function was 129809.4921875 after conversion from Single to Extended. So for debugging writing to a log file of every function call may be good idea.

LHristov
  • 1,103
  • 7
  • 16
  • 3
    Using existing function names is a very bad practice. Also, you missed to use the `Format` parameter... – TLama Jul 26 '14 at 06:30
  • Not mixed but overriten. And that's the fastest and easiest solution I've found. And if the user wants, he may overwrite the function with the Format parameter. – LHristov Jul 26 '14 at 06:33
  • You would never call it fastest and easiest considering what can happen with such function name (I know that you addressed the code with those superlatives). You might better go with a class helper for `TField` if you really want to workaround the OP's situation. – TLama Jul 26 '14 at 06:51
  • Thanks for answering, but I'm afraid I can't use this with DevExpress' TcxCurrencyEdit component with their display format setting. I'll try it first – saintfalcon Jul 26 '14 at 18:19