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&lt; T &gt;::digits10 和 MaxLen::StringLen 的值略有不同,因为如果无法达到“9”,则前者不考虑数字。 当然,如果您不想在某些情况下浪费一个字节,您当然可以使用它并简单地添加两个。

编辑:

有些人可能觉得很奇怪,包括&lt;climits&gt;。 如果您可以使用 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&lt; T &gt;::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_tint16_tint32_tint128_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:打印十进制整数的最大字符数的主要内容,如果未能解决你的问题,请参考以下文章

12:打印 1 到最大的 n 位数

C语言。编写一个程序,输入一个二进制的字符串(长度不超过32),然后计算出相应的十进制整数,并打印。

将整数向量打印为十进制数

剑指 Offer 17. 打印从1到最大的n位数 的详细题解

打印1到最大的n位数

十六进制字符串转十进制整数