如何在双精度字符串和十进制字符串之间进行转换?

How can I convert between a double-double and a decimal string?

提问人: 提问时间:1/22/2020 最后编辑:phuclv 更新时间:9/10/2020 访问量:440

问:

提高精度超过双精度的一种方法(例如,如果我的应用程序正在做一些与空间相关的事情,需要表示许多光年距离上的准确位置)是使用双精度,这是一种由两个双精度组成的结构,表示两者的总和。算法以对这种结构的各种算术运算而闻名,例如双双+双双、双×双双等,例如本文给出的。

(请注意,这与 IEEE 754-2008 二进制128 的格式不同,即四精度,并且不保证与双精度和二进制128 之间的转换是往返的。

将这样的量表示为字符串的一个明显方法是使用表示双精度的每个单独分量的字符串,例如“1.0+1.0e-200”。我的问题是,有没有一种已知的方法可以与表示值为单个小数的字符串相互转换?即给定字符串“0.3”,则提供最接近此表示的双双,或反向。一种幼稚的方法是使用连续乘法/除法 10,但这对于双精度来说是不够的,所以我有点怀疑它们在这里是否有效。

算法 序列化 浮点 双双双算

评论

0赞 rossum 1/22/2020
Java 有一个类(文档在这里),它可能很有用。java.math.BigDecimal
0赞 Kevin Wang 1/22/2020
我在堆栈溢出中发现了这个问题,在第二段中,它说选择两个常量 A 和 B,然后将你的两个双精度 U 和 V 初始化为原始数字的函数,以及 A 和 B。
0赞 aka.nice 1/22/2020
即使大部分中间位必须全部为 1 或全部为 0,最高有效位和最低有效位之间的范围也可能相当大。此范围将把打印的复杂性提高到十进制。
0赞 aka.nice 1/22/2020
因此,小数可能需要相当多的数字。出于这个原因,我会坚持使用双层印花。
0赞 Spektre 1/22/2020
为什么不使用十六进制字符串(螳螂/指数形式)?在二进制和字符串表示之间转换它们没有舍入错误,它们可以直接用于字符串的“快速”计算(因此您可以将其用作任意大小)......请参阅如何将一个很长的二进制数转换为十进制?只有十进制打印会“慢”。然而,在大多数情况下,这样做就足够了:是否有可能在尺寸和质量方面进行逼真的n体太阳系模拟?

答:

1赞 Spektre 1/22/2020 #1

将 2 个浮点变量相加这样的技术实际上使尾数位宽加倍,因此足以存储/加载更大的尾数。

标准 IEEE 754 double 具有 52+1 位尾数,导致

log10(2^53) = 15.95 = ~16 [dec digits]

因此,当您添加 2 个这样的变量时:

log10(2^(53+53)) = 31.9 = ~32 [dec digits]

因此,只需将 32 位尾数存储/加载到/从字符串中即可。2 个变量的指数将相差 +/- 53,因此仅存储其中一个就足够了。

为了进一步提高性能和精度,您可以使用十六进制字符串。它的速度要快得多,并且没有舍入,因为您可以直接在尾数位和十六进制字符串字符之间进行转换。

任何 4 位都形成一个十六进制数字,因此

(53+53) / 4 = 26.5 = ~27 [hex digits]

正如你所看到的,它的存储效率也更高,唯一的问题是指数分隔符,因为十六进制数字包含,所以你需要通过大写/小写来区分数字和指数分隔符,或者使用不同的字符或只使用符号,例如:E

1.23456789ABCDEFe10  
1.23456789ABCDEFe+10
1.23456789ABCDEF|+10
1.23456789ABCDEF+10

我通常使用第一个版本。另外,您需要记住指数是尾数的位移,因此结果数为:

mantisa<<exponent = mantisa * (2^exponent)

现在,在从/存储字符串期间,您只需加载位整数,然后将其分成 2 个尾数并在位级重建浮点值......重要的是你的尾数是对齐的,所以给予或接受......53+53exp1+53 = exp21

所有这些都可以在整数算术上完成。

如果你的指数是 exp10,那么在存储和加载到字符串/从字符串加载期间,你将对数字施加严重的四舍五入,因为你的尾数通常会在小数点之前或之后丢失许多零位,这使得十进制和二进制/十六进制之间的转换非常困难和不准确(特别是如果你将计算限制在尾数)。64/80/128/160 bits

这里有一个 C++ 示例(仅在整数算术上以十进制打印 32 位浮点):

//---------------------------------------------------------------------------
AnsiString f32_prn(float fx)    // scientific format integers only
    {
    const int ms=10+5;  // mantisa digits
    const int es=2;     // exponent digits
    const int eb=100000;// 10^(es+3)
    const int sz=ms+es+5;

    char txt[sz],c;
    int i=0,i0,i1,m,n,exp,e2,e10;
    DWORD x,y,man;
    for (i0=0;i0<sz;i0++) txt[i0]=' ';
    // float -> DWORD
    x=((DWORD*)(&fx))[0];
    // sign
    if (x>=0x80000000){ txt[i]='-'; i++; x&=0x7FFFFFFF; }
     else             { txt[i]='+'; i++; }
    // exp
    exp=((x>>23)&255)-127;
    // man
    man=x&0x007FFFFF;
    if ((exp!=-127)&&(exp!=+128)) man|=0x00800000;  // not zero or denormalized or Inf/NaN
    // special cases
    if ((man==0)&&(exp==-127)){ txt[i]='0'; i++; txt[i]=0; return txt; }    // +/- zero
    if ((man==0)&&(exp==+128)){ txt[i]='I'; i++;
                                txt[i]='N'; i++;
                                txt[i]='F'; i++; txt[i]=0; return txt; }    // +/- Infinity
    if ((man!=0)&&(exp==+128)){ txt[i]='N'; i++;
                                txt[i]='A'; i++;
                                txt[i]='N'; i++; txt[i]=0; return txt; }    // +/- Not a number
    // align man,exp to 4bit
    e2=(1+(exp&3))&3;
    man<<=e2;
    exp-=e2+23; // exp of lsb of mantisa
    e10=0;      // decimal digits to add/remove
    m=0;        // mantisa digits
    n=ms;       // max mantisa digits
    // integer part
    if (exp>=-28)
        {
        x=man; y=0; e2=exp;
        // shift x to integer part <<
        if (x) for (;e2>0;)
            {
            while (x>0x0FFFFFFF){ y/=10; y+=((x%10)<<28)/10; x/=10; e10++; }
            e2-=4; x<<=4; y<<=4;
            x+=(y>>28)&15; y&=0x0FFFFFFF;
            }
        // shift x to integer part >>
        for (;e2<0;e2+=4) x>>=4;
        // no exponent?
        if ((e10>0)&&(e10<=es+3)) n++;  // no '.'
        // print
        for (i0=i;x;)
            {
            if (m<n){ txt[i]='0'+(x%10); i++; m++; if ((m==n)&&(x<eb)) m+=es+1; } else e10++;
            x/=10;
            }
        // reverse digits
        for (i1=i-1;i0<i1;i0++,i1--){ c=txt[i0]; txt[i0]=txt[i1]; txt[i1]=c; }
        }
    // fractional part
    if (exp<0)
        {
        x=man; y=0; e2=exp;
        // shift x to fractional part <<
        if (x) for (;e2<-28;)
            {
            while ((x<=0x19999999)&&(y<=0x19999999)){ y*=10; x*=10; x+=(y>>28)&15; y&=0x0FFFFFFF; e10--; }
            y>>=4; y&=0x00FFFFFF; y|=(x&15)<<24;
            x>>=4; x&=0x0FFFFFFF; e2+=4;
            }
        // shift x to fractional part <<
        for (;e2>-28;e2-=4) x<<=4;
        // print
        x&=0x0FFFFFFF;
        if ((m)&&(!e10)) n+=es+2;   // no exponent means more digits for mantisa
        if (x)
            {
            if (m){ txt[i]='.'; i++; }
            for (i0=i;x;)
                {
                y*=10; x*=10;
                x+=(y>>28)&15;
                if (m<n)
                    {
                    i0=((x>>28)&15);
                    if (!m)
                        {
                        if (i0)
                            {
                            txt[i]='0'+i0; i++; m++;
                            txt[i]='.';    i++;
                            }
                        e10--;
                        if (!e10) n+=es+2;  // no exponent means more digits for mantisa
                        }
                    else { txt[i]='0'+i0; i++; m++; }
                    } else break;
                y&=0x0FFFFFFF;
                x&=0x0FFFFFFF;
                }
            }
        }
    else{
        // no fractional part
        if ((e10>0)&&(e10<sz-i))
         for (;e10;e10--){ txt[i]='0'+i0; i++; m++; }
        }
    // exponent
    if (e10)
        {
        if (e10>0)  // move . after first digit
            {
            for (i0=i;i0>2;i0--) txt[i0]=txt[i0-1];
            txt[2]='.'; i++; e10+=i-3;
            }
        // sign
        txt[i]='E'; i++;
        if (e10<0.0){ txt[i]='-'; i++; e10=-e10; }
         else       { txt[i]='+'; i++; }
        // print
        for (i0=i;e10;){ txt[i]='0'+(e10%10); e10/=10; i++; }
        // reverse digits
        for (i1=i-1;i0<i1;i0++,i1--){ c=txt[i0]; txt[i0]=txt[i1]; txt[i1]=c; }
        }

    txt[i]=0;
    return txt;
    }
//---------------------------------------------------------------------------

只需将返回类型更改为任何字符串类型,或者您可以使用...AnsiStringchar*

正如你所看到的,它有很多代码和很多黑客,在内部,超过24位的尾数被用来降低十进制指数造成的舍入误差。

因此,我强烈建议使用二进制指数 () 和十六位数字作为尾数,这将大大简化您的问题并完全摆脱四舍五入。唯一的问题是,当您想要打印或输入十进制数字时,在这种情况下,您别无选择,只能四舍五入......幸运的是,您可以使用十六进制输出并将其转换为字符串上的十进制......或者从单个变量打印构建打印......exp2

有关详细信息,请参阅相关 QA: