1

我发现 Delphi 的 FormatFloat 函数有一个奇怪的行为。让我展示一下案例研究。

要转换的值:129809.495
所需的格式化输出:129,809.50

案例1:从字符串转换

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

案例 2:从双变量转换

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

案例 3:从数据集的字段转换

*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

我一直在使用 SQL Server 2008 和 Firebird 1.5 测试这种行为。
使用的组件是 ADO 组件和 UniDAC 组件(由 DevArt 提供),并且都具有相同的行为。

我试过做这些:

  • 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);

但都导致相同的错误转换 0.49 而不是 0.50

一种有效的方法是

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

有没有人为这种行为找到解决方案?因为强制转换 StrToFloat 然后重新格式化变量/输出的工作量太大。并且此解决方法不能应用于使用 FormatFloat 的第 3 方组件

任何帮助表示赞赏。谢谢

4

2 回答 2

1

看起来它与 Double 和 Extended 之间的一些精度差异有关。实际上,我无法确认您对案例 2 的观察是否正确。也许是因为您将变量 number 声明为 double,但随后使用了未知类型的变量 val。

无论如何,下面的代码

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;

用 XE6 编译产生以下输出:

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.

于 2014-07-26T20:28:40.587 回答
-1

在这里创建一个像这样的单元 MyUnit:

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.

并将其插入Uses 子句的最后一个位置。对 FormatFloat 函数的任何调用都会调用您的函数。

顺便说一句,这种行为可能是四舍五入或精度不足的结果。我试过这个:

var
  val: Single;

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

使用我的函数,结果是 val 129809.49,因为从 Single 转换为 Extended 后传递给函数的变量是 129809.4921875。因此,对于调试写入每个函数调用的日志文件可能是个好主意。

于 2014-07-26T06:11:39.373 回答