如何将扩展精度浮点值转换为字符串?
背景
Intel CPU 支持三种浮点格式:
- 32 位单精度
- 64 位双精度
- 80 位扩展精度
Delphi 原生支持扩展精度浮点格式。
扩展精度分为:
- 1个符号位
- 15 个指数位
- 1 个整数部分位(即数字以
0.
or开头1.
) - 63 个尾数位
您可以将 Extended 的尾数大小与其他浮点类型的尾数大小进行比较:
| 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 |
Extended 具有比单双精度更高的精度。
例如,取实数.49999999999999999
,它的二进制表示:
Single: 0.1000000000000000000000000
Double: 0.10000000000000000000000000000000000000000000000000000
Extended: 0.01111111111111111111111111111111111111111111111111111111010001111
您会看到,虽然Single和Double已被强制舍入到0.1 binary
(0.5 decimal) ,但 extended 仍然具有一定的精度。
但是如何将二进制分数转换为字符串?
如果我尝试将扩展值转换0.49999999999999998
为字符串:
FloatToStr(v);
函数返回0.5
,当我可以看到扩展内部并看到它不是0.5 时:
0x3FFDFFFFFFFFFFFFFD1E
其他扩展值也是如此;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'
什么功能?
FloatToStr和FloatToStrF都是FloatToText的包装器。
FloatToText最终使用FloatToDecimal从扩展的记录中提取包含浮动片段的记录:
TFloatRec = packed record
Exponent: Smallint;
Negative: Boolean;
Digits: array[0..20] of Byte;
end;
就我而言:
var
v: Extended;
fr: TFloatRec;
begin
v := 0.499999999999999980;
FloatToDecimal({var}fr, v, fvExtended, 18, 9999);
end;
解码的浮点数返回为:
- 指数:0 (SmallInt)
- 负数:假(布尔值)
- 数字: [53, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1] (数组[0..20 ]字节)
在Digits
ascii 字符数组中:
- 指数:
0
- 消极的:
False
- 数字:
'5'
FloatToDecimal 限制为 18 位
扩展精度浮点数的 63 位尾数的精度可以下降到:
1 / (2^63)
= 1.08420217248550443400745280086994171142578125 × 10^-19
= 0.000000000000000000108420217248550443400745280086994171142578125
\_________________/
|
19 digits
问题是:
- Extended 可以为您提供最多 19 位的有意义的值
- FloatToDecimal,虽然最多返回 20 位,但只接受并生成扩展值的最大 18 位请求(货币为 19 位)
对于文档:
对于 Extended 类型的值,Precision 参数指定结果中请求的有效数字位数——允许的范围是 1..18。
Decimals 参数指定结果中小数点左侧所请求的最大位数。
精度和小数共同控制结果的四舍五入方式。要生成始终具有给定有效数字位数的结果,而不管数字的大小,请为 Decimals 参数指定 9999。
转换的结果存储在指定的 TFloatRec 记录中,如下所示:数字 - 最多包含 18 个(对于扩展类型)或 19 个(对于货币类型)有效数字,后跟一个空终止符。隐含的小数点(如果有)不存储在 Digits 中。
所以我遇到了内置浮点格式化函数的基本限制
如何格式化 80 位 IEEE 扩展精度浮点数?
如果 Delphi 不能自己做,那么问题就变成了:我该怎么做?
我知道 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;
但困难的部分留作回答的练习。