web-dev-qa-db-fra.com

Convertir un float en chaîne

Comment puis-je convertir un entier à virgule flottante en chaîne en C/C++ sans la fonction de bibliothèque sprintf?

Je recherche une fonction, par exemple char *ftoa(float num) qui convertit num en une chaîne et la renvoie.

ftoa(3.1415) doit renvoyer "3.1415".

15
SIVA

Lorsque vous avez affaire à des nombres fp, cela peut être très complexe, mais l'algorithme est simpliste et similaire à la réponse d'Edgar Holleis; gloire! C'est complexe, car lorsque vous utilisez des nombres à virgule flottante, les calculs seront un peu décalés en fonction de la précision choisie. C'est pourquoi ce n'est pas une bonne pratique de programmation de comparer un nombre à zéro.

Mais il y a une réponse et c'est ma tentative de la mettre en œuvre. Ici, j'ai utilisé une valeur de tolérance pour que vous ne calculiez pas trop de décimales, ce qui créerait une boucle infinie. Je suis sûr qu'il existe peut-être de meilleures solutions, mais cela devrait vous aider à bien comprendre comment le faire.

char fstr[80];
float num = 2.55f;
int m = log10(num);
int digit;
float tolerance = .0001f;

while (num > 0 + precision)
{
    float weight = pow(10.0f, m);
    digit = floor(num / weight);
    num -= (digit*weight);
    *(fstr++)= '0' + digit;
    if (m == 0)
        *(fstr++) = '.';
    m--;
}
*(fstr) = '\0';
9
Sophy Pal

D'après la réponse de Sophy Pal, il s'agit d'une solution légèrement plus complète qui prend en compte le nombre zéro, NaN, l'infini, les nombres négatifs et la notation scientifique. Bien que sprintf fournisse toujours une représentation de chaîne plus précise.

/* 
   Double to ASCII Conversion without sprintf.
   Roughly equivalent to: sprintf(s, "%.14g", n);
*/

#include <math.h>
#include <string.h>
// For printf
#include <stdio.h>

static double PRECISION = 0.00000000000001;
static int MAX_NUMBER_STRING_SIZE = 32;

/**
 * Double to ASCII
 */
char * dtoa(char *s, double n) {
    // handle special cases
    if (isnan(n)) {
        strcpy(s, "nan");
    } else if (isinf(n)) {
        strcpy(s, "inf");
    } else if (n == 0.0) {
        strcpy(s, "0");
    } else {
        int digit, m, m1;
        char *c = s;
        int neg = (n < 0);
        if (neg)
            n = -n;
        // calculate magnitude
        m = log10(n);
        int useExp = (m >= 14 || (neg && m >= 9) || m <= -9);
        if (neg)
            *(c++) = '-';
        // set up for scientific notation
        if (useExp) {
            if (m < 0)
               m -= 1.0;
            n = n / pow(10.0, m);
            m1 = m;
            m = 0;
        }
        if (m < 1.0) {
            m = 0;
        }
        // convert the number
        while (n > PRECISION || m >= 0) {
            double weight = pow(10.0, m);
            if (weight > 0 && !isinf(weight)) {
                digit = floor(n / weight);
                n -= (digit * weight);
                *(c++) = '0' + digit;
            }
            if (m == 0 && n > 0)
                *(c++) = '.';
            m--;
        }
        if (useExp) {
            // convert the exponent
            int i, j;
            *(c++) = 'e';
            if (m1 > 0) {
                *(c++) = '+';
            } else {
                *(c++) = '-';
                m1 = -m1;
            }
            m = 0;
            while (m1 > 0) {
                *(c++) = '0' + m1 % 10;
                m1 /= 10;
                m++;
            }
            c -= m;
            for (i = 0, j = m-1; i<j; i++, j--) {
                // swap without temporary
                c[i] ^= c[j];
                c[j] ^= c[i];
                c[i] ^= c[j];
            }
            c += m;
        }
        *(c) = '\0';
    }
    return s;
}

