Comment convertir une chaîne en chaîne en utilisant uniquement math.h?

J’essaie de convertir un double en chaîne dans une application NT native, c’est-à-dire une application qui ne dépend que de ntdll.dll . Malheureusement, la version de vsnprintf de vsnprintf ne prend pas en charge %f et autres, ce qui m’oblige à implémenter la conversion moi-même.

Le ntdll.dll susmentionné ntdll.dll que quelques-unes des fonctions math.h ( floor , ceil , log , pow , …). Cependant, je suis raisonnablement sûr de pouvoir implémenter les fonctions math.h indisponibles si nécessaire.

Il existe une implémentation de la conversion de virgule flottante dans la libc de GNU, mais le code est extrêmement dense et difficile à synthétiser (le style d’indentation de GNU n’aide pas ici).

J’ai déjà implémenté la conversion en normalisant le nombre (c’est-à-dire en multipliant / divisant le nombre par 10 jusqu’à ce qu’il se trouve dans l’intervalle [1, 10) ], puis en générant chaque chiffre en coupant l’intégrale avec modf et en multipliant la partie modf par 10. Cela fonctionne, mais il y a une perte de précision (seuls les 15 premiers chiffres sont corrects). La perte de précision est bien entendu inhérente à l’algorithme.

Je réglerais avec 17 chiffres, mais un algorithme capable de générer correctement un nombre arbitraire de chiffres serait préféré.

Pourriez-vous s’il vous plaît suggérer un algorithme ou m’indiquer une bonne ressource?

Les nombres en double précision n’ont pas plus de 15 chiffres de précision significatifs (décimaux). Vous ne pouvez absolument pas obtenir “un nombre arbitraire de chiffres correctement”; double s ne sont pas des bignums.

Puisque vous dites que vous êtes satisfait de 17 chiffres significatifs, utilisez long double ; sur Windows, je pense que cela vous donnera 19 chiffres significatifs.

J’ai pensé à cela un peu plus. Vous perdez en précision parce que vous normalisez en multipliant par une puissance de 10 (vous avez choisi [1,10) plutôt que [0,1), mais c’est un détail mineur). Si vous le faisiez avec une puissance de 2, vous ne perdriez aucune précision, mais vous obtiendrez des “chiffres décimaux” * 2 ^ e; vous pouvez implémenter l’arithmétique bcd et calculer le produit vous-même, mais cela ne semble pas amusant.

Je suis assez confiant que vous pourriez diviser le double g=m*2^e en deux parties: h=floor(g*10^k) et i=modf(g*10^k) pour un certain k, puis séparément convertissez-les en chiffres décimaux, puis associez-les ensemble, mais pourquoi ne pas adopter une approche plus simple: utilisez “long double” (80 bits, mais j’ai entendu dire que Visual C ++ peut ne pas le prendre en charge?) avec votre approche actuelle et vous arrêter après 17 chiffres.

_gcvt devrait le faire (edit – ce n’est pas dans ntdll.dll, mais dans certains msvcrt * .dll?)

En ce qui concerne les chiffres décimaux de précision, IEEE binary64 comporte 52 chiffres binarys. 52 * log10 (2) = 15,65 … (modifier: comme vous l’avez indiqué, pour un aller-retour, vous avez besoin de plus de 16 chiffres)

Après de nombreuses recherches, j’ai trouvé un article intitulé Impression rapide et précise de nombres en virgule flottante . Il utilise l’arithmétique rationnelle exacte pour éviter la perte de précision. Il cite un article un peu plus ancien: comment imprimer des nombres à virgule flottante avec précision , ce qui semble toutefois nécessiter un abonnement à ACM pour y accéder.

Depuis la réimpression de l’ancien journal en 2006, j’ai tendance à croire qu’il est toujours d’actualité. L’arithmétique rationnelle exacte (qui nécessite une allocation dynamic) semble être un mal nécessaire.

Une implémentation complète du code C pour l’algorithme le plus rapide connu à ce jour: http://code.google.com/p/double-conversion/downloads/list

Il comprend même une suite de tests.

C’est le code C à la base de l’algorithme décrit dans ce PDF: Impression rapide et précise de nombres en virgule flottante http://www.cs.indiana.edu/~burger/FP-Printing-PLDI96.pdf

 #include  // -------------------------------------------------------------------------- // Return number of decimal-digits of a given unsigned-integer // N is unit8_t/uint16_t/uint32_t/uint64_t template  inline uint8_t GetUnsignedDecDigits(const N n) { static_assert(std::numeric_limits::is_integer && !std::numeric_limits::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 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; } 

Est- vsnprintf que vsnprintf prend en charge 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( 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 ); 

Une autre option consiste à essayer de trouver la mise en œuvre de gcvt .

Avez-vous examiné la mise en œuvre de printf uClibc ?