Author Topic: Convert large hex number to decimal  (Read 4487 times)

0 Members and 1 Guest are viewing this topic.

Offline ArCoNTopic starter

  • Contributor
  • Posts: 24
  • Country: dk
Convert large hex number to decimal
« on: June 18, 2019, 12:12:55 pm »
Hey All

How can I convert ex. 0xFFFFFFFFFF to decimal (1099511627775) on a 8bit mcu (pic18, xc8)?

ex.
byte 0 = 5
byte 1 = 7
byte 2 = 7
and so on or vice versa

Thanks

 

Offline Psi

  • Super Contributor
  • ***
  • Posts: 9951
  • Country: nz
Re: Convert large hex number to decimal
« Reply #1 on: June 18, 2019, 12:34:47 pm »
The fact that you are asking about doing it on a 8bit cpu makes me think you are working in ASM rather than C?

Greek letter 'Psi' (not Pounds per Square Inch)
 

Online Kleinstein

  • Super Contributor
  • ***
  • Posts: 14202
  • Country: de
Re: Convert large hex number to decimal
« Reply #2 on: June 18, 2019, 12:52:22 pm »
C has standard functions like itoa and ltoa to convert integer, long integer and so on to ASCII representation.
For even longer numbers not supported by the compiler one could to the math yourself: divide by 10 and get the reminder for the last digit and the result as the input to the next step until one gets 0.
In ASM it is the same - mainly the divide by 10 part.
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6264
  • Country: fi
    • My home page and email address
Re: Convert large hex number to decimal
« Reply #3 on: June 18, 2019, 01:19:20 pm »
Usually, you write a function that calculates the quotient and remainder of a bignum divided by ten.  In pseudo-C,
Code: [Select]
char *to_decimal(char *buf, int len, UNSIGNED_TYPE value)
{
    char *ptr = buf + len;

    *(--ptr) = '\0';

    do {
        *(--ptr) = '0' + (value % 10);
        value /= 10;
    } while (value);

    return ptr;
}

However, if that is a problem (the architecture does not have hardware division at all, for example), you can do the same with a couple of helper functions.  You need:
Code: [Select]
/* a < b: -1,   a == b: 0,   a > b: -1 */
signed char    biguint_cmp(unsigned char a[], unsigned char b[], unsigned char n);

/* a += b, returns 0 if successful, overflow byte otherwise */
unsigned char  biguint_add(unsigned char a[], unsigned char b[], unsigned char n);

/* a -= b, returns 0 if successful, nonzero if b > a (and a garbled?) */
unsigned char  biguint_sub(unsigned char a[], unsigned char b[], unsigned char n);

/* a = 10*a + c.  Returns 0 if successful, overflow byte otherwise. */
unsigned char  biguint_mul10(unsigned char a[], unsigned char n, unsigned char c);
The idea is that you create two temporary biguints the same size as the value to be printed, one and tmp. You initialize tmp to 1 (i.e., all zeros except least significant byte 1).
First, you find the largest power of ten not greater than the value.  You do this by checking if tmp is less than the value.  If it is, you copy it to one, then multiply tmp by 10.  (If that overflows, abort.)  Remember the power of ten. Then, you recheck.
For each digit, you substract one from the value, until the result would be negative.  The number of times you can do this, is the digit in this place.  You redo this for each lesser power of ten, down to ones (\$10^0 = 1\$).  (To construct the power of ten, just start from 1, and multiply it by 10 one fewer times than last time.)

The reason for biguint_mul10() having a small additive constant (between 0 and 9, inclusive), is that with it, you can construct multibyte integers from decimal strings, by reading the decimal from left (most significant digit) to right (least significant digit), and multiplying your temporary result by 10 and adding the corresponding digit.

