Как я могу преобразовать между двойной, двойной и десятичной строкой? - PullRequest
2 голосов
/ 22 января 2020

Один из способов повышения точности сверх двойного (например, если мое приложение делает что-то, связанное с пространством, которое должно представлять точные позиции на расстояниях многих световых лет), это использовать двойную двойную структуру, состоящую из два двойных, которые представляют сумму двух. Известны алгоритмы для различных арифметических операций c на такой структуре, например, двойной-двойной + двойной-двойной, двойной × двойной-двойной и т. Д. c, например, как указано в в этой статье .

(Обратите внимание, что это не тот же формат, что и двоичный128 IEEE 754-2008, он же с четверной точностью, и преобразование в / из двойного-двойного и двоичного128 не гарантируется для приема-передачи.)

Очевидным способом представления такой величины в виде строки будет использование строк, представляющих каждый отдельный компонент двойника, например «1.0 + 1.0e-200». У меня вопрос, есть ли известный способ преобразования в и из строк, которые представляют значение в виде одного десятичного числа? Т.е. с учетом строки «0.3» затем дабл-дабл, ближайший к этому представлению, или go в обратном направлении. Один наивный способ - использовать последовательные умножения / деления на 10, но этого недостаточно для двойных, поэтому я несколько скептически отношусь к тому, что они сработают здесь.

1 Ответ

1 голос
/ 22 января 2020

такой метод, как суммирование 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 di git мантиссу в / из строки. Экспонента двух переменных будет отличаться на +/- 53, поэтому достаточно сохранить только одну из них.

Для дальнейшего повышения производительности и точности вы можете использовать шестнадцатеричные строки. Это намного быстрее, и здесь нет округления, поскольку вы можете напрямую преобразовывать биты мантиссы в шестнадцатеричные символы.

любые 4 бита образуют единое шестнадцатеричное число git, поэтому

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

As Вы также можете видеть, что он также более эффективен в хранении, единственная проблема - разделитель экспоненты, поскольку шестнадцатеричные цифры содержат E, поэтому вам нужно различать разделитель ди git и экспоненты по верхнему / нижнему регистру или использовать другой символ или использовать просто знак, например :

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

Я обычно использую первую версию. Кроме того, вы должны иметь в виду, что показатель степени - битовое смещение мантиссы, поэтому результирующее число:

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

Теперь во время загрузки / сохранения из / в строку вы просто загружаете 53+53 битное целое число, а затем разделяете его на 2 мантиссы и воссоздание значений с плавающей запятой на битовом уровне ... Важно, чтобы ваши мантиссы были выровнены так, чтобы exp1+53 = exp2 давал или принимал 1 ...

Все это можно сделать на целочисленной арифметике.

Если ваш показатель степени равен exp10, то вы будете применять сильное округление числа как во время хранения, так и при загрузке в / из строки, поскольку ваша мантисса обычно пропускает много нулевых битов до или после создания десятичной запятой преобразование между десятичным c и двоичным / шестнадцатеричным очень трудным и неточным (особенно если вы ограничиваете свои вычисления только 64/80/128/160 bits мантиссы).

Вот пример C ++ только этого ( печать 32-битного числа с плавающей запятой в декади c только на целочисленной арифметике):

//---------------------------------------------------------------------------
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 int o любой тип строки или char*, который вы получили в свое распоряжение ...

Как вы можете видеть, здесь много кода с множеством хаков и внутренне более 24-битного мантиссы, чтобы уменьшить ошибки округления, вызванные показателем decadi c.

Поэтому я настоятельно рекомендую использовать для мантиссы двоичный показатель степени (exp2) и шестнадцатеричные цифры, что значительно упростит вашу проблему и полностью избавит от округления. Единственная проблема - когда вы хотите напечатать или ввести десятичное число c, в таком случае у вас нет выбора, кроме как округлить ... К счастью, вы можете использовать вывод в шестнадцатеричном формате и преобразовать его в десятичные c в строках ... Или создать печать из одной переменной печатает ...

для получения дополнительной информации см. связанные QA:

...