将扩展(80位)转换为字符串

时间:2018-06-21 18:49:04

标签: algorithm delphi floating-point extended-precision

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

背景

Intel CPU支持三种浮点格式:

Delphi原生支持扩展精度浮点格式。

扩展精度细分为:

  • 1个符号位
  • 15个指数位
  • 1个整数部分的位(即数字以0.1.开头)
  • 63个尾数位

您可以将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  |

Extended具有更高的精度,可以达到单精度和双精度。

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

Single:   0.1000000000000000000000000
Double:   0.10000000000000000000000000000000000000000000000000000
Extended: 0.01111111111111111111111111111111111111111111111111111111010001111

您看到,虽然 Double 被迫四舍五入为0.1 binary(十进制0.5),但扩展仍具有一定的精度。

但是如何将二进制分数转换为字符串?

如果我尝试将扩展值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

问题在于:

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

对于文档:

  

对于类型为Extended的值,Precision参数指定结果中所请求的有效数字位数-允许的范围为1..18。
  Decimals参数指定结果中小数点左侧要求的最大位数。
  精度和小数共同控制结果的取整方式。若要产生始终具有给定数量有效数字的结果,而与数字的大小无关,请为Decimals参数指定9999。
  转换结果存储在指定的TFloatRec记录中,如下所示:

     

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

所以我遇到了内置float格式化功能的基本限制

如何格式化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;

但是最困难的部分是作为答案的练习。

奖金截图

enter image description here

1 个答案:

答案 0 :(得分:6)

  

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

由于Delphi RTL没有针对'<a>'+'User name is "' + user.name + '"</a>'+ (以及FloatToStr())正确且完整的Extended函数的任何实现,因此需要使用外部库,找到here,最初位于EDN, Codecentral

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

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.