2012-05-10 16 views
8

Me gustaría saber si es una manera fácil de determinar el número máximo de caracteres para imprimir un decimal int.ANSI-C: número máximo de caracteres imprimiendo un decimal int

<limits.h> contiene definiciones como INT_MAX que dicen que el valor máximo un int puede asumir, pero no es lo que quiero.

Me gustaría ser capaz de hacer algo como:

int get_int(void) 
{ 
    char draft[ MAX_CHAR_OF_A_DECIMAL_INT ]; 

    fgets(draft, sizeof(draft), stdin); 
    return strtol(draft, NULL, 10); 
} 

Pero cómo encontrar el valor de MAX_CHAR_OF_A_DECIMAL_INT de una manera overheaded portátil y de bajo?

Gracias!

+0

¿No podría tener INT_MAX, convertir en una cadena, y el recuento de la longitud, y luego añadir un código (para permitir una leading -) –

+2

¿Presumiblemente no necesita la longitud máxima posible, solo un número mayor o igual a eso, y no tan grande como para ser un desperdicio? 'BIG_ENOUGH_FOR_AN_INT', en lugar de' BIGGEST_AN_INT_CAN_BE'. –

Respuesta

2

No sé si se trata de ningún truco para hacer lo que quiera en la llanura ANSI-C, pero en C++ puede utilizar simplemente metaprogramming plantilla para hacer:

#include <iostream> 
#include <limits> 
#include <climits> 

template< typename T, unsigned long N = INT_MAX > 
class MaxLen 
{ 
public: 
    enum 
    { 
     StringLen = MaxLen< T, N/10 >::StringLen + 1 
    }; 
}; 

template< typename T > 
class MaxLen< T, 0 > 
{ 
public: 
    enum 
    { 
     StringLen = 1 
    }; 
}; 

Y se le puede llamar desde su código-C pura creación de una función adicional C++ así:

extern "C" 
int int_str_max() 
{ 
    return MaxLen<int>::StringLen; 
} 

esto tiene un tiempo de ejecución CERO gastos generales y calcula el espacio exacto necesario.


Puede probar las plantillas anteriores con algo como:

int main() 
{ 
std::cout << "Max: " << std::numeric_limits<short>::max() << std::endl; 
std::cout << "Digits: " << std::numeric_limits<short>::digits10 << std::endl; 
std::cout << "A \"short\" is " << sizeof(short) << " bytes." << std::endl 
    << "A string large enough to fit any \"short\" is " 
    << MaxLen< short, SHRT_MAX >::StringLen << " bytes wide." << std::endl; 

std::cout << "Max: " << std::numeric_limits<int>::max() << std::endl; 
std::cout << "Digits: " << std::numeric_limits<int>::digits10 << std::endl; 
std::cout << "An \"int\" is " << sizeof(int) << " bytes." << std::endl 
    << "A string large enough to fit any \"int\" is " 
    << MaxLen<int>::StringLen << " bytes wide." << std::endl; 

std::cout << "Max: " << std::numeric_limits<long>::max() << std::endl; 
std::cout << "Digits: " << std::numeric_limits<long>::digits10 << std::endl; 
std::cout << "A \"long\" is " << sizeof(long) << " bytes." << std::endl 
    << "A string large enough to fit any \"long\" is " 
    << MaxLen< long, LONG_MAX >::StringLen << " bytes wide." << std::endl; 

    return 0; 
} 

la salida es:

Max: 32767 
Digits: 4 
A "short" is 2 bytes. 
A string large enough to fit any "short" is 6 bytes wide. 
Max: 2147483647 
Digits: 9 
An "int" is 4 bytes. 
A string large enough to fit any "int" is 11 bytes wide. 
Max: 9223372036854775807 
Digits: 18 
A "long" is 8 bytes. 
A string large enough to fit any "long" is 20 bytes wide. 
  • Nota los valores ligeramente diferentes de std::numeric_limits<T>::digits10 y MaxLen < T, N > :: StringLen, ya que el primero no tiene en cuenta los dígitos si no puede llegar a '9'. Por supuesto que puede usarlo y simplemente agregar dos si no le importa perder un solo byte en algunos casos.

