代码之家  ›  专栏  ›  技术社区  ›  avakar

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

  •  3
  • avakar  · 技术社区  · 16 年前

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

    上述 ntdll.dll math.h 功能( floor , ceil , log pow , ...). 然而,我有理由相信我可以实现任何不可用的 数学 如有必要,请执行以下操作。

    我已经通过规范化数字实现了转换(即,将数字乘以/除以10,直到它在间隔内) [1, 10) )然后通过使用 modf 将小数部分乘以10。这是可行的,但精度有所下降(只有前15位是正确的)。当然,精度的损失是算法固有的。

    你能推荐一个算法或者给我一个好的资源吗?

    7 回复  |  直到 16 年前
        1
  •  5
  •   C. K. Young    16 年前

    双精度数字的精度不超过15位有效(十进制)数字。绝对不可能“正确地获得任意数量的数字”; double 大人物不是大人物。

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

        2
  •  4
  •   Jonathan Graehl    16 年前

    我想了很久了。你失去了精度,因为你通过乘以10的某个幂来标准化(你选择了[1,10]而不是[0,1],但这是一个小细节)。如果你用2的幂来进行标准化,你不会失去精度,但是你会得到“十进制数”*2^e;你可以实现bcd算法并自己计算乘积,但这听起来并不有趣。

    我很有信心你能分两杯羹 g=m*2^e h=floor(g*10^k) i=modf(g*10^k) 对于一些k,然后分别转换为十进制数字,然后将它们拼接在一起,但如何使用一个更简单的方法:使用“长双”(80位,但我听说Visual C++可能不支持它)?用当前的方法,在17位后停止。

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

    至于精度的小数位数, IEEE binary64 有52个二进制数字。52*log10(2)=15.65。。。(编辑:正如您所指出的,往返需要超过16位数字)

        3
  •  3
  •   avakar    16 年前

    Printing Floating-Point Numbers Quickly and Accurately How to Print Floating-Point Numbers Accurately ,但似乎需要ACM订阅才能访问。

    由于前一篇论文是在2006年重印的,我倾向于相信它仍然是最新的。精确的理性算法(需要动态分配)似乎是一个必要的恶魔。

        5
  •  2
  •   Lior Kogan    12 年前
    #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;
    }
    
        6
  •  1
  •   Kirill V. Lyadvinsky    16 年前

    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

        7
  •  1
  •   caf    16 年前

    你看过报纸了吗 uClibc implementation 属于 printf