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.
¿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 -) –
¿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'. –