EDIT:

Algunos pueden haber encontrado raro incluyendo <climits>. Si usted puede contar con C++ 11, usted no lo necesitan, y ganará una simplicidad adicional:

#include <iostream> 
#include <limits> 

template< typename T, unsigned long N = std::numeric_limits<T>::max() > 
class MaxLen 
{ 
public: 
    enum 
    { 
     StringLen = MaxLen< T, N/10 >::StringLen + 1 
    }; 
}; 

template< typename T > 
class MaxLen< T, 0 > 
{ 
public: 
    enum 
    { 
     StringLen = 1 
    }; 
}; 

Ahora puede utilizar

MaxLen<short>::StringLen 

en lugar de

MaxLen< short, SHRT_MAX >::StringLen 

Bueno, ¿verdad?

+1

Creo que puedo vivir con 'std :: numeric_limits < T > :: digits10 + 2' y perder un byte. Esto parece simple pero rápido. Gracias. – j4x

+4

Primero, C++! = C En segundo lugar, es una cantidad horrible de complejidad hacer algo que se puede hacer tanto en C como en C++ en una expresión relativamente simple usando sizeof(). –

9

Si supone CHAR_BIT es 8 (requerido en POSIX, por lo que es una suposición segura para cualquier código dirigido a sistemas POSIX así como a cualquier otro sistema convencional como Windows), una fórmula segura y barata es 3*sizeof(int)+2. Si no, puede hacerlo 3*sizeof(int)*CHAR_BIT/8+2, o hay una versión un poco más simple.

En caso de que esté interesado en la razón por la que esto funciona, sizeof(int) es esencialmente un logaritmo de INT_MAX (aproximadamente log base 2^CHAR_BIT), y la conversión entre los logaritmos de diferentes bases (por ejemplo, a la base 10) es simplemente la multiplicación. En particular, 3 es una aproximación de entero/límite superior en base de registro 10 de 256.

El +2 es para dar cuenta de un posible signo y terminación nula.

+1

Derivación: toma un promedio de 3.2 bits para representar un dígito decimal; cada byte de 8 bits puede representar un promedio de 2.5 dígitos decimales; redondeando te da 3 (de ahí '3 * sizeof (int)'). Luego necesita un carácter adicional para el signo y un carácter adicional para el terminador 0 (de ahí el '+ 2'). –

+0

que no es del todo correcto, pero está en el camino correcto .... –

2

La forma más sencilla canónica y podría decirse que más portátil es preguntar snprintf() cómo se requeriría mucho espacio:

char sbuf[2]; 
int ndigits; 

ndigits = snprintf(sbuf, (size_t) 1, "%lld", (long long) INT_MIN); 

ligeramente menos portátil tal vez usando intmax_t y %j:

ndigits = snprintf(sbuf, (size_t) 1, "%j", (intmax_t) INT_MIN); 

Uno podría considerar que Sin embargo, es demasiado costoso de hacer en tiempo de ejecución, pero puede funcionar para cualquier valor, no solo para los valores MIN/MAX de cualquier tipo de entero.

Se podría, por supuesto, también calcular simplemente directamente el número de dígitos que un entero dado que requeriría que se expresa en la base 10 de la notación con una simple función recursiva:

unsigned int 
numCharsB10(intmax_t n) 
{ 
     if (n < 0) 
       return numCharsB10((n == INTMAX_MIN) ? INTMAX_MAX : -n) + 1; 
     if (n < 10) 
       return 1; 

     return 1 + numCharsB10(n/10); 
} 

pero que por supuesto también requiere CPU en tiempo de ejecución, incluso cuando está en línea, aunque tal vez un poco menos de snprintf() hace.

