如何避免 ftFmtBcd 失去精度?

ven*_*eis 3 delphi precision tclientdataset bcd delphi-10-seattle

文档

TFMTBCDField 封装了二进制编码十进制 (BCD) 字段常见的基本行为。BCD 值比浮点数提供更高的精度和准确度。BCD 字段通常用于存储和操作货币值。

不幸的是,我发现将这样的字段与Extended值结合使用,我会失去精度(在本例中为两位数):如果我使用

BcdField.AsExtended := Value;
Run Code Online (Sandbox Code Playgroud)

该值实际上被截断为四位数字。对此我能做什么?

完整示例:

procedure TForm1.Button1Click(Sender: TObject);
var
  LValue: Double;
  LDataset: TClientDataSet;
  LFieldDef: TFieldDef;
begin
  LValue := 1 / 3;
  LDataset := TClientDataSet.Create(self);
  try
    LFieldDef := LDataset.FieldDefs.AddFieldDef;
    LFieldDef.DataType := ftFMTBcd;
    LFieldDef.Size := 6;
    LFieldDef.Precision := 10;
    LFieldDef.Name := 'A';
    LDataset.CreateDataset;
    LDataset.Append;
    LDataset.FieldByName('A').AsExtended := LValue;
    LDataset.Post;
    ShowMessage(FloatToStr(LDataset.FieldByName('A').AsExtended));
    ShowMessage(FloatToStr(LValue));
  finally
    FreeAndNil(LDataset);
  end;
end;
Run Code Online (Sandbox Code Playgroud)

输出(在消息框中):

0,3333
0,333333333333333
Run Code Online (Sandbox Code Playgroud)

Dis*_*ned 5

TField.AsBCD严格来说,使用返回记录会更准确TBcdTFmtBcdField覆盖默认实现并返回准确的 TBcd记录。

TBcd是一种记录结构,支持简单的算术运算符以及从Integer和的隐式转换Double。所以它应该适合大多数用途。

缺点是:

  • 由于缺乏内置机器指令,数学运算可能会稍微慢一些。但对于需要精确表示的情况来说,这可能是一个合理的权衡。根据需要进行基准测试和评估。
  • 一些采用DoubleInteger参数的函数可能需要TBcd重载实现。

一些相关注意事项:

  • Double如果由于浮点表示的本质而需要精确的精度,则使用是不合适的。请参阅浮点数学是否损坏?了解更多信息。
  • 尽管额外的 2 个字节提供了更大的范围和更高的精度,但它的使用也Extended存在同样的问题-它仍然是浮点数据类型。此外,还有其自身的问题。请注意此处的警告。DoubleExtended
  • 如果您不需要精确的表示,那么您可以转换为Double使用BcdToDouble. 请参阅BCD 支持例程
  • 在不需要超过 4 位小数但确实需要精确表示的情况下,可以考虑另一个选项:使用Currency数据类型。它表示为 64 位整数,假定除以 10000,这就是它支持 4 位十进制数字的方式。