Преобразовать число с плавающей точкой в ​​строку - PullRequest
16 голосов
/ 20 февраля 2010

Как я могу преобразовать целое число с плавающей точкой в ​​строку в C / C ++ без функции библиотеки sprintf?

Я ищу функцию, например, char *ftoa(float num), который преобразует num в строку и возвращает ее.

ftoa(3.1415) должен вернуть "3.1415".

Ответы [ 8 ]

17 голосов
/ 17 августа 2011

Основываясь на ответе Софи Пала, это чуть более полное решение, которое учитывает число ноль, NaN, бесконечные, отрицательные числа и научную запись. Хотя sprintf по-прежнему обеспечивает более точное представление строк.

/* 
   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]));
    }
}

Выходы:

  1. printf: 0, dtoa: 0
  2. printf: 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
10 голосов
/ 21 февраля 2010

Когда вы имеете дело с числами fp, оно может быть очень сложным, но алгоритм упрощен и похож на ответ Эдгара Холлейса; престижность! Это сложно, потому что когда вы имеете дело с числами с плавающей запятой, вычисления будут немного отклоняться в зависимости от выбранной вами точности. Поэтому сравнивать число с плавающей точкой с нулем не очень хорошая практика программирования.

Но есть ответ, и это моя попытка реализовать его. Здесь я использовал значение допуска, чтобы вы не вычислили слишком много десятичных знаков, что привело к бесконечному циклу. Я уверен, что могут быть лучшие решения, но это должно помочь вам лучше понять, как это сделать.

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';
6 голосов
/ 20 февраля 2010
  1. Используйте функцию log, чтобы узнать величину m вашего числа. Если величина отрицательная, выведите "0." и соответствующее количество нулей.
  2. Последовательное деление на 10^m и приведение результата к int, чтобы получить десятичные цифры. m-- для следующей цифры.
  3. Если вы пришли через m==0, не забудьте напечатать десятичную точку ".".
  4. Разрыв после пары цифр. Если m>0 при разрыве, не забудьте напечатать "E" и itoa(m).

Вместо функции log вы также можете напрямую извлекать показатель степени, сдвигая биты и корректируя смещение показателя (см. IEEE 754). В Java есть функция двойных битов для получения двоичного представления.

3 голосов
/ 06 марта 2013

Только что нашел действительно хорошую реализацию на 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 голосов
/ 19 апреля 2010
 /*
  * 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;
}
1 голос
/ 20 февраля 2010

У вас есть две основные проблемы:

  1. Преобразование битового представления в строку символов
  2. Выделение достаточного количества памяти для хранения символов.

Самый простой способ решить вторую часть - выделить достаточно большой кусок для каждого возможного ответа. Начни с этого. Позже вы захотите быть более умным, но не беспокойтесь, пока не решите числовую часть проблемы.

У вас есть два набора инструментов для решения числовой части задачи: прямое управление битами (маскирование, сдвиг и т. Д.) И арифметические операции (*, +, /, плюс, возможно, математические функции ссылка log()).

В принципе, вы можете напрямую работать с побитовым представлением, но это не будет переносимым в случае изменения форматов представления с плавающей запятой в будущем. Метод , предложенный edgar.holleis , должен быть переносимым.

0 голосов
/ 11 октября 2017

Вот что я придумал; это очень эффективно и очень просто. Предполагается, что ваша система имеет 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;
}

Попробуйте онлайн!

0 голосов
/ 23 августа 2013

Эта суть может помочь: https://gist.github.com/psych0der/6319244 Основная идея состоит в том, чтобы разбить целую часть и десятичную часть, а затем объединить их обе с десятичной дробью между ними.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...