Tech Tip: Integer In, String Out

The C standard library includes a family of functions useful for generating formatted output: printf(), sprintf(), fprintf(), and others. They are very powerful, but to get that power they suffer from the twin problems of size (usually vital in embedded systems) and complexity (which contributes to the size problem, but also makes it very easy to mis-use them). Usually, an embedded program does not need the full functionality and so can get away with smaller, custom-tailored routines that can be easier to use, too.

Let's assume that you already have a function called putch() that takes a single char argument and sends it to your output device, whatever that may be.

The simplest step up from that is a function to send a string to that same output device:

/* Send 'len' characters from 's' to 'putch()';
   if 'len'==0, keep sending until you hit a '\0'. */
void put_string(const char* s, unsigned len)
{
	if (!s) return;
	if (len) while (len--) putch(*(s++));
	else while (*s) putch(*(s++));
}

(This function will, if you ask it nicely, send only part of an array of chars to putch()).


Code to output other data types can be written either to call putch() for every character, or (with appropriate precautions to avoid buffer overflows) can generate strings which are then fed to put_string(). In this article, the second approach will be used.

To output integers in decimal, the following code is much smaller than a full sprintf() implementation:

#include <limits.h>

#if UINT_MAX >= 100000
#error This code supports 'int' less than 100000 only.
#endif

/* Put the decimal representation of 'v' into the array of 'n'
   characters pointed to by 'buffer', and return 1.
   Return 0 if it would not fit. */
unsigned char
format_dec_unsigned(unsigned v,
                    char* buffer, unsigned n)
{
	const unsigned powers[] = { 1u, 10u, 100u, 1000u, 10000u };
	unsigned char i;

	if (!buffer || !n) return 0;
	for (i = 1; i < sizeof powers/sizeof powers[0]; i++)
		if (v < powers[i]) break;

	if (n-1 <= i) return 0;
	buffer[i] = 0;
	do {
		buffer[--i] = '0' + v%10;
		v /= 10;
	} while (i);

	return 1;
}

A few simple modifications will expand it to work with unsigned long values:

#if UINT_MAX >= 1000ul*1000ul*1000ul
#error This code supports 'int' less than 100000 only.
#endif
unsigned char
format_dec_unsigned_long(unsigned long v,
                         char* buffer, unsigned n)
{
	const unsigned long powers[] = {
		1ul, 10ul, 100ul, 1000ul, 10000ul,
		100*1000ul, 1000*1000ul, 10000*1000ul,
		100*1000ul*1000ul, 1000*1000ul*1000ul
	};
	unsigned char i;

	if (!buffer || !n) return 0;
	for (i = 1; i < sizeof powers/sizeof powers[0]; i++)
		if (v < powers[i]) break;

	if (n-1 <= i) return 0;
	buffer[i] = 0;
	do {
		buffer[--i] = '0' + v%10;
		v /= 10;
	} while (i);

	return 1;
}

To support signed int you could add:

unsigned char
format_dec_signed(unsigned v,
                  char* buffer, unsigned n)
{
	if (!buffer || !n) return 0;
	if (v < 0) {
		buffer[0] = '-';
	} else {
		buffer[0] = '+';
	}
	return format_dec_unsigned(v, base, buffer+1, n-1);
}

(Most of our compilers will even replace the function call at the end with a jump, saving one level of precious call-stack space).


If you wish to output integers in other bases, you can either create a number of specialized output routines similar to the above:

#include <limits.h>

#if UINT_MAX >= 0x1000
#error This code supports 'int' less than 0x10000 only.
#endif

/* Put the hexadecimal representation of 'v' into the array of 'n'
   characters pointed to by 'buffer', and return 1.
   Return 0 if it would not fit. */
unsigned char
format_hex_unsigned(unsigned v,
                    char* buffer, unsigned n)
{
	const char digits[16] = "0123456789abcdef";
	const unsigned powers[] = { 0x1u, 0x10u, 0x100u, 0x1000u };
	unsigned char i;

	if (!buffer || !n) return 0;
	for (i = 1; i < sizeof powers/sizeof powers[0]; i++)
		if (v < powers[i]) break;

	if (n-1 <= i) return 0;
	buffer[i] = 0;
	do {
		buffer[--i] = digits[v%0x10];
		v /= 0x10;
	} while (i);

	return 1;
}

Note: The different method of getting each digit - the decimal digits '0' through '9' are guaranteed by the C standard to be sequential in the runtime character set, but there is no such guarantee for alphabetic characters.

Or, you could create a more-flexible "inverse-strtoul":

/* Put the base-'base' representation of 'v' into the array of 'n'
   characters pointed to by 'buffer', and return 1.
   Return 0 if it would not fit, or if 'base' is invalid.
 */
unsigned char
format_unsigned_int(unsigned int v, unsigned char base,
                    char* buffer, unsigned n)
{
	const char digits[36] = "0123456789abcdefghijklmnopqrstuvwxyz";
	unsigned base_pow = base, b;
	unsigned char i = 1;

	if (base < 2 || base > sizeof digits) return 0;
	if (!buffer || !n) return 0;

	/* generate powers on the fly */
	for (b = base_pow * base;
	     b/base == base_pow; /* wraparound => overflow */
	     b = base_pow * base;) {

		if (v < b) break;
		base_pow = b; i++
	}

	if (n-1 <= i) return 0;
	buffer[i] = 0;
	do {
		buffer[--i] = digits[v%base];
		v /= base;
	} while (i);

	return 1;
}
 

But remember, as you add complexity, you generally add code size and bugs.

 

March 2006.