Ian*_*oyd 8 delphi algorithm floating-point extended-precision
如何将扩展精度浮点值转换为字符串?
Intel CPU支持三种浮点格式:
Delphi本身支持Extended精度浮点格式.
扩展精度分为:
0.或开头1.)您可以将Extended的尾数大小与其他float类型的尾数进行比较:
| Type | Sign | Exponent | Integer | Mantissa |
|----------|-------|----------|---------|----------|
| Single | 1 bit | 8 bits | n/a | 23 bits |
| Double | 1 bit | 11 bits | n/a | 52 bits |
| Extended | 1 bit | 15 bits | 1 bit | 63 bits |
Run Code Online (Sandbox Code Playgroud)
扩展能够实现单倍和双倍的更高精度.
例如,取实数.49999999999999999,它以二进制表示:
Single: 0.1000000000000000000000000
Double: 0.10000000000000000000000000000000000000000000000000000
Extended: 0.01111111111111111111111111111111111111111111111111111111010001111
Run Code Online (Sandbox Code Playgroud)
你会看到,当Single和Double被强制舍入到0.1 binary(0.5十进制)时,扩展仍然具有一定的精度.
如果我尝试将扩展值0.49999999999999998转换为字符串:
FloatToStr(v);
Run Code Online (Sandbox Code Playgroud)
函数返回0.5,当我在Extended中看到它并且看到它不是 0.5时:
0x3FFDFFFFFFFFFFFFFD1E
Run Code Online (Sandbox Code Playgroud)
其他扩展值也是如此; Delphi中的所有函数(我都能找到)都返回0.5:
Value Hex representation FloatToSTr
0.499999999999999980 0x3FFDFFFFFFFFFFFFFD1E '0.5'
0.499999999999999981 0x3FFDFFFFFFFFFFFFFD43 '0.5'
0.499999999999999982 0x3FFDFFFFFFFFFFFFFD68 '0.5'
0.499999999999999983 0x3FFDFFFFFFFFFFFFFD8D '0.5'
0.499999999999999984 0x3FFDFFFFFFFFFFFFFDB2 '0.5'
0.499999999999999985 0x3FFDFFFFFFFFFFFFFDD7 '0.5'
0.499999999999999986 0x3FFDFFFFFFFFFFFFFDFB '0.5'
0.499999999999999987 0x3FFDFFFFFFFFFFFFFE20 '0.5'
0.499999999999999988 0x3FFDFFFFFFFFFFFFFE45 '0.5'
0.499999999999999989 0x3FFDFFFFFFFFFFFFFE6A '0.5'
0.499999999999999990 0x3FFDFFFFFFFFFFFFFE8F '0.5'
... ...
0.49999999999999999995 0x3FFDFFFFFFFFFFFFFFFF '0.5'
Run Code Online (Sandbox Code Playgroud)
FloatToStr和 FloatToStrF都是围绕这两个包装 FloatToText.
FloatToText最终使用FloatToDecimal从扩展中提取包含float片段的记录:
TFloatRec = packed record
Exponent: Smallint;
Negative: Boolean;
Digits: array[0..20] of Byte;
end;
Run Code Online (Sandbox Code Playgroud)
就我而言:
var
v: Extended;
fr: TFloatRec;
begin
v := 0.499999999999999980;
FloatToDecimal({var}fr, v, fvExtended, 18, 9999);
end;
Run Code Online (Sandbox Code Playgroud)
解码后的浮点数返回:
的Digits是在ASCII字符数组:
0False'5'扩展精度浮点数的63位尾数的精度可以降至:
1 / (2^63)
= 1.08420217248550443400745280086994171142578125 × 10^-19
= 0.000000000000000000108420217248550443400745280086994171142578125
\_________________/
|
19 digits
Run Code Online (Sandbox Code Playgroud)
问题是:
对于文档:
对于Extended类型的值,Precision参数指定结果中请求的有效位数 - 允许的范围是1..18.
Decimals参数指定结果中小数点左侧所请求的最大位数.
Precision和Decimals一起控制结果的舍入方式.要生成始终具有给定数量的有效数字的结果而不管数字的大小,请为Decimals参数指定9999.
转换结果存储在指定的TFloatRec记录中,如下所示:数字 - 包含最多18个(对于类型扩展)或19(对于类型货币)有效数字后跟一个空终止符.隐含小数点(如果有)不存储在数字中.
所以我遇到了内置浮点格式化函数的基本限制
如果德尔福不能自己做,问题就变成:我该怎么做?
我知道Extended是10个字节(SizeOf(Extended) = 10).现在的问题是深入研究将IEEE浮点数转换为字符串的黑暗艺术.
有些部分很简单:
function ExtendedToDecimal(v: Extended): TFloatRec;
var
n: UInt64;
const
BIAS = 16383;
begin
Result := Default(TFloatRec);
Result.Negative := v.Sign;
Result.Exponent := v.Exponent;
n := v.Mantissa;
// Result.Digits :=
end;
Run Code Online (Sandbox Code Playgroud)
但困难的部分留下作为答案的练习.
如何将扩展精度浮点值转换为字符串?
由于Delphi RTL没有任何正确和完整FloatToStr()功能的实现Extended(并且Double就此而言),因此需要使用外部库,该库位于此处,最初位于EDN,Codecentral.
该库由John Herbster创建,John Herbster是Delphi RTL库的长期撰稿人,特别是关于浮点处理.GitHub源代码已更新为使用UniCode字符串处理和TFormatSettings格式化结构.该库包含了ExactFloatToStr()一个处理的浮动功能Extended,Double和Single类型.
Program TestExactFloatToStr;
{$APPTYPE CONSOLE}
Uses
SysUtils,ExactFloatToStr_JH0;
begin
WriteLn(ExactFloatToStr(Extended(0.49999999999999999)));
WriteLn(ExactFloatToStr(Double(0.49999999999999999)));
WriteLn(ExactFloatToStr(Single(0.49999999999999999)));
ReadLn;
end.
Run Code Online (Sandbox Code Playgroud)
输出:
Run Code Online (Sandbox Code Playgroud)0.49999999999999998999823495882122159628124791197478771209716796875 0.5 0.5