If this is a bottleneck, you can speed it up by doing pairs of decimal digits; i.e. using 100 instead of 10.  (Note that 100 = 64 + 32 + 4, so you can write a*100 as (a<<6)+(a<<5)+(a<<2). You'll only need to skip a possible high 0 when displaying the decimal result.)
 

Online oPossum

  • Super Contributor
  • ***
  • Posts: 1417
  • Country: us
  • Very dangerous - may attack at any time
Re: Convert large hex number to decimal
« Reply #4 on: June 18, 2019, 01:53:49 pm »
Typically in C you would use _itoa() if available, or printf().

If you want to do it differently...
  • Addition / Subtraction only
  • In order generation of digits
  • Leading zero suppression
  • User helper functions for various bit sizes and signed support
  • Can work with other number bases (but not a good choice for octal/hex)

Code: [Select]
static const unsigned long dv[] = {
//  4294967296 // 32 bit unsigned max
1000000000, // +0
100000000, // +1
  10000000, // +2
   1000000, // +3
    100000, // +4
//       65535 // 16 bit unsigned max    
     10000, // +5
      1000, // +6
       100, // +7
        10, // +8
         1, // +9
};

static void xtoa(unsigned long x, const unsigned long *dp)
{
char c;
unsigned long d;
if(x) {
while(x < *dp) ++dp;
do {
d = *dp++;
c = '0';
while(x >= d) ++c, x -= d;
putc(c);
} while(!(d & 1));
} else
putc('0');
}

Usage example...

Code: [Select]
void printf(char *format, ...)
{
char c;
int i;
long n;

va_list a;
va_start(a, format);
while(c = *format++) {
if(c == '%') {
switch(c = *format++) {
case 's': // String
puts(va_arg(a, char*));
break;
case 'c': // Char
putc(va_arg(a, char));
break;
case 'i': // 16 bit signed Integer
case 'u': // 16 bit Unsigned integer
i = va_arg(a, int);
if(c == 'i' && i < 0) i = -i, putc('-');
xtoa((unsigned)i, dv + 5);
break;
case 'l': // 32 bit signed Long
case 'n': // 32 bit uNsigned loNg
n = va_arg(a, long);
if(c == 'l' &&  n < 0) n = -n, putc('-');
xtoa((unsigned long)n, dv);
break;
case 0: return;
default: goto bad_fmt;
}
} else
bad_fmt: putc(c);
}
va_end(a);
}

 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14476
  • Country: fr
Re: Convert large hex number to decimal
« Reply #5 on: June 18, 2019, 01:54:19 pm »
If you're using C, see above.

Note that you have to check whether your "large" numbers can be represented directly with a scalar type with your C compiler. The example you gave would require more than 32 bits, so the C type would be uint64_t for instance. Check that the C compiler you're going to use can directly handle 64-bit integers (if it's recent enough and compliant enough, it should). Obviously, on an 8-bit target, it's not going to be very efficient, but it should work. If your large numbers can't be represented with a scalar type, you'll first have to create a specific library to handle them.