@ R. La respuesta anterior es más o menos incorrecta, pero en el camino correcto. Aquí está la derivación correcta de algunas macros muy bien probadas y altamente portátiles que implementan el cálculo en tiempo de compilación usando sizeof(), usando una ligera corrección de la redacción inicial de @ R. para comenzar:

Primero podemos ver fácilmente (o demostración) que sizeof(int) es el logaritmo en base 2 de UINT_MAX dividido por el número de bits representada por una unidad de sizeof() (8, también conocido como CHAR_BIT):

sizeof (int) == log2 (UINT_MAX)/8

porque UINT_MAX es, por supuesto, solo 2^(sizeof (int) * 8)) y log2 (x) es el inverso de 2^x.

Podemos usar la identidad "logb (x) = log (x)/log (b)" (donde log() es el logaritmo natural) para encontrar logaritmos de otras bases.Por ejemplo, se podía calcular el "log base 2" de "x" usando:

log2 (x) = log (x)/log (2)

y también:

log10 (x) = log (x)/log (10)

por lo tanto, se puede deducir que:

log10 (v) = log2 (v)/log2 (10)

Ahora lo que queremos en el final es la base de registro 10 de UINT_MAX, por lo que desde l OG2 (10) es de aproximadamente 3, y puesto que sabemos por encima de lo que log2() es en términos de sizeof(), podemos decir que log10 (UINT_MAX) es aproximadamente:

log10 (2^(sizeof (int) * 8)) ~ = (sizeof (int) * 8)/3

Sin embargo, eso no es perfecto, especialmente porque lo que realmente queremos es el valor máximo, pero con algunos ajustes menores para compensar el entero de log2 (10) a 3, podemos obtener lo que necesitamos añadiendo primero uno con el término log2, entonces subtracing 1 a partir del resultado para cualquier entero mayor tamaño, lo que resulta en esta expresión "suficientemente buena":

#if 0 
#define __MAX_B10STRLEN_FOR_UNSIGNED_TYPE(t) \ 
    ((((sizeof(t) * CHAR_BIT) + 1)/3) - ((sizeof(t) > 2) ? 1 : 0)) 
#endif 

Aún mejor podemos multiplicar nuestro primer término log2() por 1/log2 (10) (multiplicar por el recíproco del divisor es lo mismo que dividir por el divisor), y al hacerlo podemos encontrar una mejor aproximación entera . Recientemente (¿re?) Encontré esta sugerencia mientras leía los bithacks de Sean Anderson: http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10

Para hacer esto con matemáticas enteras con la mejor aproximación posible, necesitamos encontrar la relación ideal que represente nuestro recíproco. Esto se puede encontrar buscando la parte fraccional más pequeña de multiplicar nuestro valor deseado de 1/log2 (10) por potencias sucesivas de 2, dentro de un rango razonable de potencias de 2, como con la siguiente secuencia de comandos AWK:

awk 'BEGIN { 
      minf=1.0 
    } 
    END { 
      for (i = 1; i <= 31; i++) { 
        a = 1.0/(log(10)/log(2)) * 2^i 
        if (a > (2^32/32)) 
          break; 
        n = int(a) 
        f = a - (n * 1.0) 
        if (f < minf) { 
          minf = f 
          minn = n 
          bits = i 
        } 
        # printf("a=%f, n=%d, f=%f, i=%d\n", a, n, f, i) 
      } 
      printf("%d + %f/%d, bits=%d\n", minn, minf, 2^bits, bits) 
    }' < /dev/null 

    1233 + 0.018862/4096, bits=12 

Así que podemos obtener una aproximación de entero bueno de multiplicar nuestro valor log2 (v) por 1/log2 (10) multiplicándolo por 1233 seguido de un desplazamiento a la derecha de 12 (2^12 es 4096 por supuesto):

log10 (UINT_MAX) ~ = ((sizeof (int) * 8) + 1) * 1233 >> 12

y, junto con la adición de uno para hacer el equivalente a encontrar el valor de techo, tha t se libra de la necesidad de jugar con los valores impares:

#define __MAX_B10STRLEN_FOR_UNSIGNED_TYPE(t) \ 
    (((((sizeof(t) * CHAR_BIT)) * 1233) >> 12) + 1) 

/* 
* for signed types we need room for the sign, except for int64_t 
*/ 
#define __MAX_B10STRLEN_FOR_SIGNED_TYPE(t) \ 
    (__MAX_B10STRLEN_FOR_UNSIGNED_TYPE(t) + ((sizeof(t) == 8) ? 0 : 1)) 

/* 
* NOTE: this gives a warning (for unsigned types of int and larger) saying 
* "comparison of unsigned expression < 0 is always false", and of course it 
* is, but that's what we want to know (if indeed type 't' is unsigned)! 
*/ 
#define __MAX_B10STRLEN_FOR_INT_TYPE(t)      \ 
    (((t) -1 < 0) ? __MAX_B10STRLEN_FOR_SIGNED_TYPE(t)  \ 
        : __MAX_B10STRLEN_FOR_UNSIGNED_TYPE(t)) 

mientras que normalmente el compilador evaluará en tiempo de compilación de la expresión de mi __MAX_B10STRLEN_FOR_INT_TYPE() macro se convierte. Por supuesto, mi macro siempre calcula el espacio máximo requerido por un tipo dado de entero, no el espacio exacto requerido por un valor entero particular.

2

Después de aceptar la respuesta (2+ años)

la siguiente fracción 10/33 cumple exactamente las necesidades de int8_t sin relleno, int16_t, int32_t y int128_t. Solo 1 char adicional para int64_t. Exacto o 1 sobre todos los tamaños enteros hasta int362_t. Más allá de eso puede haber más de 1 sobre.

#include <limits.h> 
#define MAX_CHAR_LEN_DECIMAL_INTEGER(type) (10*sizeof(type)*CHAR_BIT/33 + 2) 
#define MAX_CHAR_SIZE_DECIMAL_INTEGER(type) (10*sizeof(type)*CHAR_BIT/33 + 3) 

int get_int(void) { 
              // + 1 for the \n of fgets() 
    char draft[MAX_CHAR_SIZE_DECIMAL_INTEGER(long) + 1]; //** 

    fgets(draft, sizeof draft, stdin); 
    return strtol(draft, NULL, 10); 
} 

** fgets() normalmente funciona mejor con un char adicional para la terminación de '\n'.

Similar a @R.. pero con una mejor fracción.


Recomiende utilizar generosas, 2x, búferes al leer la entrada del usuario. A veces un usuario agrega espacios, ceros a la izquierda, etc.

char draft[2*(MAX_CHAR_SIZE_DECIMAL_INTEGER(long) + 1)]; 
    fgets(draft, sizeof draft, stdin); 
0

Se puede calcular el número de dígitos utilizando logaritmo en base 10. En mi sistema, el cálculo del límite del logaritmo en base 2 usando la representación de bits del número didn' proporcionar cualquier ganancia significativa en velocidad. El piso de la base de registro 10 + 1 da la cantidad de dígitos, agrego 2 para tener en cuenta el carácter nulo y el signo.

#include <limits.h> 
#include <stdio.h> 
#include <math.h> 

int main(void){ 
    printf("%d %d\n", INT_MAX, (int)floor(log10(INT_MAX)) + 3); 

    return 0; 
} 

También tenga en cuenta que el número de bytes de un int puede ser 2 ó 4 y es 2 sólo en los sistemas antiguos, por lo que podría calcular el límite superior y utilizarlo en su programa.

1

Aquí está la versión C:

#include <limits.h> 

#define xstr(s) str(s) 
#define str(s) #s 
#define INT_STR_MAX sizeof(xstr(INT_MAX)) 

char buffer[INT_STR_MAX]; 

continuación:

$ gcc -E -o str.cpp str.c 
$ grep buffer str.cpp 
char buffer[sizeof("2147483647")]; 

$ gcc -S -o str.S str.c 
$ grep buffer str.S 
    .comm buffer,11,1