将扩展(80 位)转换为字符串
Convert Extended (80-bit) to string
如何将扩展精度浮点值转换为字符串?
背景
Intel CPU 支持三种浮点格式:
- 32 位 单精度
- 64 位 双精度精度
- 80 位 Extended precision
Delphi 原生支持扩展精度浮点格式。
扩展精度分为:
- 1 个符号位
- 15 个指数位
- 1 个整数部分位(即数字以
0.
或 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 比 single 和 double 具有更高的精度。
例如取实数.49999999999999999
,用二进制表示:
Single: 0.1000000000000000000000000
Double: 0.10000000000000000000000000000000000000000000000000000
Extended: 0.01111111111111111111111111111111111111111111111111111111010001111
你看到虽然 Single 和 Double 被迫四舍五入到 0.1 binary
(0.5 小数),但仍然扩展有一定的精度。
但是如何将二进制分数转换为字符串呢?
如果我尝试将扩展值 0.49999999999999998
转换为字符串:
FloatToStr(v);
函数 returns 0.5
,当我可以在扩展内部看到它时 不是 0.5:
0x3FFDFFFFFFFFFFFFFD1E
其他Extended值同理; Delphi 中的所有函数(我能找到)全部 return 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 and FloatToStrF are both wrappers around 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,虽然 return 最多 20 位,但只接受并生成最多 18 位的扩展值请求(19 位货币)
对于文档:
For values of type Extended, the Precision parameter specifies the requested number of significant digits in the result--the allowed range is 1..18.
The Decimals parameter specifies the requested maximum number of digits to the left of the decimal point in the result.
Precision and Decimals together control how the result is rounded. To produce a result that always has a given number of significant digits regardless of the magnitude of the number, specify 9999 for the Decimals parameter.
The result of the conversion is stored in the specified TFloatRec record as follows:
Digits - Contains up to 18 (for type Extended) or 19 (for type Currency) significant digits followed by a null terminator. The implied decimal point (if any) is not stored in Digits.
所以我遇到了内置浮点格式函数的基本限制
如何格式化 80 位 IEEE 扩展精度浮点数?
如果Delphi自己做不到,那么问题就变成了:i怎么做?
我知道扩展是 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;
但困难的部分留作答案练习。
奖金截图
How can i convert an Extended precision floating point value to a string?
由于 Delphi RTL 没有任何针对 Extended
(以及 Double
的)正确且完整的 FloatToStr()
函数的实现,因此需要要使用外部库,找到 here and originally at EDN, Codecentral。
该库由 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.
输出:
0.49999999999999998999823495882122159628124791197478771209716796875
0.5
0.5
如何将扩展精度浮点值转换为字符串?
背景
Intel CPU 支持三种浮点格式:
- 32 位 单精度
- 64 位 双精度精度
- 80 位 Extended precision
Delphi 原生支持扩展精度浮点格式。
扩展精度分为:
- 1 个符号位
- 15 个指数位
- 1 个整数部分位(即数字以
0.
或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 比 single 和 double 具有更高的精度。
例如取实数.49999999999999999
,用二进制表示:
Single: 0.1000000000000000000000000
Double: 0.10000000000000000000000000000000000000000000000000000
Extended: 0.01111111111111111111111111111111111111111111111111111111010001111
你看到虽然 Single 和 Double 被迫四舍五入到 0.1 binary
(0.5 小数),但仍然扩展有一定的精度。
但是如何将二进制分数转换为字符串呢?
如果我尝试将扩展值 0.49999999999999998
转换为字符串:
FloatToStr(v);
函数 returns 0.5
,当我可以在扩展内部看到它时 不是 0.5:
0x3FFDFFFFFFFFFFFFFD1E
其他Extended值同理; Delphi 中的所有函数(我能找到)全部 return 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 and FloatToStrF are both wrappers around 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,虽然 return 最多 20 位,但只接受并生成最多 18 位的扩展值请求(19 位货币)
对于文档:
For values of type Extended, the Precision parameter specifies the requested number of significant digits in the result--the allowed range is 1..18.
The Decimals parameter specifies the requested maximum number of digits to the left of the decimal point in the result.
Precision and Decimals together control how the result is rounded. To produce a result that always has a given number of significant digits regardless of the magnitude of the number, specify 9999 for the Decimals parameter.
The result of the conversion is stored in the specified TFloatRec record as follows:Digits - Contains up to 18 (for type Extended) or 19 (for type Currency) significant digits followed by a null terminator. The implied decimal point (if any) is not stored in Digits.
所以我遇到了内置浮点格式函数的基本限制
如何格式化 80 位 IEEE 扩展精度浮点数?
如果Delphi自己做不到,那么问题就变成了:i怎么做?
我知道扩展是 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;
但困难的部分留作答案练习。
奖金截图
How can i convert an Extended precision floating point value to a string?
由于 Delphi RTL 没有任何针对 Extended
(以及 Double
的)正确且完整的 FloatToStr()
函数的实现,因此需要要使用外部库,找到 here and originally at EDN, Codecentral。
该库由 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.
输出:
0.49999999999999998999823495882122159628124791197478771209716796875 0.5 0.5