Also note that many 8-bitters have BCD instructions (see: https://en.wikipedia.org/wiki/Binary-coded_decimal ), so if you're going to implement that in assembly, that would be wise to use them. In C, the optimizer may make use of them instead of pure divide/modulo, but I'd be surprised if it did.

 

Offline JPortici

  • Super Contributor
  • ***
  • Posts: 3461
  • Country: it
Re: Convert large hex number to decimal
« Reply #6 on: June 18, 2019, 02:44:20 pm »
Yes, XC8 from V2.0 has support for 64bit integers in PIC18. (2.05 added support for 64bit integers in enhanced midrange pic too.. PIC16F1xxx and PIC16F1xxxx)

However, XC8 in C99 mode don't have support for itoa() and simillar, altough it's trivial to write your implementation
 

Online oPossum

  • Super Contributor
  • ***
  • Posts: 1417
  • Country: us
  • Very dangerous - may attack at any time
Re: Convert large hex number to decimal
« Reply #7 on: June 18, 2019, 05:35:41 pm »
I didn't notice that was 40 bits of hex.

In that case the code I previously posted just requires a larger table and change to uint64_t

Code: [Select]
static const uint64_t dv[] = {
//  18446744073709551616    // 64 bit unsigned max
    10000000000000000000,   // +0
     1000000000000000000,   // +1
      100000000000000000,   // +2
       10000000000000000,   // +3
        1000000000000000,   // +4
//       281474976710656    // 48 bit unsigned max
         100000000000000,   // +5
          10000000000000,   // +6
           1000000000000,   // +7
            100000000000,   // +8
             10000000000,   // +9
//            4294967296    // 32 bit unsigned max
              1000000000,   // +10
               100000000,   // +11
                10000000,   // +12
                 1000000,   // +13
                  100000,   // +14
//                 65535    // 16 bit unsigned max     
                   10000,   // +15
                    1000,   // +16
                     100,   // +17
                      10,   // +18
                       1,   // +19
};

static void xtoa(uint64_t x, const uint64_t *dp)
...
 

Offline chris_leyson

  • Super Contributor
  • ***
  • Posts: 1541
  • Country: wales
Re: Convert large hex number to decimal
« Reply #8 on: June 18, 2019, 06:10:01 pm »
There are serial shift methods for converting bin to bcd and bcd to bin. The method is explained in Xilinx xapp029 and is very fast if done in HDL. http://www.ingelec.uns.edu.ar/dclac2558/BCD2BIN.PDF
 

Offline rhodges

  • Frequent Contributor
  • **
  • Posts: 306
  • Country: us
  • Available for embedded projects.
    • My public libraries, code samples, and projects for STM8.
Re: Convert large hex number to decimal
« Reply #9 on: June 18, 2019, 09:24:38 pm »
I have PIC18 assembly to convert binary to BCD. From there, it is easy to get ASCII decimal. I'll dig it up if anyone is interested.
Currently developing STM8 and STM32. Past includes 6809, Z80, 8086, PIC, MIPS, PNX1302, and some 8748 and 6805. Check out my public code on github. https://github.com/unfrozen
 

Offline ArCoNTopic starter

  • Contributor
  • Posts: 24
  • Country: dk
Re: Convert large hex number to decimal
« Reply #10 on: June 19, 2019, 06:45:47 am »
Hey

Thanks for the answers.

Maybe i went a little fast.  ::)

I only have licence for Xc8 v1.32 (c/c++ compiler). and it only supports 32bit variables.
i need to be able to convert a max of 14 digits in decimal

the problem i have now

I have 5 bytes

array[0xFE,00,00,05,75] LSB -> Dec (01090921694580)

its should ende op in array[0x00,01,00,09,...,08,00] (14bytes)

Thanks again  ;D
« Last Edit: June 19, 2019, 09:18:52 am by ArCoN »
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4199
  • Country: us
Re: Convert large hex number to decimal
« Reply #11 on: June 19, 2019, 08:39:48 am »
5 bytes isn’t enough for 14 decimal digits.  It only gives you a little bit more than 12 digits.


The point of the previous posts is that the conversion is not difficult to do yourself, using smaller datatypes.
All you need is a div10 function that give both quotient and remainder,  OR multi-byte addition and subtraction.

 

Offline ArCoNTopic starter

  • Contributor
  • Posts: 24
  • Country: dk
Re: Convert large hex number to decimal
« Reply #12 on: June 19, 2019, 08:56:43 am »
I maybe have 7 byte later.

like oPossum wrote it wont be a problem if i had 64bit varible, but how do i do it without?

how do i find digit 13 (1)?
« Last Edit: June 19, 2019, 09:31:25 am by ArCoN »
 

Offline NorthGuy

  • Super Contributor
  • ***
  • Posts: 3146
  • Country: ca
Re: Convert large hex number to decimal
« Reply #13 on: June 19, 2019, 12:29:48 pm »
like oPossum wrote it wont be a problem if i had 64bit varible, but how do i do it without?

Make your own. All you need is addition, subtraction, and comparison, which are very easy to write in assembler.
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6264
  • Country: fi
    • My home page and email address
Re: Convert large hex number to decimal
« Reply #14 on: June 19, 2019, 12:44:37 pm »
Consider this example C program:
Code: [Select]
#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.
« Last Edit: June 19, 2019, 12:47:24 pm by Nominal Animal »
 
The following users thanked this post: oPossum

Online oPossum

  • Super Contributor
  • ***
  • Posts: 1417
  • Country: us
  • Very dangerous - may attack at any time
Re: Convert large hex number to decimal
« Reply #15 on: June 19, 2019, 12:54:32 pm »
Using assembly has been suggested a few times. One of the reasons for this is that assembly code has access to the ALU's carry flag, but C does not. That greatly simplifies multibyte arithmetic and results in code that is smaller and faster.

