Is there a better way to size a buffer for printing integers?

后端 未结 5 650
执笔经年
执笔经年 2021-01-16 01:41

I want to create a buffer for sprintfing a integer (in this case an unsigned int). A simple and misguided approach would be:

char b         


        
5条回答
  •  情歌与酒
    2021-01-16 02:44

    OP's solution minimally meets design goals.

    Is there a better way to size a buffer for printing integers?

    Even a short analysis indicates the the number of bits needed with a unsigned grows by a factor of log10(2) or about 0.30103.... for each value bit when printing decimal and by 1/3 for printing octal. OP's code uses a factor of one-third or 0.33333...

    unsigned x;
    char buf[(CHAR_BIT*sizeof(unsigned)+5)/3];
    sprintf(buf, "%u", x);
    

    Considerations:

    1. If buffer tightness concerns are real, then a buffer for decimal printing deserves a separate consideration than printing in octal.

    2. Correctness: Unless code uses a strange locale with sprintf(), the conversion of the widest unsigned, which is UINT_MAX works for all platforms.

    3. Clarity: the ...5)/3 is unadorned and does not indicate the rational for 5 and 3.

    4. Efficiency. The buffer size is modestly excessive. This would not be an issues for a single buffer, but for an array of buffers a tighter value is recommended.

    5. Generality: macro is crafted to only one type.

    6. Potential hazard: With code re-use, a code extrapolation could use the same 5 and 3 for int without due consideration. OP's 5/3 works for int too, so this is not an issue.

    7. Corner case: Using 5/3 for signed types and octal is a problem as (CHAR_BIT*sizeof(unsigned)+5)/3 should be (CHAR_BIT*sizeof(unsigned) + 5)/3 + 1. Example: problem occurs when trying to convert an int -32768 to base 8 text: "-100000" via some function (not sprintf(... "%o" ...)). That buffer needed is 8 where as CHAR_BIT*sizeof(unsigned)+5)/3 could be 7.


    Is there a better way to do this?

    Candidate for base 10:

    28/93 (0.301075...) is a very close, and greater, approximation of log10(2). Of course code could use a more obvious fraction like 30103/100000.

    Generality: A good macro would also adapt to other types. Below is one for various unsigned types.

    #define LOG10_2_N 28
    #define LOG10_2_D 93
    //                              1 for the ceiling                          1 for \0
    #define UINT_BUFFER10_SIZE(type) (1 + (CHAR_BIT*sizeof(type)*LOG10_2_N)/LOG10_2_D + 1)
    
    
    unsigned x;
    char bufx[UINT_BUFFER10_SIZE(x)];
    sprintf(bufx, "%u", x);
    
    size_t z;
    char bufz[UINT_BUFFER10_SIZE(z)];
    sprintf(bufz, "%zu", z);
    

    The 28/93 fraction give the same answer integer results as log10(2) for integer sizes 1 to 92 bits and so is space efficient for arrays of buffers. It is never too small.

    A macro for signed type could use

    #define INT_BUFFER_SIZE(type) (1+1+ (CHAR_BIT*sizeof(type)-1)*LOG10_2_N)/LOG10_2_D + 1)
    

    Avoid an off-by-one issue: I recommend using SIZE in the macro name to convey the buffer size needed and not the maximum string length.

    Candidate for base 8:

    Once a computed size for non-base 10 is needed, applications I've made usually need a a buffer to handle any base 2 and up. Consider printf() may allow %b someday too. So for a general purpose buffer to handle integer to text, any base, any sign-ness suggest:

    #define INT_STRING_SIZE(x)  (1 /* sign */ + CHAR_BIT*sizeof(x) + 1 /* \0 */)
    
    int x = INT_MIN;
    char buf[INT_STRING_SIZE(x)];
    my_itoa(buf, sizeof buf, x, 2);
    puts(buf); --> "-10000000000000000000000000000000"  (34 char were needed)
    

提交回复
热议问题