Search code examples
cprintfc-preprocessormicrocontrollerc-strings

Determine size of char array to convert numbers with the preprocessor


I'm facing this simple "problem" literally every day and don't yet have a good solution for it:

char text[STRING_LENGTH + 1] = ""; 
int32_t number = -12312313; 
itoa(number, text, 10); //or sprintf(text, "%d", number);

How do I determine STRING_LENGTH in the preprocessor? STRING_LENGTH should be the maximum length, the type to convert could produce. In my case int32_t => 11. Surely, I could do something like this:

#define INT32_STRING_LENGTH 11
#define UINT32_STRING_LENGTH 10
//...

but it seems like there should already be a solution to this out there.

It's also an option to use snprintf in that case, but that would calculate the length while the application is already running. There's really no need to do this while running (if you have some spare-bytes):

int32_t number = -12312313; 
char text[snprintf(NULL, 0, "%d", number) + 1];
snprintf(text, sizeof(text), "%d", number);

Maybe there's a solution using "%d", "%lu", ...?

Please correct me, if I'm wrong with anything I mentioned here.

Thanks in advance!


Solution

  • The GNU Portability Library has an amazing single header intprops part which has some amazing code available from github/coreutils/gnulib/intprops.h. The header contains INT_BUFSIZE_BOUND and INT_STRLEN_BOUND:

    /* Bound on buffer size needed to represent an integer type or expression T,
       including the terminating null.  T must not be a bit-field expression.  */
    #define INT_BUFSIZE_BOUND(t) (INT_STRLEN_BOUND (t) + 1)
    

    The documentation with usage example is available in the documentation https://www.gnu.org/software/gnulib/manual/html_node/Integer-Bounds.html :

    INT_BUFSIZE_BOUND (t) is an integer constant expression that is a bound on the size of the string representing an integer type or expression t in decimal notation, including the terminating null character and any leading - character. For example, if INT_BUFSIZE_BOUND (int) is 12, any value of type int can be represented in 12 bytes or less, including the terminating null. The bound is not necessarily tight.

    Example usage:

    #include <intprops.h>
    #include <stdio.h>
    int
    int_strlen (int i)
    {
      char buf[INT_BUFSIZE_BOUND (int)];
      return sprintf (buf, "%d", i);
    }
    

    Note that the header is under GNU Lesser General Public License. Remember to copy the header with license and distribute your software with some contribution and license. Then just:

    int32_t number = -12312313; 
    char text[INT_BUFSIZE_BOUND(int32_t)];
    // or    [INT_BUFSIZE_BOUND(-12312313)];
    sprintf(text, "%"PRId32, number);
    

    Note that %d may be invalid for int32_t. Use standard PRId32 from inttypes.h.


    The heart of the implementation is this nice function, that can be used to basically calculate log10 of a number as a constant expression:

    /* Bound on length of the string representing an unsigned integer
       value representable in B bits.  log10 (2.0) < 146/485.  The
       smallest value of B where this bound is not tight is 2621.  */
    #define INT_BITS_STRLEN_BOUND(b) (((b) * 146 + 484) / 485)
    

    So basically to get maximum buffer size length, get the number of bits for a type, calculate * 146 + 484) / 485) of that number of bits, add +1 for minus sign and add +1 for zero terminating byte - that's the number of bytes that you need.