The fastest generalized way I know of the do binary to BCD (and then BCD to ASCII) in 8 bit PIC is shown here: http://www.piclist.com/techref/microchip/math/radix/b2bp-32b10d.htm

That code can easily be extended to any number of bits.
« Last Edit: June 19, 2019, 12:56:11 pm by oPossum »
 

Offline ArCoNTopic starter

  • Contributor
  • Posts: 24
  • Country: dk
Re: Convert large hex number to decimal
« Reply #16 on: June 20, 2019, 01:27:41 pm »
Hi all

Got it to work with Nominal Animal latest post.
I made a some adjustments to make it fit my app ;D

Thanks a lot

Edit  :palm:

Code: [Select]

char OutArray[14];
//unsigned char InArray[] = {0xFE,0x00,0x00,0x05,0x74}; //brain fart
unsigned char InArray[] = {0x74,0x05,0x00,0x00,0xFE}; //MSB
memset(OutArray, 0, sizeof(OutArray));
decimal(OutArray, sizeof(OutArray), InArray, sizeof(InArray));


void decimal(unsigned char *Out, unsigned char MaxOutSize, unsigned char *In, unsigned char InCnt)
{
    unsigned char *p = Out;
    unsigned char z, r, i, ByteCnt = 0;
    unsigned int t;
   
    do {
        /* Divide b[] by ten. */
        z = r = 0;
        i = InCnt;
       
        while (i-- > 0)
        {
            t = 256 * (unsigned int)r + In[i];  /* t = 0 .. 2559 */
            r = t % 10;                    /* Remainder */
            In[i] = t / 10;
            z |= In[i];                     /* Make z nonzero if b[i] is nonzero. */
        }
       
        *(p++) = r;
        /* Loop iff b[] is still nonzero. */
    } while (z && (++ByteCnt < MaxOutSize));
}


« Last Edit: June 21, 2019, 09:24:53 am by ArCoN »
 

Offline jesuscf

  • Frequent Contributor
  • **
  • Posts: 499
  • Country: ca
Re: Convert large hex number to decimal
« Reply #17 on: June 26, 2019, 05:55:04 am »
Why not the double-dabble algorithm?  It doesn't require division.  Also, it is easy to implement in assembly for any number of bits, but here it is in C:

Code: [Select]
#include <stdio.h>
#include <stdlib.h>

void Double_Dabble (unsigned char * hex, unsigned char * bcd, int hexbytes, int bcdbytes)
{
int i, j;
unsigned char msb_hex;

// Initialize bcd to zero
for(j=0; j<bcdbytes; j++) bcd[j]=0;

for(i=0; i<(hexbytes*8); i++) // The loop has to be repeated for all the bits of hex[].
{
// Store the most significant bit of hex[]
msb_hex=hex[hexbytes-1]&0x80?1:0;

// Shift left hex[] one bit
for(j=(hexbytes-1); j>0; j--)
{
hex[j]<<=1;
if(hex[j-1]&0x80) hex[j]|=0x01;
}
hex[0]<<=1;
hex[0]+=msb_hex; // This way hex[] remains un-altered after calling this function.

// Multiply bcd[] by two.  Start by applying correction for digits larger than 4.
for(j=0; j<bcdbytes; j++)
{
if((bcd[j]&0xf0)>0x40) bcd[j]+=0x30; // Correction to MSD
if((bcd[j]&0x0f)>0x04) bcd[j]+=0x03; // Correction to LSD
}

// Shift left the corrected bcd[] one bit.  This multiplies bcd[] by 2.
for(j=(bcdbytes-1); j>0; j--)
{
bcd[j]<<=1;
if(bcd[j-1]&0x80) bcd[j]|=0x01;
}
bcd[0]<<=1;
bcd[0]+=msb_hex; // Finally add the bit we saved before
}
}

void Dump_Hex (unsigned char * hex, int n)
{
int j;
for(j=(n-1); j>=0; j--)
{
printf("%02X", hex[j]);
}
}

unsigned char InArray[] = {0x74,0x05,0x00,0x00,0xFE}; // MSB
unsigned char OutArray[7];

