【问题标题】:Convert Extended (80-bit) to string将扩展(80 位)转换为字符串
【发布时间】:2018-12-01 04:57:18
【问题描述】:

如何将扩展精度浮点值转换为字符串?

背景

Intel CPU 支持三种浮点格式:

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 具有比单精度和双精度更高的精度。

例如取实数.49999999999999999,用二进制表示:

Single:   0.1000000000000000000000000
Double:   0.10000000000000000000000000000000000000000000000000000
Extended: 0.01111111111111111111111111111111111111111111111111111111010001111

您会看到,虽然 SingleDouble 已强制舍入为 0.1 binary(十进制 0.5),但 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'

什么功能?

FloatToStrFloatToStrF 都是 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

问题是:

  • 扩展可以为您提供有意义的值,直到第 19 位
  • FloatToDecimal 虽然返回最多 20 位,但仅接受并生成最大为 18 位的扩展值请求(货币为 19 位)

对于文档:

对于 Extended 类型的值,Precision 参数指定结果中请求的有效位数——允许的范围是 1..18。
Decimals 参数指定结果中小数点左侧所请求的最大位数。
精度和小数共同控制结果的四舍五入方式。要生成始终具有给定有效数字位数的结果,而不管数字的大小,请为 Decimals 参数指定 9999。
转换结果存储在指定的TFloatRec记录中,如下:

Digits - 最多包含 18 个(对于扩展类型)或 19 个(对于货币类型)有效数字,后跟一个空终止符。隐含的小数点(如果有)不存储在 Digits 中。

所以我遇到了内置浮点格式化函数的基本限制

如何格式化 80 位 IEEE 扩展精度浮点数?

如果Delphi自己做不到,问题就变成了:怎么做?

我知道扩展是 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;

但最难的部分留作答题练习。

奖励截图

【问题讨论】:

  • 请注意,Extended 仅在 Windows 32 位上为 10 个字节。在 Windows 64 位和 iOS 设备上,ExtendedDouble 的别名,在 OSX、iOS 模拟器和 Linux 上,Extended 是 16 个字节。所以Extended 的内部布局会根据平台而变化。使用TExtendedHelperTExtended80Rec 帮助您跨多个平台使用Extended 的组件字段。
  • 你看过 John Herbsters ExactFloatToStr(x:Extended)
  • Delphi 的 FloatToStr 甚至无法正确地将双精度数转换为字符串.....
  • ExactFloatToStr(Extended(0.49999999999999999)) 给出:0.49999999999999998999823495882122159628124791197478771209716796875 使用上面的链接库。
  • @LURD 我之前没找到John's code;它工作得很好。把它作为一个答案,你就得到了一个接受。而且因为它会一直运行到没有多余的东西要添加,所以你也可以用它来打印SingleDouble以及Extended

标签: algorithm delphi floating-point extended-precision


【解决方案1】:

如何将扩展精度浮点值转换为字符串?

由于 Delphi RTL 没有为Extended(以及Double)提供任何正确且完整的FloatToStr() 函数实现,因此需要使用外部库,找到here,最初在EDN, Codecentral

该库由 John Herbster 创建,他是 Delphi RTL 库的长期贡献者,尤其是在浮点处理方面。 GitHub 源代码已更新为使用 UniCode 字符串处理和 TFormatSettings 结构进行格式化。该库包含一个 ExactFloatToStr() 函数,用于处理 ExtendedDoubleSingle 类型的浮点数。

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

【讨论】:

  • 我的Exact command line tool 也可以这样做。它为此使用了我的 BigIntegers。
  • FWIW;我认为 John Herbster 没有编写任何 Delphi RTL 例程。 RTL 中提到的 JOH 是 John O'Harrow。 John Herbster 是 TeamB 的成员。
  • @RudyVelthuis,我知道 JOH 和 JH 之间的区别。 John 贡献了“很多” QC 报告,在 borland 的 delphi 小组中非常活跃,您在您的文章中引用了他的工作。
  • @RudyVelthuis,来自 Johns CV:"2002-2005。担任 Team-B 的受邀成员,致力于帮助 Borland 编程工具的其他用户。使用浮点变量的专家,如那些由 IEEE-754 定义并在 PC 上使用的。”
  • 是的,但他没有为 Delphi 编写任何 RTL 函数。我经常和他交谈,尤其是关于这些话题。我什至在 TeamB 会议期间在 Scotts Valley 遇到了他,当时 Borland 还拥有一半的校园。
猜你喜欢
  • 1970-01-01
  • 2023-03-29
  • 2018-03-21
  • 2016-06-10
  • 1970-01-01
  • 1970-01-01
  • 2012-11-18
  • 1970-01-01
  • 2011-02-27
相关资源
最近更新 更多