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

时间:2017-12-12 20:46:09

标签: delphi precision delphi-10-seattle tclientdataset bcd

documentation说:

  

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

不幸的是,我发现将这样的字段与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

1 个答案:

答案 0 :(得分:2)

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

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

缺点是:

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

一些相关的考虑因素:

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