void main (void)
{
Double_Dabble (InArray, OutArray, sizeof(InArray), sizeof(OutArray));
printf("InArray=");
Dump_Hex(InArray, sizeof(InArray));
printf("\n");
printf("OutArray=");
Dump_Hex(OutArray, sizeof(OutArray));
printf("\n");
}

The output:

Code: [Select]
InArray=FE00000574
OutArray=01090921694580

Homer: Kids, there's three ways to do things; the right way, the wrong way and the Max Power way!
Bart: Isn't that the wrong way?
Homer: Yeah, but faster!
 
The following users thanked this post: SiliconWizard

Offline DDunfield

  • Regular Contributor
  • *
  • Posts: 173
  • Country: ca
Re: Convert large hex number to decimal
« Reply #18 on: June 26, 2019, 11:30:32 pm »
Why not the double-dabble algorithm?  It doesn't require division.  Also, it is easy to implement in assembly for any number of bits, but here it is in C:

I find DD slow (especially in C) as it needs to shift by bits, but it can work well in ASM, especially if you don't have 16-bit division.
There are a few simple optimizations you can do to make it better, most notably you can do the digit correction within the second shift loop, thereby eliminating a loop, something like:

Code: [Select]
void Double_Dabble(U8 *bin, U8 *bcd, U16 binbytes, U16 bcdbytes)
{
    U16 i, j;
    U8  c, d;

    // Initialize bcd to zero
    memset(bcd, 0, bcdbytes);

    for(i = binbytes*8; i; --i) {       // Do all bits in bin[]
        // Shift left bin[] one bit
        for(j = c = 0; j < binbytes; ++j) {
            d = bin[j];
            bin[j] = (d << 1) | c;
            c = d >> 7; }
        bin[0] |= c;        // Rotate in carry (keeps bin[] unmodified)

        // Multiply bcd[] by two (<<1), while correcting for digits > 4.
        // Note: c retains carry from bin[] shift above
        for(j = 0; j < bcdbytes; ++j) {
            if((d = bcd[j]) > 0x4F) d += 0x30;  // Correct MSD
            if((d & 0x0F) > 0x04)   d += 0x03;  // Correct LSD
            bcd[j] = (d << 1) | c;
            c = d >> 7; } }
}

If you do have 16 bit division, I prefer the carried divide method as it only loops by bytes. This is the one I keep in my toolbox for those cases where I need to output a big decimal number and don't have my longmath library linked:

