【问题标题】:Delphi FormatFloat different behavior (variable & dataset)Delphi FormatFloat 不同的行为(变量和数据集)
【发布时间】:2014-09-17 23:53:45
【问题描述】:

我发现 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);

但都导致相同的错误转换 .49 而不是 .50

一种有效的方法是

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

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

任何帮助表示赞赏。谢谢

【问题讨论】:

  • 该字段的数据类型是什么?
  • @UweRaabe 的数据类型是 Firebird 1.5 的“双精度”和 SQLServer 的“金钱”

标签: delphi ado delphi-xe5 unidac


【解决方案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

这导致了这样的结论,即 double 无法按预期保持正确的值进行四舍五入,而extended 更合适。此外,当值曾经以双精度格式存储时,简单地将其转换为扩展格式(这就是 FormatFloat 发生的情况)并不能修复舍入错误。

【讨论】:

【解决方案2】:

在这里创建一个像这样的单元 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。因此,对于调试写入每个函数调用的日志文件可能是个好主意。

【讨论】:

  • 使用现有的函数名称是一种非常糟糕的做法。另外,您错过了使用Format 参数...
  • 未混合但被覆盖。这是我找到的最快和最简单的解决方案。如果用户愿意,他可以用 Format 参数覆盖函数。
  • 考虑到这样的函数名称会发生​​什么,你永远不会称之为最快和最简单的(我知道你用这些最高级来处理代码)。如果您真的想解决 OP 的情况,最好为 TField 使用类助手。
  • 感谢您的回答,但恐怕我无法将其与 DevExpress 的 TcxCurrencyEdit 组件及其显示格式设置一起使用。我先试试
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-24
  • 2020-12-09
  • 1970-01-01
  • 1970-01-01
  • 2021-06-08
相关资源
最近更新 更多