Consider this example C program:
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
/* Convert b[] to decimal string d. Clears b[] to zero.
*/
char *decimal(char *buf, int len,
uint8_t *b, uint8_t n)
{
char *p = buf + len;
uint16_t t;
uint8_t z, r, i;
/* Sanity checks */
if (!buf || len < 2)
return NULL;
if (n < 1) {
buf[0] = '0';
buf[1] = '\0';
return buf;
}
/* We construct the number right-to-left.
Start with the end-of-string mark. */
*(--p) = '\0';
do {
/* Buf too short? */
if (p == buf)
return NULL;
/* Divide b[] by ten. */
z = r = 0;
i = n;
while (i-->0) {
t = 256 * (uint16_t)r + b[i]; /* t = 0 .. 2559 */
r = t % 10; /* Remainder */
b[i] = t / 10;
z |= b[i]; /* Make z nonzero if b[i] is nonzero. */
}
/* r = b[] % 10. */
*(--p) = '0' + r;
/* Loop iff b[] is still nonzero. */
} while (z);
/* Done. */
return p;
}
unsigned char hexdigit(const char c)
{
switch (c) {
case 'F': case 'f': return 15;
case 'E': case 'e': return 14;
case 'D': case 'd': return 13;
case 'C': case 'c': return 12;
case 'B': case 'b': return 11;
case 'A': case 'a': return 10;
case '9': return 9;
case '8': return 8;
case '7': return 7;
case '6': return 6;
case '5': return 5;
case '4': return 4;
case '3': return 3;
case '2': return 2;
case '1': return 1;
case '0': return 0;
default: return 16;
}
}
int main(int argc, char *argv[])
{
char *sptr = NULL;
size_t slen = 0;
uint8_t *nptr = NULL;
int nlen = 0;
char *hex;
int len, arg, i;
if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s HEXNUM [ HEXNUM ... ]\n", argv[0]);
fprintf(stderr, "\n");
return EXIT_FAILURE;
}
for (arg = 1; arg < argc; arg++) {
hex = argv[arg];
if (hex[0] == '0' && (hex[1] == 'x' || hex[1] == 'X'))
hex += 2;
len = strspn(hex, "0123456789ABCDEFabcdef");
if (len < 1 || len != strlen(hex)) {
fprintf(stderr, "%s: Not a hexadecimal integer.\n", argv[arg]);
return EXIT_FAILURE;
} else
if (len > 510) {
fprintf(stderr, "%s: Too long; the internal limit is 255 bytes.\n", argv[arg]);
return EXIT_FAILURE;
}
if (slen < 3*len + 1) {
slen = 3*len + 1; /* Overestimate! */
free(sptr);
sptr = malloc(slen); /* sizeof (char) == 1 in C */
if (!sptr) {
fprintf(stderr, "%s: Too long; out of memory.\n", argv[arg]);
return EXIT_FAILURE;
}
}
if (nlen < len) {
nlen = len; /* Overestimate! */
free(nptr);
nptr = calloc(nlen, sizeof nptr[0]);
if (!nptr) {
fprintf(stderr, "%s: Too long; out of memory.\n", argv[arg]);
return EXIT_FAILURE;
}
}
/* Parse hex. */
i = 0;
while (len >= 2) {
nptr[i] = hexdigit(hex[len - 2]) * 16
+ hexdigit(hex[len - 1]);
i++;
len -= 2;
}
if (len > 0) {
nptr[i] = hexdigit(hex[0]);
i++;
len--;
}
/* Use len from now on to refer to the number of bytes. */
len = i;
/* Print the hex bytes: */
printf("%s:\n Hex: ", argv[arg]);
for (i = len-1; i >= 0; i--)
printf("%02x", nptr[i]);
printf("\n Dec: %s\n", decimal(sptr, slen, nptr, nlen));
fflush(stdout);
}
free(sptr);
free(nptr);
return EXIT_SUCCESS;
}
If you run it on the command line, giving it one or more hexadecimal numbers as parameters, it will display the value in decimal using the
decimal() function.
The core of the function is under the
/* Divide b[] by ten */ comment. The loop does
long division, with radix 256. Note that the value we divide (
/) or take the modulus of (
%), is always between 0 and 2559 inclusive; never larger.
z is a variable that remains zero over the loop, if and only if all bytes of
b[] are zero. This is easiest and fastest to do using the binary OR operator. The outer
do { ... } while (z); loop iterates, until
b[] is all zeros.
r is the remainder of the previous division, just like the remainder (of the subtraction) in long division. The inner loop starts from most significant bytes, ending with the least significant byte; after the loop,
r is the remainder of the entire divide-the-large-number-by-ten operation.
i is the inner loop variable. It is decremented, because the inner loop goes from most significant towards least significant byte;
b[0] always being the least significant byte. (That is, the value of the binary number is
b[0] + 256*b[1] + 256*256*b[2] + 256*256*256*b[3] + ... , i.e. we use least significant byte first, or little-endian byte order here. It is mathematically simpler.)
t is the temporary 16-bit (12-bit, since it is always between 0 and 2559, inclusive) value. The compiler must implement a 16-bit unsigned division and modulo operations for this code to work.
Because
n is an 8-bit unsigned integer, it limits the numbers to 255 bytes (255×8 = 2040 bits, 615 decimal digits). Just change it to a larger type to support bigger numbers.
Note that you need 0.30103 decimal digits per bit, or 2.40824 decimal digits per byte, rounded up; plus one for string-terminating nul (
\0) char in the string buffer.
I don't personally do PICs (I've kinda glommed on GCC on various architectures), but if the hardware has a 16-bit unsigned division, this is trivial to convert to hand-written assembly. Otherwise, you only need to find a way to calculate
x/10 and
x%10 for x in 0 to 2559, inclusive, for that particular architecture. If speed is not an issue, a simple loop (subtracting 10 each iteration, and counting the iterations) works fine.