我正在尝试将double转换为本机NT应用程序中的字符串,即仅依赖于ntdll.dll
的应用程序。不幸的是,ntdll的vsnprintf
版本不支持%f
等,迫使我自己实现转换。
前面提到的ntdll.dll
仅导出了math.h
个floor
个函数中的一些ceil
,log
,pow
,math.h
,... )。但是,我有理由相信,如有必要,我可以实现任何不可用的[1, 10)
函数。
在GNU的libc中有一个浮点转换的实现,但代码非常密集且难以理解(GNU缩进样式在这里没有帮助)。
我已经通过对数字进行归一化来实现转换(即将数字乘以/除以10,直到它在区间modf
中),然后通过用{{1}切割整数部分来生成每个数字并将小数部分乘以10.这有效,但精度有所下降(只有前15位数是正确的)。当然,精度的损失是算法固有的。
我满足于17位,但是能够正确生成任意位数的算法将是首选。
您能否建议一个算法或指向一个好的资源?
答案 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}}?