最简单的规范和可以说是最便携的方法是询问snprintf()需要多少空间:
char sbuf[2];
int ndigits;
ndigits = snprintf(sbuf, (size_t) 1, "%lld", (long long) INT_MIN);
可能使用intmax_t 和%j 的便携性稍差:
ndigits = snprintf(sbuf, (size_t) 1, "%j", (intmax_t) INT_MIN);
人们可能会认为在运行时这样做太昂贵了,但它可以用于任何值,而不仅仅是任何整数类型的 MIN/MAX 值。
您当然也可以使用简单的递归函数直接计算给定整数需要以 Base 10 表示法表示的位数:
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);
}
但这当然在运行时也需要 CPU,即使是内联时也是如此,尽管可能比 snprintf() 少一点。
@R. 上面的回答虽然或多或少是错误的,但在正确的轨道上。以下是一些经过广泛测试且高度可移植的宏的正确推导,这些宏在编译时使用 sizeof() 实现计算,对 @R. 的初始措辞稍作修正:
首先我们可以很容易地看到(或显示)sizeof(int) 是 UINT_MAX 的日志基数 2 除以 sizeof() 的一个单元所代表的位数(8,又名 CHAR_BIT):
sizeof(int) == log2(UINT_MAX) / 8
因为UINT_MAX 当然只是 2 ^ (sizeof(int) * 8)) 而 log2(x) 是 2^x 的倒数。
我们可以使用恒等式“logb(x) = log(x) / log(b)”(其中 log() 是自然对数)来求其他底的对数。例如,您可以使用以下方法计算“x”的“log base 2”:
log2(x) = log(x) / log(2)
还有:
log10(x) = log(x) / log(10)
所以,我们可以推断:
log10(v) = log2(v) / log2(10)
现在我们最终想要的是UINT_MAX 的以 10 为底的对数,所以由于 log2(10) 大约是 3,而且我们从上面知道 log2() 是用 sizeof() 表示的,所以我们可以说 log10(UINT_MAX) 大约是:
log10(2^(sizeof(int)*8)) ~= (sizeof(int) * 8) / 3
虽然这并不完美,特别是因为我们真正想要的是上限值,但是通过一些细微的调整来考虑 log2(10) 到 3 的整数舍入,我们可以通过首先将 1 添加到log2 项,然后从任何较大整数的结果中减去 1,得到这个“足够好”的表达式:
#if 0
#define __MAX_B10STRLEN_FOR_UNSIGNED_TYPE(t) \
((((sizeof(t) * CHAR_BIT) + 1) / 3) - ((sizeof(t) > 2) ? 1 : 0))
#endif
更好的是,我们可以将第一个 log2() 项乘以 1/log2(10)(乘以除数的倒数与除以除数相同),这样做可以找到更好的整数近似。我最近(重新?)在阅读肖恩安德森的比特黑客时遇到了这个建议:http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10
要使用可能的最佳近似整数数学来做到这一点,我们需要找到代表我们倒数的理想比率。这可以通过搜索将我们期望的值 1/log2(10) 乘以 2 的连续幂的最小小数部分来找到,在 2 的某个合理范围内,例如使用以下小 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
所以我们可以得到一个很好的整数近似值,将我们的 log2(v) 值乘以 1/log2(10),方法是将它乘以 1233,然后右移 12(当然,2^12 是 4096):
log10(UINT_MAX) ~= ((sizeof(int) * 8) + 1) * 1233 >> 12
并且,加上加一来做相当于找到上限的操作,这消除了摆弄奇数值的需要:
#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))
而通常编译器会在编译时评估我的__MAX_B10STRLEN_FOR_INT_TYPE() 宏变成的表达式。当然,我的宏总是计算给定类型整数所需的最大空间,而不是特定整数值所需的确切空间。