如何在双精度双精度和十进制字符串之间转换? [英] How can I convert between a double-double and a decimal string?

查看:153
本文介绍了如何在双精度双精度和十进制字符串之间转换?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

一种将精度提高到双精度以上的方法(例如,如果我的应用程序正在做一些空间相关的事情,需要在许多光年的距离上表示准确的位置),则是使用双精度双精度结构,该结构由两个双打,代表两个的总和。对于这种结构上的各种算术运算,例如算法是已知的。 double-double,double-double,double××double-double等,例如如本文中所述。

One way of increasing precision beyond that of a double (e.g. if my application is doing something space-related that needs to represent accurate positions over distances of many light-years) is to use a double-double, a structure composed of two doubles which represents the sum of the two. Algorithms are known for the various arithmetic operations on such a structure, e.g. double-double + double-double, double × double-double, etc, e.g. as given in this paper.

(请注意,此格式与IEEE754-2008 binary128的格式不同,又称为四精度,不能保证往返于double-double和binary128的转换。)

(Note that this is not the same format as the IEEE 754-2008 binary128, a.k.a. quad-precision and conversion to/from double-double and binary128 is not guaranteed to round-trip.)

一个很明显的表示数量的方法是使用字符串来表示双精度数的每个单独成分,例如 1.0 + 1.0e-200。我的问题是,有没有一种已知的方法可以将表示该值的字符串转换为一个小数?即给定字符串 0.3,则提供最接近此表示形式的double-double,或者提供相反的方向。一种简单的方法是将连续的乘法/除数乘以10,但这对于双精度数是不够的,因此我有些怀疑它们是否可以在这里工作。

An obvious way to represent such a quantity as a string would then be to use strings representing each individual component of the double, e.g. "1.0+1.0e-200". My question is, is there a known way to convert to and from strings that represent the value as a single decimal? I.e. given the string "0.3" then provide the double-double closest to this representation, or go in the reverse direction. One naïve way would be to use successive multiplications/divisions by 10, but that is insufficient for doubles so I'm somewhat sceptical that they would work here.

推荐答案

像这样对2个浮点变量求和的技术可以有效地使尾数位宽加倍,因此足以存储/加载更大的尾数。

such technique as summing 2 floating point variables just effectively doubles the mantissa bitwidth so its enough to just store/load bigger mantissa.

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

Standard IEEE 754 double has 52+1 bit mantissa leading to

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

所以当您添加2个这样的变量时:

so when you add 2 such variables then:

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

所以只需存储/加载32位尾数到/从字符串。这两个变量的指数相差+/- 53,因此足以存储其中一个变量。

so just store/load 32 digit mantissa to/from string. The exponent of the 2 variables will differ by +/- 53 so its enough to store just one of them.

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

To further improve performance and precision you can use hex strings. Its much faster and there is no rounding as you can directly convert between the mantissa bits and hex string characters.

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

any 4 bits form a single hexadecimal digit so

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

如您所见,它的存储效率也更高,唯一的问题是指数分隔符,因为六位数字包含 E 您需要按上/下大写字母区分数字和指数分隔符,或者使用其他字符或仅使用例如符号:

As you can see its also more storage efficient the only problem is the exponent delimiter as hexa digits contain E so you need to distinct the digit and exponent separator by upper/lower casing or use different character or use just sign for example:

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

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

I usually use the first version. Also you need to take in mind the exponent is bit shift of mantissa so resulting number is:

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

现在在从字符串加载/存储到字符串期间,您只需加载 53 + 53 位整数,然后将其分隔为2个尾数,并在位级重构浮点值...重要的是,尾数必须对齐,以便 exp1 + 53 = exp2 给予或接受 1 ...

Now during loading/storing from/to string you just load 53+53 bit integer number then separate it to 2 mantissas and reconstruct the floating point values at bit level ... Its important that your mantissas are aligned so exp1+53 = exp2 give or take 1 ...

这一切都可以完成

如果您的指数是exp10,则在存储和从/加载到字符串或从字符串加载时,都会对数字进行大数舍入。您的尾数通常会在小数点之前或之后丢失许多零位,从而使十进制和二进制/十六进制之间的转换非常困难且不准确(特别是如果将计算限制为 64/80/128/160位尾数。

If your exponent is exp10 then you will inflict heavy rounding on the number during both storage and loading to/from string as your mantissa will usually missing many zero bits before or after the decimal point making transformation between decadic and binary/hexadecimal very hard and inaccurate (especially if you limit your computation just to 64/80/128/160 bits of mantissa).

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

Here an C++ example of just that (printing 32bit float in decadic on integer arithmetics only):

//---------------------------------------------------------------------------
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;
    }
//---------------------------------------------------------------------------

只需将 AnsiString 返回类型更改为任何字符串类型或 char * 您可以随意使用...

Just change the AnsiString return type into any string type or char* you got at your disposal ...

您可以看到它的很多代码,很多hack,内部超过了24bit的尾数用于降低由十进制指数引起的舍入误差。

As you can see its a lot of code with a lot of hacks and internally a lot more than 24bit of mantissa is used to lower the rounding errors inflicted by decadic exponent.

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

So I strongly advice to use binary exponent (exp2) and hexa digits for mantissa it will simplify your problem a lot and get rid of the rounding entirely. The only problem is when you want print or input decadic number in such case you have no choice but to round ... Luckily you can use hexa output and convert it to decadic on strings... Or construct the print from single variable prints ...

有关更多信息,请参见相关的质量保证:

for more info see related QAs:

  • How do I convert a very long binary number to decimal?

这篇关于如何在双精度双精度和十进制字符串之间转换?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