如何仅使用math.h将double转换为字符串?

时间:2009-09-16 17:49:21

标签: c++ c floating-point

我正在尝试将double转换为本机NT应用程序中的字符串,即仅依赖于ntdll.dll的应用程序。不幸的是,ntdll的vsnprintf版本不支持%f等,迫使我自己实现转换。

前面提到的ntdll.dll仅导出了math.hfloor个函数中的一些ceillogpowmath.h,... )。但是,我有理由相信,如有必要,我可以实现任何不可用的[1, 10)函数。

在GNU的libc中有一个浮点转换的实现,但代码非常密集且难以理解(GNU缩进样式在这里没有帮助)。

我已经通过对数字进行归一化来实现转换(即将数字乘以/除以10,直到它在区间modf中),然后通过用{{1}切割整数部分来生成每个数字并将小数部分乘以10.这有效,但精度有所下降(只有前15位数是正确的)。当然,精度的损失是算法固有的。

我满足于17位,但是能够正确生成任意位数的算法将是首选。

您能否建议一个算法或指向一个好的资源?

7 个答案:

答案 0 :(得分:5)

双精度数字的精度(十进制)数不超过15。你绝对没有办法得到“正确的任意数字”; double不是bignums。

既然你说你对17个重要数字感到满意,那就用long double;我认为,在Windows上,这将为您提供19个重要数字。

答案 1 :(得分:4)

我已经考虑过这个了。你失去了精度,因为你通过乘以10的幂(你选择[1,10]而不是[0,1)来标准化,但这是一个小细节)。如果你用2的幂来做,那你就没有精确度,但是你会得到“十进制数字”* 2 ^ e;你可以实现bcd算术并自己计算产品,但这听起来并不好玩。

我非常有信心你可以将双g=m*2^e分成两部分:h=floor(g*10^k)i=modf(g*10^k)部分k,然后分别转换为十进制数字,然后将它们拼接在一起,但是更简单的方法怎么样:使用“long double”(80位,但我听说Visual C ++可能不支持它?)使用您当前的方法并在17位数后停止。

_gcvt应该这样做(编辑 - 它不在ntdll.dll中,它在某些msvcrt * .dll中?)

对于精度的十进制数字,IEEE binary64有52个二进制数字。 52 * log10(2)= 15.65 ...(编辑:正如你所指出的那样,往返旅程需要16位以上)

答案 2 :(得分:3)

经过大量研究,我发现了一篇名为Printing Floating-Point Numbers Quickly and Accurately的论文。它使用精确的有理算法来避免精度损失。它引用了一篇较旧的论文:How to Print Floating-Point Numbers Accurately,但似乎需要ACM订阅才能访问。

自从前一篇论文于2006年再版以来,我倾向于认为它仍然是最新的。确切的理性算术(需要动态分配)似乎是一种必要的恶魔。

答案 3 :(得分:2)

#include <cstdint>

// --------------------------------------------------------------------------
// Return number of decimal-digits of a given unsigned-integer
// N is unit8_t/uint16_t/uint32_t/uint64_t
template <class N> inline uint8_t GetUnsignedDecDigits(const N n)
{
    static_assert(std::numeric_limits<N>::is_integer && !std::numeric_limits<N>::is_signed,
                  "GetUnsignedDecDigits: unsigned integer type expected"                   );

    const uint8_t anMaxDigits[]= {3, 5, 8, 10, 13, 15, 17, 20};
    const uint8_t nMaxDigits   = anMaxDigits[sizeof(N)-1];

    uint8_t nDigits=  1;
    N       nRoof  = 10;

    while ((n >= nRoof) && (nDigits<nMaxDigits))
    {
        nDigits++;
        nRoof*= 10;
    }

    return nDigits;
}

// --------------------------------------------------------------------------
// Convert floating-point value to NULL-terminated string represention
TCHAR* DoubleToStr(double f       ,  // [i  ]
                   TCHAR* pczStr  ,  // [i/o] caller should allocate enough space
                   int    nDigitsI,  // [i  ] digits of integer    part including sign / <1: auto
                   int    nDigitsF ) // [i  ] digits of fractional part                / <0: auto
{
    switch (_fpclass(f))
    {
        case _FPCLASS_SNAN:
        case _FPCLASS_QNAN: _tcscpy_s(pczStr, 5, _T("NaN" )); return pczStr;
        case _FPCLASS_NINF: _tcscpy_s(pczStr, 5, _T("-INF")); return pczStr;
        case _FPCLASS_PINF: _tcscpy_s(pczStr, 5, _T("+INF")); return pczStr;
    }

    if (nDigitsI> 18) nDigitsI= 18;  if (nDigitsI< 1) nDigitsI= -1;
    if (nDigitsF> 18) nDigitsF= 18;  if (nDigitsF< 0) nDigitsF= -1;

    bool bNeg= (f<0);
    if (f<0)
        f= -f;

    int nE= 0;                                  // exponent (displayed if != 0)

    if ( ((-1 == nDigitsI) && (f >= 1e18              )) ||   // large value: switch to scientific representation
         ((-1 != nDigitsI) && (f >= pow(10., nDigitsI)))    )
    {
       nE= (int)log10(f);
       f/= (double)pow(10., nE);

       if (-1 != nDigitsF)
           nDigitsF= __max(nDigitsF, nDigitsI+nDigitsF-(bNeg?2:1)-4);

       nDigitsI= (bNeg?2:1);
    }
    else if (f>0)
    if ((-1 == nDigitsF) && (f <= 1e-10))       // small value: switch to scientific representation
    {
        nE= (int)log10(f)-1;
        f/= (double)pow(10., nE);

       if (-1 != nDigitsF)
           nDigitsF= __max(nDigitsF, nDigitsI+nDigitsF-(bNeg?2:1)-4);

        nDigitsI= (bNeg?2:1);
    }

    double fI;
    double fF= modf(f, &fI);                    // fI: integer part, fF: fractional part

    if (-1 == nDigitsF)                         // figure out number of meaningfull digits in fF
    {
        double fG, fGI, fGF;
        do
        {
            nDigitsF++;
            fG = fF*pow(10., nDigitsF);
            fGF= modf(fG, &fGI);
        }
        while (fGF > 1e-10);
    }

    const double afPower10[20]= {1e0 , 1e1 , 1e2 , 1e3 , 1e4 , 1e5 , 1e6 , 1e7 , 1e8 , 1e9 , 
                                 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19 };

    uint64_t uI= (uint64_t)round(fI                    );
    uint64_t uF= (uint64_t)round(fF*afPower10[nDigitsF]);

    if (uF)
        if (GetUnsignedDecDigits(uF) > nDigitsF)    // X.99999 was rounded to X+1
        {
            uF= 0;
            uI++;

            if (nE)
            {
                uI/= 10;
                nE++;
            }
        }

    uint8_t nRealDigitsI= GetUnsignedDecDigits(uI);
    if (bNeg)
        nRealDigitsI++;

    int nPads= 0;

    if (-1 != nDigitsI)
    {
        nPads= nDigitsI-nRealDigitsI;

        for (int i= nPads-1; i>=0; i--)         // leading spaces
            pczStr[i]= _T(' ');
    }

    if (bNeg)                                   // minus sign
    {
        pczStr[nPads]= _T('-');
        nRealDigitsI--;
        nPads++;
    }

    for (int j= nRealDigitsI-1; j>=0; j--)      // digits of integer    part
    {
        pczStr[nPads+j]= (uint8_t)(uI%10) + _T('0');
        uI /= 10;
    }

    nPads+= nRealDigitsI;

    if (nDigitsF)
    {
        pczStr[nPads++]= _T('.');               // decimal point
        for (int k= nDigitsF-1; k>=0; k--)      // digits of fractional part
        {
            pczStr[nPads+k]= (uint8_t)(uF%10)+ _T('0');
            uF /= 10;
        }
    }

    nPads+= nDigitsF;

    if (nE)
    {
        pczStr[nPads++]= _T('e');               // exponent sign

        if (nE<0)
        {
            pczStr[nPads++]= _T('-');
            nE= -nE;
        }
        else
            pczStr[nPads++]= _T('+');

        for (int l= 2; l>=0; l--)               // digits of exponent
        {
            pczStr[nPads+l]= (uint8_t)(nE%10) + _T('0');
            nE /= 10;
        }

        pczStr[nPads+3]= 0;
    }
    else
        pczStr[nPads]= 0;

    return pczStr;
}

答案 4 :(得分:2)

完全实现最快的已知(截至今日)算法的C代码: http://code.google.com/p/double-conversion/downloads/list

它甚至包括一个测试套件。

这是本PDF中描述的算法背后的C代码: 快速准确地打印浮点数 http://www.cs.indiana.edu/~burger/FP-Printing-PLDI96.pdf

答案 5 :(得分:1)

vsnprintf是否支持I64?

double x = SOME_VAL; // allowed to be from -1.e18 to 1.e18
bool sign = (SOME_VAL < 0);
if ( sign ) x = -x;
__int64 i = static_cast<__int64>( x );
double xm = x - static_cast<double>( i );
__int64 w = static_cast<__int64>( xm*pow(10.0, DIGITS_VAL) ); // DIGITS_VAL indicates how many digits after the decimal point you want to get

char out[100];
vsnprintf( out, sizeof out, "%s%I64.%I64", (sign?"-":""), i, w );

另一种选择是尝试to find implementation of gcvt

答案 6 :(得分:1)

您是否查看了printf的{​​{3}}?