int main(int argc, char** argv) {

    int i;
    char s[MAX_NUMBER_STRING_SIZE];
    double d[] = {
        0.0,
        42.0,
        1234567.89012345,
        0.000000000000018,
        555555.55555555555555555,
        -888888888888888.8888888,
        111111111111111111111111.2222222222
    };
    for (i = 0; i < 7; i++) {
        printf("%d: printf: %.14g, dtoa: %s\n", i+1, d[i], dtoa(s, d[i]));
    }
}

Les sorties:

  1. printf: 0, dtoa: 0
  2. impression: 42, dtoa: 42
  3. printf: 1234567.8901234, dtoa: 1234567.89012344996444
  4. printf: 1.8e-14, dtoa: 1.79999999999999e-14
  5. printf: 555555.55555556, dtoa: 555555.55555555550381
  6. printf: -8.8888888888889e + 14, dtoa: -8.88888888888888e + 14
  7. printf: 1.1111111111111e + 23, dtoa: 1.11111111111111e + 23
16
androider
  1. Utilisez la fonction log- pour connaître la magnitude m de votre nombre. Si la magnitude est négative, imprimez "0." et une quantité appropriée de zéros.
  2. Divisez ensuite par 10^m et convertissez le résultat en int pour obtenir les chiffres décimaux. m-- pour le chiffre suivant.
  3. Si vous avez dépassé m==0, n'oubliez pas d’imprimer le point décimal ".".
  4. Couper après quelques chiffres. Si m>0 lorsque vous arrêtez de, n'oubliez pas d'imprimer "E" et itoa(m).

Au lieu de la fonction log-, vous pouvez également extraire directement l'exposant en décalant et en corrigeant le décalage de l'exposant (voir IEEE 754). Java a une fonction double en bits pour obtenir la représentation binaire.

6
edgar.holleis
 /*
  * Program to convert float number to string without using sprintf
  */

#include "iostream"    
#include "string"    
#include "math.h"

# define PRECISION 5

using namespace std;

char*  floatToString(float num)
{
   int whole_part = num;
   int digit = 0, reminder =0;
   int log_value = log10(num), index = log_value;
   long wt =0;

   // String containg result
   char* str = new char[20];

   //Initilise stirng to zero
   memset(str, 0 ,20);

   //Extract the whole part from float num
   for(int  i = 1 ; i < log_value + 2 ; i++)
   {
       wt  =  pow(10.0,i);
       reminder = whole_part  %  wt;
       digit = (reminder - digit) / (wt/10);

       //Store digit in string
       str[index--] = digit + 48;              // ASCII value of digit  = digit + 48
       if (index == -1)
          break;    
   }

    index = log_value + 1;
    str[index] = '.';

   float fraction_part  = num - whole_part;
   float tmp1 = fraction_part,  tmp =0;

   //Extract the fraction part from  num
   for( int i= 1; i < PRECISION; i++)
   {
      wt =10; 
      tmp  = tmp1 * wt;
      digit = tmp;

      //Store digit in string
      str[++index] = digit +48;           // ASCII value of digit  = digit + 48
      tmp1 = tmp - digit;
   }    

   return str;
}


//Main program
void main()
{
    int i;
    float f = 123456.789;
    char* str =  floatToString(f);
    cout  << endl <<  str;
    cin >> i;
    delete [] str;
}
3
Rahul Naik

Je viens de trouver vraiment une bonne implémentation sur https://code.google.com/p/stringencoders/

