如何避免使用 ftFmtBcd 丢失精度?

How can I avoid losing precision with ftFmtBcd?

documentation 说:

TFMTBCDField encapsulates the fundamental behavior common to binary-coded decimal (BCD) fields. BCD values provide greater precision and accuracy than floating-point numbers. BCD fields are often used for storing and manipulating monetary values.

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

BcdField.AsExtended := Value;

该值实际上被截断为四位数字。我该怎么办?

完整示例:

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;

输出(在消息框中):

0,3333
0,333333333333333

严格来说用TField.AsBCD which returns a TBcd记录会更准确。 TFmtBcdField 覆盖默认实现和 returns 一个 准确 TBcd 记录。

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

缺点是:

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

一些相关的考虑:

  • 如果由于自然浮点表示而需要准确的精度,则不适合使用 Double。有关详细信息,请参阅 Is floating point math broken?
  • 使用 ExtendedDouble 存在相同的问题,尽管额外的 2 个字节提供了更大的范围和更高的精度 - 它仍然是浮点数据类型。此外,Extended 也有其自身的问题。注意警告 here.
  • 如果您不需要精确表示,那么您可以使用BcdToDouble转换为Double。参见 BCD Support Routines
  • 在不需要超过 4 位小数但确实需要精确表示的情况下要考虑的另一种选择:使用 Currency 数据类型。它表示为一个 64 位整数,假定除以 10000,这就是它支持 4 个十进制数字的方式。