Как преобразовать целое число с плавающей запятой в строку в C/С++ без функции библиотеки sprintf
?
Я ищу функцию, например. char *ftoa(float num)
, который преобразует num
в строку и возвращает ее.
ftoa(3.1415)
должен возвращать "3.1415"
.
Как преобразовать целое число с плавающей запятой в строку в C/С++ без функции библиотеки sprintf
?
Я ищу функцию, например. char *ftoa(float num)
, который преобразует num
в строку и возвращает ее.
ftoa(3.1415)
должен возвращать "3.1415"
.
Когда вы имеете дело с номерами fp, он может получить очень сложный, но алгоритм упрощен и подобен ответу edgar holleis; престижность! Его сложность, потому что, когда вы имеете дело с числами с плавающей запятой, вычисления будут немного завышены в зависимости от выбранной вами точности. Вот почему его неплохая практика программирования для сравнения float с нолем.
Но есть ответ, и это моя попытка реализовать его. Здесь я использовал значение допуска, чтобы вы не вычисляли слишком много десятичных знаков, что приводило к бесконечному циклу. Я уверен, что там могут быть лучшие решения, но это должно помочь вам хорошо понять, как это сделать.
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';
Основываясь на ответе Софи Паль, это немного более полное решение, которое учитывает число ноль, 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]));
}
}
Выходы:
log
-функцию, чтобы узнать величину m
вашего номера. Если величина отрицательная, напечатайте "0."
и соответствующее количество нулей.10^m
и передайте результат в int, чтобы получить десятичные цифры. m--
для следующей цифры.m==0
, не забудьте напечатать десятичную точку "."
.m>0
при разрыве, не забудьте напечатать "E"
и itoa(m)
.Вместо функции log
вы также можете непосредственно извлечь экспоненту путем смещения бит и корректировки смещения экспоненты (см. IEEE 754). Java имеет функцию двойного бита для получения двоичного представления.
/*
* 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;
}
Просто найденная хорошая реализация на 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);
}
У вас две основные проблемы:
Самый простой способ решить вторую часть - выделить достаточно большой кусок для каждого возможного ответа. Начните с этого. Позже вы захотите быть более умными, но не беспокойтесь, пока не решите числовую часть проблемы.
У вас есть два набора инструментов для работы с числовой частью проблемы: непосредственная манипуляция бит (маскирование, перемещение и т.д.) и арифметическая операция (*, +,/, плюс возможно математические функции link log()
).
В принципе, вы можете напрямую заниматься поразрядным представлением, но это не будет переносимым в том случае, если форматы представления с плавающей запятой изменяются в будущем. Метод предложенный edgar.holleis, должен быть портативным.
Здесь http://www.edaboard.com/ftopic41714.html У вас есть реализация ftoa. BTW: Странный вопрос для интервью: | Написание такой функции может быть не очевидно даже для программистов-программистов.
Вот что я придумал; он очень эффективен и очень прост. Предполагается, что ваша система имеет 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;
}
Этот метод может помочь: https://gist.github.com/psych0der/6319244 Основная идея состоит в том, чтобы разделить всю часть и десятичную часть, а затем объединить их между собой с десятичной точкой.