size_t modp_dtoa(double value, char* str, int prec)
{
    /* Hacky test for NaN
     * under -fast-math this won't work, but then you also won't
     * have correct nan values anyways.  The alternative is
     * to link with libmath (bad) or hack IEEE double bits (bad)
     */
    if (! (value == value)) {
        str[0] = 'n'; str[1] = 'a'; str[2] = 'n'; str[3] = '\0';
        return (size_t)3;
    }
    /* if input is larger than thres_max, revert to exponential */
    const double thres_max = (double)(0x7FFFFFFF);

    double diff = 0.0;
    char* wstr = str;

    if (prec < 0) {
        prec = 0;
    } else if (prec > 9) {
        /* precision of >= 10 can lead to overflow errors */
        prec = 9;
    }


    /* we'll work in positive values and deal with the
       negative sign issue later */
    int neg = 0;
    if (value < 0) {
        neg = 1;
        value = -value;
    }


    int whole = (int) value;
    double tmp = (value - whole) * powers_of_10[prec];
    uint32_t frac = (uint32_t)(tmp);
    diff = tmp - frac;

    if (diff > 0.5) {
        ++frac;
        /* handle rollover, e.g.  case 0.99 with prec 1 is 1.0  */
        if (frac >= powers_of_10[prec]) {
            frac = 0;
            ++whole;
        }
    } else if (diff == 0.5 && ((frac == 0) || (frac & 1))) {
        /* if halfway, round up if odd, OR
           if last digit is 0.  That last part is strange */
        ++frac;
    }

    /* for very large numbers switch back to native sprintf for exponentials.
       anyone want to write code to replace this? */
    /*
      normal printf behavior is to print EVERY whole number digit
      which can be 100s of characters overflowing your buffers == bad
    */
    if (value > thres_max) {
        sprintf(str, "%e", neg ? -value : value);
        return strlen(str);
    }

    if (prec == 0) {
        diff = value - whole;
        if (diff > 0.5) {
            /* greater than 0.5, round up, e.g. 1.6 -> 2 */
            ++whole;
        } else if (diff == 0.5 && (whole & 1)) {
            /* exactly 0.5 and ODD, then round up */
            /* 1.5 -> 2, but 2.5 -> 2 */
            ++whole;
        }
    } else {
        int count = prec;
        // now do fractional part, as an unsigned number
        do {
            --count;
            *wstr++ = (char)(48 + (frac % 10));
        } while (frac /= 10);
        // add extra 0s
        while (count-- > 0) *wstr++ = '0';
        // add decimal
        *wstr++ = '.';
    }

    // do whole part
    // Take care of sign
    // Conversion. Number is reversed.
    do *wstr++ = (char)(48 + (whole % 10)); while (whole /= 10);
    if (neg) {
        *wstr++ = '-';
    }
    *wstr='\0';
    strreverse(str, wstr-1);
    return (size_t)(wstr - str);
}
3
shapkin

Vous avez deux problèmes majeurs:

  1. Conversion de la représentation de bit en une chaîne de caractères 
  2. Allouer suffisamment de mémoire pour stocker les caractères.

Le moyen le plus simple de résoudre la seconde partie consiste à allouer un bloc suffisamment volumineux pour chaque réponse possible. Commence avec ça. Plus tard, vous voudrez être plus intelligent, mais ne vous inquiétez pas jusqu'à ce que vous ayez résolu le problème numérique.

Vous disposez de deux jeux d’outils pour traiter la partie numérique du problème: la manipulation directe de bits (masquage, décalage, etc.) et l’opération arithmétique (*, +, /, plus éventuellement des fonctions mathématiques link log()).

En principe, vous pouvez vous attaquer directement à la représentation au niveau du bit, mais cela ne serait pas portable si les formats de représentation en virgule flottante changeaient à l'avenir. La méthode suggérée par edgar.holleis devrait être portable.

1
dmckee

Voici ce que je suis venu avec; c'est très efficace et très simple. Cela suppose que votre système a itoa.

#include <math.h>
#include <string.h>

/* return decimal part of val */
int dec(float val)
{
    int mult = floor(val);

    while (floor(val) != ceil(val)) {
        mult *= 10;
        val *= 10;
    }

    return floor(val) - mult;
}

/* convert a double to a string */
char *ftoa(float val, char *str)
{
    if (isnan(n)) {
        strcpy(str, "NaN");
        return str;
    } else if (isinf(n)) {
        strcpy(str, "inf");
        return str;
    }

    char leading_integer[31]  = {0};  // 63 instead of 31 for 64-bit systems
    char trailing_decimal[31] = {0};  // 63 instead of 31 for 64-bit systems

    /* fill string with leading integer */
    itoa(floor(val), leading_integer, 10);

    /* fill string with the decimal part */
    itoa(dec(val), trailing_decimal, 10);

    /* set given string to full decimal */
    strcpy(str, leading_integer);
    strcat(str, ".");
    strcat(str, trailing_decimal);

    return str;
}

Essayez-le en ligne!

0
MD XF