如何避免使用 ftFmtBcd 丢失精度?
How can I avoid losing precision with ftFmtBcd?
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
是一个记录结构,支持简单的算术运算符和从 Integer
和 Double
的隐式转换。所以它应该适合大多数用途。
缺点是:
- 由于缺少内置机器指令,数学运算可能会稍慢一些。但对于需要精确表示的情况,这可能是一个合理的权衡。 根据需要进行基准测试和评估。
- 某些采用
Double
或 Integer
参数的函数可能需要 TBcd
重载实现。
一些相关的考虑:
- 如果由于自然浮点表示而需要准确的精度,则不适合使用
Double
。有关详细信息,请参阅 Is floating point math broken?。
- 使用
Extended
与 Double
存在相同的问题,尽管额外的 2 个字节提供了更大的范围和更高的精度 - 它仍然是浮点数据类型。此外,Extended
也有其自身的问题。注意警告 here.
- 如果您不需要精确表示,那么您可以使用
BcdToDouble
转换为Double
。参见 BCD Support Routines。
- 在不需要超过 4 位小数但确实需要精确表示的情况下要考虑的另一种选择:使用
Currency
数据类型。它表示为一个 64 位整数,假定除以 10000,这就是它支持 4 个十进制数字的方式。
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
是一个记录结构,支持简单的算术运算符和从 Integer
和 Double
的隐式转换。所以它应该适合大多数用途。
缺点是:
- 由于缺少内置机器指令,数学运算可能会稍慢一些。但对于需要精确表示的情况,这可能是一个合理的权衡。 根据需要进行基准测试和评估。
- 某些采用
Double
或Integer
参数的函数可能需要TBcd
重载实现。
一些相关的考虑:
- 如果由于自然浮点表示而需要准确的精度,则不适合使用
Double
。有关详细信息,请参阅 Is floating point math broken?。 - 使用
Extended
与Double
存在相同的问题,尽管额外的 2 个字节提供了更大的范围和更高的精度 - 它仍然是浮点数据类型。此外,Extended
也有其自身的问题。注意警告 here. - 如果您不需要精确表示,那么您可以使用
BcdToDouble
转换为Double
。参见 BCD Support Routines。 - 在不需要超过 4 位小数但确实需要精确表示的情况下要考虑的另一种选择:使用
Currency
数据类型。它表示为一个 64 位整数,假定除以 10000,这就是它支持 4 个十进制数字的方式。