ANSI-C:打印十进制整数的最大字符数
Posted
技术标签:
【中文标题】ANSI-C:打印十进制整数的最大字符数【英文标题】:ANSI-C: maximum number of characters printing a decimal int 【发布时间】:2012-05-19 03:44:24 【问题描述】:我想知道确定打印小数 int
的最大字符数是否是一种简单的方法。
我知道 <limits.h>
包含像 INT_MAX
这样的定义,表示 int 可以假设的最大 值,但这不是我想要的。
我希望能够做类似的事情:
int get_int( void )
char draft[ MAX_CHAR_OF_A_DECIMAL_INT ];
fgets( draft, sizeof( draft ), stdin );
return strtol( draft, NULL, 10 );
但是如何以可移植和低开销的方式找到MAX_CHAR_OF_A_DECIMAL_INT
的值呢?
谢谢!
【问题讨论】:
你不能取INT_MAX,转换成字符串,计算长度,然后加一个(允许前导-) 大概你实际上并不需要最大可能的长度,只需要一个大于或等于那个的数字,而且不会大到非常浪费?BIG_ENOUGH_FOR_AN_INT
,而不是BIGGEST_AN_INT_CAN_BE
。
【参考方案1】:
如果您假设CHAR_BIT
是 8(POSIX 需要,因此对于任何针对 POSIX 系统以及任何其他主流系统(如 Windows)的代码来说,这是一个安全的假设),一个便宜的安全公式是 3*sizeof(int)+2
。如果没有,你可以把它设为3*sizeof(int)*CHAR_BIT/8+2
,或者有一个稍微简单一点的版本。
如果您对它起作用的原因感兴趣,sizeof(int)
本质上是INT_MAX
的对数(大约以 2^CHAR_BIT 为底数),不同底数的对数之间的转换(例如以 10 为底)只是乘法。特别是,3 是 256 的对数基数为 10 的整数近似值/上限。
+2 是为了说明可能的符号和空终止。
【讨论】:
推导:表示一个十进制数字平均需要3.2位;每个 8 位字节平均可以表示 2.5 个十进制数字;四舍五入给你3(因此3 * sizeof (int)
)。然后,您需要一个额外的符号字符和一个额外的字符 0 终止符(因此是 + 2
)。【参考方案2】:
我不知道用普通的 ANSI-C 做你想做的事是否有什么技巧,但是在 C++ 中你可以轻松地使用模板元编程来做:
#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
;
;
您可以从纯 C 代码中调用它,创建一个额外的 C++ 函数,如下所示:
extern "C"
int int_str_max( )
return MaxLen< int >::StringLen;
这具有零执行时间开销并计算所需的确切空间。
您可以使用以下内容测试上述模板:
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;
输出是:
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.
请注意与 std::numeric_limits< T >::digits10
和 MaxLen::StringLen 的值略有不同,因为如果无法达到“9”,则前者不考虑数字。
当然,如果您不想在某些情况下浪费一个字节,您当然可以使用它并简单地添加两个。
编辑:
有些人可能觉得很奇怪,包括<climits>
。
如果您可以使用 C++11,您将不需要它,并且会获得额外的简单性:
#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
;
;
现在你可以使用
MaxLen< short >::StringLen
而不是
MaxLen< short, SHRT_MAX >::StringLen
很好,不是吗?
【讨论】:
我想我可以忍受std::numeric_limits< T >::digits10 + 2
并浪费一个字节。这看起来简单而快速。谢谢。
首先,C++ != C 其次,使用 sizeof() 以相对简单的表达式在 C 和 C++ 中完成的操作非常复杂。
当然你可以使用它,如果你不介意在某些地方浪费一个字节,只需添加两个 -- 为什么要添加 2 而不仅仅是 1 位? 是为了标志吗?是 NULL 字符吗?更明确。【参考方案3】:
最简单的规范和可以说是最便携的方法是询问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()
宏变成的表达式。当然,我的宏总是计算给定类型整数所需的最大空间,而不是特定整数值所需的确切空间。
【讨论】:
【参考方案4】:有符号或无符号整数的最大十进制位数d b位的x与数字的十进制位数匹配2^b。 对于带符号的数字,必须为符号添加一个额外的字符。
x的小数位数可以计算为log_10(x),四舍五入。
因此,x 的最大小数位数为 log_10(2^b) = b * log_10(2) = b * 0.301029995663981,向上取整。
如果 s 是用于存储 x 的某种整数类型的字节大小(由 sizeof 运算符给出),它的大小 b 以位为单位将是 b = s * 8。因此,十进制数字的最大数量 d 将be (s * 8) * 0.301029995663981,向上取整。 向上取整将包括截断(转换为整数)和加 1。
当然,所有这些常量都必须加 1 才能计算出最后的 0 字节(参见下例中的 IntegerString)。
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#define COMMON_LOG_OF_2 0.301029995663981
#define MAX_DECIMAL_DIGITS_UCHAR ((unsigned) (sizeof (unsigned char ) * 8 * COMMON_LOG_OF_2) + 1)
#define MAX_DECIMAL_DIGITS_USHORT ((unsigned) (sizeof (unsigned short ) * 8 * COMMON_LOG_OF_2) + 1)
#define MAX_DECIMAL_DIGITS_UINT ((unsigned) (sizeof (unsigned int ) * 8 * COMMON_LOG_OF_2) + 1)
#define MAX_DECIMAL_DIGITS_ULONG ((unsigned) (sizeof (unsigned long ) * 8 * COMMON_LOG_OF_2) + 1)
#define MAX_DECIMAL_DIGITS_ULONGLONG ((unsigned) (sizeof (unsigned long long) * 8 * COMMON_LOG_OF_2) + 1)
#define MAX_DECIMAL_DIGITS_UINT128 ((unsigned) (sizeof (unsigned __int128 ) * 8 * COMMON_LOG_OF_2) + 1)
#define MAX_DECIMAL_DIGITS_CHAR (1 + MAX_DECIMAL_DIGITS_UCHAR )
#define MAX_DECIMAL_DIGITS_SHORT (1 + MAX_DECIMAL_DIGITS_USHORT )
#define MAX_DECIMAL_DIGITS_INT (1 + MAX_DECIMAL_DIGITS_UINT )
#define MAX_DECIMAL_DIGITS_LONG (1 + MAX_DECIMAL_DIGITS_ULONG )
#define MAX_DECIMAL_DIGITS_LONGLONG (1 + MAX_DECIMAL_DIGITS_ULONGLONG)
#define MAX_DECIMAL_DIGITS_INT128 (1 + MAX_DECIMAL_DIGITS_UINT128 )
int main (void)
char IntegerString[MAX_DECIMAL_DIGITS_INT + 1];
printf ("MAX_DECIMAL_DIGITS_UCHAR = %2u\n",MAX_DECIMAL_DIGITS_UCHAR );
printf ("MAX_DECIMAL_DIGITS_USHORT = %2u\n",MAX_DECIMAL_DIGITS_USHORT );
printf ("MAX_DECIMAL_DIGITS_UINT = %2u\n",MAX_DECIMAL_DIGITS_UINT );
printf ("MAX_DECIMAL_DIGITS_ULONG = %2u\n",MAX_DECIMAL_DIGITS_ULONG );
printf ("MAX_DECIMAL_DIGITS_ULONGLONG = %2u\n",MAX_DECIMAL_DIGITS_ULONGLONG);
printf ("MAX_DECIMAL_DIGITS_UINT128 = %2u\n",MAX_DECIMAL_DIGITS_UINT128 );
printf ("MAX_DECIMAL_DIGITS_CHAR = %2u\n",MAX_DECIMAL_DIGITS_CHAR );
printf ("MAX_DECIMAL_DIGITS_SHORT = %2u\n",MAX_DECIMAL_DIGITS_SHORT );
printf ("MAX_DECIMAL_DIGITS_INT = %2u\n",MAX_DECIMAL_DIGITS_INT );
printf ("MAX_DECIMAL_DIGITS_LONG = %2u\n",MAX_DECIMAL_DIGITS_LONG );
printf ("MAX_DECIMAL_DIGITS_LONGLONG = %2u\n",MAX_DECIMAL_DIGITS_LONGLONG );
printf ("MAX_DECIMAL_DIGITS_INT128 = %2u\n",MAX_DECIMAL_DIGITS_INT128 );
sprintf (IntegerString,"%d",INT_MAX);
printf ("INT_MAX = %d\n",INT_MAX);
printf ("IntegerString = %s\n",IntegerString);
sprintf (IntegerString,"%d",INT_MIN);
printf ("INT_MIN = %d\n",INT_MIN);
printf ("IntegerString = %s\n",IntegerString);
return EXIT_SUCCESS;
编辑:
不幸的是,在将表达式作为常量求值时,使用浮点可能会导致问题。我通过乘以 2 ^ 11 并除以 2 ^ 8 对它们进行了修改,因此所有计算都应由具有整数的预处理器执行:
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#define LOG2_x_2_11 616 // log(2) * 2^11
#define MAX_DECIMAL_DIGITS_UCHAR (((sizeof (unsigned char ) * LOG2_x_2_11) >> 8) + 1)
#define MAX_DECIMAL_DIGITS_USHORT (((sizeof (unsigned short ) * LOG2_x_2_11) >> 8) + 1)
#define MAX_DECIMAL_DIGITS_UINT (((sizeof (unsigned int ) * LOG2_x_2_11) >> 8) + 1)
#define MAX_DECIMAL_DIGITS_ULONG (((sizeof (unsigned long ) * LOG2_x_2_11) >> 8) + 1)
#define MAX_DECIMAL_DIGITS_ULONGLONG (((sizeof (unsigned long long) * LOG2_x_2_11) >> 8) + 1)
#define MAX_DECIMAL_DIGITS_UINT128 (((sizeof (unsigned __int128 ) * LOG2_x_2_11) >> 8) + 1)
#define MAX_DECIMAL_DIGITS_CHAR (1 + MAX_DECIMAL_DIGITS_UCHAR )
#define MAX_DECIMAL_DIGITS_SHORT (1 + MAX_DECIMAL_DIGITS_USHORT )
#define MAX_DECIMAL_DIGITS_INT (1 + MAX_DECIMAL_DIGITS_UINT )
#define MAX_DECIMAL_DIGITS_LONG (1 + MAX_DECIMAL_DIGITS_ULONG )
#define MAX_DECIMAL_DIGITS_LONGLONG (1 + MAX_DECIMAL_DIGITS_ULONGLONG)
#define MAX_DECIMAL_DIGITS_INT128 (1 + MAX_DECIMAL_DIGITS_UINT128 )
int main (void)
char IntegerString[MAX_DECIMAL_DIGITS_INT + 1];
printf ("MAX_DECIMAL_DIGITS_UCHAR = %2zu\n",MAX_DECIMAL_DIGITS_UCHAR );
printf ("MAX_DECIMAL_DIGITS_USHORT = %2zu\n",MAX_DECIMAL_DIGITS_USHORT );
printf ("MAX_DECIMAL_DIGITS_UINT = %2zu\n",MAX_DECIMAL_DIGITS_UINT );
printf ("MAX_DECIMAL_DIGITS_ULONG = %2zu\n",MAX_DECIMAL_DIGITS_ULONG );
printf ("MAX_DECIMAL_DIGITS_ULONGLONG = %2zu\n",MAX_DECIMAL_DIGITS_ULONGLONG);
printf ("MAX_DECIMAL_DIGITS_UINT128 = %2zu\n",MAX_DECIMAL_DIGITS_UINT128 );
printf ("MAX_DECIMAL_DIGITS_CHAR = %2zu\n",MAX_DECIMAL_DIGITS_CHAR );
printf ("MAX_DECIMAL_DIGITS_SHORT = %2zu\n",MAX_DECIMAL_DIGITS_SHORT );
printf ("MAX_DECIMAL_DIGITS_INT = %2zu\n",MAX_DECIMAL_DIGITS_INT );
printf ("MAX_DECIMAL_DIGITS_LONG = %2zu\n",MAX_DECIMAL_DIGITS_LONG );
printf ("MAX_DECIMAL_DIGITS_LONGLONG = %2zu\n",MAX_DECIMAL_DIGITS_LONGLONG );
printf ("MAX_DECIMAL_DIGITS_INT128 = %2zu\n",MAX_DECIMAL_DIGITS_INT128 );
sprintf (IntegerString,"%d",INT_MAX);
printf ("INT_MAX = %d\n",INT_MAX);
printf ("IntegerString = %s\n",IntegerString);
sprintf (IntegerString,"%d",INT_MIN);
printf ("INT_MIN = %d\n",INT_MIN);
printf ("IntegerString = %s\n",IntegerString);
return EXIT_SUCCESS;
【讨论】:
【参考方案5】:接受答案后(2 年以上)
以下分数 10/33 完全满足未填充的 int8_t
、int16_t
、int32_t
和 int128_t
的需求。 int64_t
只有 1 个 char
。对于不超过int362_t
的所有整数大小,精确或大1。超过 1 可能超过 1。
#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()
通常与终止 '\n'
的附加 char
配合使用效果最佳。
类似于@R..,但分数更好。
建议在读取用户输入时使用大容量的 2x 缓冲区。有时用户会添加空格、前导零等。
char draft[2*(MAX_CHAR_SIZE_DECIMAL_INTEGER(long) + 1)];
fgets(draft, sizeof draft, stdin);
【讨论】:
【参考方案6】:您可以使用以 10 为底的对数来计算位数。在我的系统中,使用数字的位表示来计算以 2 为底的对数上限并没有显着提高速度。 log base 10 + 1的底数给出位数,我加2来说明空字符和符号。
#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;
还要注意int
的字节数可以是 2 或 4,并且仅在旧系统中为 2,因此您可以计算上限并在程序中使用它。
【讨论】:
【参考方案7】:在 C++11 及更高版本中,您可以执行以下操作:
namespace details
template<typename T>
constexpr size_t max_to_string_length_impl(T value)
return (value >= 0 && value < 10) ? 1 // [0..9] -> 1
: (std::is_signed<T>::value && value < 0 && value > -10) ? 2 // [-9..-1] -> 2
: 1 + max_to_string_length_impl(value / 10); // ..-10] [10.. -> recursion
template<typename T>
constexpr size_t max_to_string_length()
return std::max(
details::max_to_string_length_impl(std::numeric_limits<T>::max()),
details::max_to_string_length_impl(std::numeric_limits<T>::min()));
【讨论】:
【参考方案8】:这是 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];
然后:
$ 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
【讨论】:
标准中没有任何内容要求INT_MAX
以十进制形式给出。在最近的 gccs 中使用0x7FFFFFFF
代替。以上是关于ANSI-C:打印十进制整数的最大字符数的主要内容,如果未能解决你的问题,请参考以下文章
C语言。编写一个程序,输入一个二进制的字符串(长度不超过32),然后计算出相应的十进制整数,并打印。