General Forums >> Interesting Projects and Tutorials

Pages: 1
Mark Pappin

***

Reged: Nov 01 2004
Posts: 599
Loc: Brisbane, Australia
Tech Tip: Integer In, String Out
      #27823 - Tue May 29 2007 09:37 PM

I started posting these Tech Tip articles here (search for "+Tech +Tip" to find the others) a while ago, but then got side-tracked. Let's try again.

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:

Code:
/* 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:

Code:
#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:
Code:
#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:
Code:
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:
Code:
#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":
Code:
/* 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.

--------------------
Mark Pappin - HI-TECH Software


Post Extras: Print Post   Remind Me!   Notify Moderator  
John Payson

****

Reged: Oct 16 2003
Posts: 1135
Re: Tech Tip: Integer In, String Out [Re: Mark Pappin]
      #27834 - Wed May 30 2007 04:42 PM

In many circumstances, rather than preprocessing for length, it would be easier to simply write the code to process 'n' digits from right to left and pad with a specified byte value. While some circumstances may require that data be printed out without any leading blanks or zeroes, this could probably be handled just as well in the output routine as in the format routine.

Incidentally, many LCD modules can accept data in either left-to-right or right-to-left order. When outputting numeric data to such modules, the extra buffering step may be avoided. Simple set the module to accept characters right-to-left and then feed the digits in the order they're pulled off the end.


Post Extras: Print Post   Remind Me!   Notify Moderator  
mikerjModerator
Guru
****

Reged: Oct 19 2003
Posts: 1482
Loc: Devon, UK
Re: Tech Tip: Integer In, String Out [Re: Mark Pappin]
      #64772 - Thu Jul 10 2008 04:42 AM

Unless I am misunderstanding the intended usage, I think there may be a small bug in the examples above. For instance, say we want to print an unsigned char using format_dec_unsigned, and the value is 255. This should require a buffer of 4 bytes, 3 for digits, and one for the terminator. I believe a 4 byte buffer would cause the function to fail however:

Code:

unsigned char foo = 255;
char buff[4];

if( format_dec_unsigned( foo, buff, sizeof(buff) ) {
/* Won't get here */
puts( buff );
}



The reason is that the expression used to check the buffer length is sufficient has an off-by-one error:

Code:

format_dec_unsigned(unsigned v,
char* buffer, unsigned n)
{
const unsigned powers[] = { 1u, 10u, 100u, 1000u, 10000u };
unsigned char i;

/* Check the pointer to the buffer and size of buffer is valid */
if (!buffer || !n) return 0;

/* Find the first value in powers[] that is larger than v */
for (i = 1; i < sizeof powers/sizeof powers[0]; i++)
if (v < powers[i]) break;

/* v=255 so i will now hold 3 */

/* Check for enough buffer space.
This fails since 4-1 <= 3 is true, even though there is enough room.
I think this should be if (n <= i) return 0; */
if (n-1 <= i) return 0;




Post Extras: Print Post   Remind Me!   Notify Moderator  
Pages: 1



Extra information
0 registered and 6 anonymous users are browsing this forum.

Moderator:  jtemples, mikerj, Dan Henry, Andrew L 

Print Topic

Forum Permissions
      You cannot start new topics
      You cannot reply to topics
      HTML is enabled
      UBBCode is enabled

Rating:
Topic views: 7613

Rate this topic

Jump to

Contact Us | Privacy statement HI-TECH Software

Powered by UBB.threads™ 6.5.5