Code: [Select]
// Convert 'size' (bytes) binary number 'bin' to ascii-decimal string 'ads'
//  ads     is assumed to be large enough for the maximum decimal number
//          which can be represented in 'size'*8 bits.
//  bin     contains binary number, byte order as defined by ENDIAN
//          Note: bin is destroyed (set to zero)
//  size    must range from 0 to 266 (bytes) - see note below
#define ENDIAN  1       // 1=Little, 2=Big
void bin2dec(U8 *ads, U8 *bin, U16 size)
{
    U16  i, j;
    U8   *p, m10, zf;

    // The approximation 118/49 (2.40816) will yield the correct maximum
    // output size for size=0-266 bytes (0-2128 input bits). At size=267,
    // it will undersize the output by 1. If you need to convert numbers
    // >2128 bits, you will need to use a less accurate conversion which
    // estimates high, keeping in mind that you must not overflow U16.
    // 171/71 works well, and can go up to size=383 (3064 bits), but you
    // must insure that ads[] has an extra byte available.
    *(p = ((size*118)/49)+ads+1) = 0;
    if(size) do {
        zf = m10 = 0;
#if ENDIAN == 1
        i = size; while(i--) {
#elif ENDIAN == 2
        for(i=0; i < size; ++i) {
#else
    #error "ENDIAN must be 1(little) or 2(big)"
#endif
            m10 = (j = ((U16)m10 << 8) + bin[i]) % 10;
            zf |= (bin[i] = j / 10); }
        *--p = m10 + '0'; }
    while(zf);  // Until we hit zero
    while(*ads++ = *p++);       // Shift result up to start of buffer.
}

Basically a variation of what "Nominal Animal" posted.

Dave
« Last Edit: June 26, 2019, 11:34:44 pm by DDunfield »
 
The following users thanked this post: oPossum

Offline jesuscf

  • Frequent Contributor
  • **
  • Posts: 499
  • Country: ca
Re: Convert large hex number to decimal
« Reply #19 on: June 27, 2019, 06:17:17 am »
If you do have 16 bit division, I prefer the carried divide method as it only loops by bytes. This is the one I keep in my toolbox for those cases where I need to output a big decimal number and don't have my longmath library linked:

I may be wrong, but I don't think the PIC18 has a divide instruction of any size.
Homer: Kids, there's three ways to do things; the right way, the wrong way and the Max Power way!
Bart: Isn't that the wrong way?
Homer: Yeah, but faster!
 

Offline Doctorandus_P

  • Super Contributor
  • ***
  • Posts: 3360
  • Country: nl
Re: Convert large hex number to decimal
« Reply #20 on: June 29, 2019, 09:31:56 pm »
I'm a bit surprised that ldiv() has not beenmentioned in this tread.
In C both '%' and '/' do essentially the same operation, but return a different result.
ldiv( ) also does the same, but packs both the result and remainer in a structure.

Here an example of a function that uses ldiv( ) to format a number into a "fixed precision" string.
//===========================================================================
// String Print a number with Fixed widht and precision (decimal point).
// Signed version.
void SPrintFixed( char* Buf, int32_t n, int8_t Length, int8_t Precision) {
   char Sign;
   ldiv_t   Number;

   if( n < 0) {
      n = -n;
      Sign = '-';
   }
   else
      Sign = ' ';

   Buf[Length] = 0x00;
   Number.quot = n;
   do {
      Number = ldiv( Number.quot, 10);
      Buf[--Length] = Number.rem + '0';
      if( --Precision == 0)
         Buf[--Length] = '.';
   }while( (Length > 0) && ((Number.quot) || (Precision >= 0)) );

   if( Sign == '-') {
      if( Length > 0)
         Buf[--Length] = '-';
   }

   while(Length > 0)      // Fill remaining space with spaces.
      Buf[--Length] = ' ';
}
 

Offline jesuscf

  • Frequent Contributor
  • **
  • Posts: 499
  • Country: ca
Re: Convert large hex number to decimal
« Reply #21 on: July 01, 2019, 04:54:07 pm »
I'm a bit surprised that ldiv() has not been mentioned in this tread.

If the CPU doesn't provide a division instruction, then using division to convert a multi byte binary to decimal would be slow.  This is the case for the PIC18.  On the other hand if the processor provides a decimal adjust instruction then the double dabble algorithm can be implemented very efficiently in assembly language.  The PIC18 does provide a decimal adjust instruction: DAW (Decimal Adjust W register, after addition).  I am not proficient in PIC18 assembly, but I can illustrate what I mean with this routine for the 8051 which also has a decimal adjust instruction: 'DA A' (Decimal Adjust Accumulator, after addition).

Code: [Select]
;----------------------------------------------------
; Converts the 32-bit hex number in 'x' to a
; 10-digit packed BCD in 'bcd' using the
; double-dabble algorithm.
;----------------------------------------------------
hex2bcd:
clr a
mov bcd+0, a ; Initialize BCD to 00-00-00-00-00
mov bcd+1, a
mov bcd+2, a
mov bcd+3, a
mov bcd+4, a
mov r2, #32  ; Loop counter.
hex2bcd_L0:
; Shift binary left
mov a, x+3
mov c, acc.7 ; This way x remains unchanged!
mov r1, #4
mov r0, #(x+0)
hex2bcd_L1:
mov a, @r0
rlc a
mov @r0, a
inc r0
djnz r1, hex2bcd_L1 
; Perform bcd + bcd + carry using BCD arithmetic
mov r1, #5
mov r0, #(bcd+0)
hex2bcd_L2:   
mov a, @r0
addc a, @r0
da a
mov @r0, a
inc r0
djnz r1, hex2bcd_L2
djnz r2, hex2bcd_L0
ret
Homer: Kids, there's three ways to do things; the right way, the wrong way and the Max Power way!
Bart: Isn't that the wrong way?
Homer: Yeah, but faster!
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf