Convert large hex number to decimal - Page 1
Forum section: electronics / general

author: ArCoN
author_rank: Contributor
timestamp: June 18, 2019, 12:12:55 pm
content: Hey AllHow can I convert ex. 0xFFFFFFFFFF to decimal (1099511627775) on a 8bit mcu (pic18, xc8)?ex.byte 0 = 5byte 1 = 7byte 2 = 7and so on or vice versaThanks

author: Psi
author_rank: Super Contributor
timestamp: June 18, 2019, 12:34:47 pm
content: The fact that you are asking about doing it on a 8bit cpu makes me think you are working in ASM rather than C?

author: Kleinstein
author_rank: Super Contributor
timestamp: June 18, 2019, 12:52:22 pm
content: 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.

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 18, 2019, 01:19:20 pm
content: Usually, you write a function that calculates the quotient and remainder of a bignum divided by ten. In pseudo-C,Code: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:/* 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,oneandtmp. You initializetmpto 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 iftmpis less than the value. If it is, you copy it toone, then multiplytmpby 10. (If that overflows, abort.) Remember the power of ten. Then, you recheck.For each digit, you substractonefrom 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 forbiguint_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.)

author: oPossum
author_rank: Super Contributor
timestamp: June 18, 2019, 01:53:49 pm
content: Typically in C you would use _itoa() if available, or printf().If you want to do it differently...Addition / Subtraction onlyIn order generation of digitsLeading zero suppressionUser helper functions for various bit sizes and signed supportCan work with other number bases (but not a good choice for octal/hex)Code:static const unsigned long dv[] = {// 4294967296// 32 bit unsigned max1000000000,// +0100000000,// +110000000,// +21000000,// +3100000,// +4// 65535// 16 bit unsigned max10000,// +51000,// +6100,// +710,// +81,// +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));} elseputc('0');}Usage example...Code: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':// Stringputs(va_arg(a, char*));break;case 'c':// Charputc(va_arg(a, char));break;case 'i':// 16 bit signed Integercase 'u':// 16 bit Unsigned integeri = va_arg(a, int);if(c == 'i' && i < 0) i = -i, putc('-');xtoa((unsigned)i, dv + 5);break;case 'l':// 32 bit signed Longcase 'n':// 32 bit uNsigned loNgn = 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;}} elsebad_fmt:putc(c);}va_end(a);}

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 18, 2019, 01:54:19 pm
content: 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.

author: JPortici
author_rank: Super Contributor
timestamp: June 18, 2019, 02:44:20 pm
content: 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

author: oPossum
author_rank: Super Contributor
timestamp: June 18, 2019, 05:35:41 pm
content: 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_tCode:static const uint64_t dv[] = {// 18446744073709551616 // 64 bit unsigned max10000000000000000000, // +01000000000000000000, // +1100000000000000000, // +210000000000000000, // +31000000000000000, // +4// 281474976710656 // 48 bit unsigned max100000000000000, // +510000000000000, // +61000000000000, // +7100000000000, // +810000000000, // +9// 4294967296 // 32 bit unsigned max1000000000, // +10100000000, // +1110000000, // +121000000, // +13100000, // +14// 65535 // 16 bit unsigned max10000, // +151000, // +16100, // +1710, // +181, // +19};static void xtoa(uint64_t x, const uint64_t *dp)...

author: chris_leyson
author_rank: Super Contributor
timestamp: June 18, 2019, 06:10:01 pm
content: 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

author: rhodges
author_rank: Frequent Contributor
timestamp: June 18, 2019, 09:24:38 pm
content: 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.

author: ArCoN
author_rank: Contributor
timestamp: June 19, 2019, 06:45:47 am
content: HeyThanks 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 decimalthe problem i have nowI have 5 bytesarray[0xFE,00,00,05,75] LSB -> Dec (01090921694580)its should ende op in array[0x00,01,00,09,...,08,00] (14bytes)Thanks again

author: westfw
author_rank: Super Contributor
timestamp: June 19, 2019, 08:39:48 am
content: 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.

author: ArCoN
author_rank: Contributor
timestamp: June 19, 2019, 08:56:43 am
content: 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)?

author: NorthGuy
author_rank: Super Contributor
timestamp: June 19, 2019, 12:29:48 pm
content: Quote from: ArCoN on June 19, 2019, 08:56:43 amMake your own. All you need is addition, subtraction, and comparison, which are very easy to write in assembler.

author: Nominal Animal
author_rank: Super Contributor
timestamp: June 19, 2019, 12:44:37 pm
content: Consider this example C program:Code:#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';buf = '\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; /* t = 0 .. 2559 */r = t % 10; /* Remainder */b = t / 10;z |= b; /* Make z nonzero if b 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, "-h") || !strcmp(argv, "--help")) {fprintf(stderr, "\n");fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv);fprintf(stderr, " %s HEXNUM [ HEXNUM ... ]\n", argv);fprintf(stderr, "\n");return EXIT_FAILURE;}for (arg = 1; arg < argc; arg++) {hex = argv;if (hex == '0' && (hex == 'x' || hex == 'X'))hex += 2;len = strspn(hex, "0123456789ABCDEFabcdef");if (len < 1 || len != strlen(hex)) {fprintf(stderr, "%s: Not a hexadecimal integer.\n", argv);return EXIT_FAILURE;} elseif (len > 510) {fprintf(stderr, "%s: Too long; the internal limit is 255 bytes.\n", argv);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);return EXIT_FAILURE;}}if (nlen < len) {nlen = len; /* Overestimate! */free(nptr);nptr = calloc(nlen, sizeof nptr);if (!nptr) {fprintf(stderr, "%s: Too long; out of memory.\n", argv);return EXIT_FAILURE;}}/* Parse hex. */i = 0;while (len >= 2) {nptr = hexdigit(hex[len - 2]) * 16+ hexdigit(hex[len - 1]);i++;len -= 2;}if (len > 0) {nptr = hexdigit(hex);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);for (i = len-1; i >= 0; i--)printf("%02x", nptr);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 thedecimal()function.The core of the function is under the/* Divide b[] by ten */comment. The loop doeslong division, with radix 256. Note that the value we divide (/) or take the modulus of (%), is always between 0 and 2559 inclusive; never larger.zis a variable that remains zero over the loop, if and only if all bytes ofb[]are zero. This is easiest and fastest to do using the binary OR operator. The outerdo { ... } while (z);loop iterates, untilb[]is all zeros.ris 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,ris the remainder of the entire divide-the-large-number-by-ten operation.iis the inner loop variable. It is decremented, because the inner loop goes from most significant towards least significant byte;balways being the least significant byte. (That is, the value of the binary number isb + 256*b + 256*256*b + 256*256*256*b + ..., i.e. we use least significant byte first, or little-endian byte order here. It is mathematically simpler.)tis 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.Becausenis 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 calculatex/10andx%10for 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.

author: oPossum
author_rank: Super Contributor
timestamp: June 19, 2019, 12:54:32 pm
content: 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.htmThat code can easily be extended to any number of bits.

author: ArCoN
author_rank: Contributor
timestamp: June 20, 2019, 01:27:41 pm
content: Hi allGot it to work with Nominal Animal latest post.I made a some adjustments to make it fit my appThanks a lotEditCode:char OutArray;//unsigned char InArray[] = {0xFE,0x00,0x00,0x05,0x74}; //brain fartunsigned char InArray[] = {0x74,0x05,0x00,0x00,0xFE}; //MSBmemset(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; /* t = 0 .. 2559 */r = t % 10; /* Remainder */In = t / 10;z |= In; /* Make z nonzero if b is nonzero. */}*(p++) = r;/* Loop iff b[] is still nonzero. */} while (z && (++ByteCnt < MaxOutSize));}

author: jesuscf
author_rank: Frequent Contributor
timestamp: June 26, 2019, 05:55:04 am
content: 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:#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 zerofor(j=0; j<bcdbytes; j++) bcd=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 bitfor(j=(hexbytes-1); j>0; j--){hex<<=1;if(hex[j-1]&0x80) hex|=0x01;}hex<<=1;hex+=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&0xf0)>0x40) bcd+=0x30; // Correction to MSDif((bcd&0x0f)>0x04) bcd+=0x03; // Correction to LSD}// Shift left the corrected bcd[] one bit. This multiplies bcd[] by 2.for(j=(bcdbytes-1); j>0; j--){bcd<<=1;if(bcd[j-1]&0x80) bcd|=0x01;}bcd<<=1;bcd+=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);}}unsigned char InArray[] = {0x74,0x05,0x00,0x00,0xFE}; // MSBunsigned char OutArray;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:InArray=FE00000574OutArray=01090921694580

author: DDunfield
author_rank: Regular Contributor
timestamp: June 26, 2019, 11:30:32 pm
content: Quote from: jesuscf on June 26, 2019, 05:55:04 amI 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:void Double_Dabble(U8 *bin, U8 *bcd, U16 binbytes, U16 bcdbytes){U16 i, j;U8 c, d;// Initialize bcd to zeromemset(bcd, 0, bcdbytes);for(i = binbytes*8; i; --i) { // Do all bits in bin[]// Shift left bin[] one bitfor(j = c = 0; j < binbytes; ++j) {d = bin;bin = (d << 1) | c;c = d >> 7; }bin |= c; // Rotate in carry (keeps bin[] unmodified)// Multiply bcd[] by two (<<1), while correcting for digits > 4.// Note: c retains carry from bin[] shift abovefor(j = 0; j < bcdbytes; ++j) {if((d = bcd) > 0x4F) d += 0x30; // Correct MSDif((d & 0x0F) > 0x04) d += 0x03; // Correct LSDbcd = (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:// 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=Bigvoid 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 == 1i = size; while(i--) {#elif ENDIAN == 2for(i=0; i < size; ++i) {#else#error "ENDIAN must be 1(little) or 2(big)"#endifm10 = (j = ((U16)m10 << 8) + bin) % 10;zf |= (bin = j / 10); }*--p = m10 + '0'; }while(zf); // Until we hit zerowhile(*ads++ = *p++); // Shift result up to start of buffer.}Basically a variation of what "Nominal Animal" posted.Dave

author: jesuscf
author_rank: Frequent Contributor
timestamp: June 27, 2019, 06:17:17 am
content: Quote from: DDunfield on June 26, 2019, 11:30:32 pmI may be wrong, but I don't think the PIC18 has a divide instruction of any size.

author: Doctorandus_P
author_rank: Super Contributor
timestamp: June 29, 2019, 09:31:56 pm
content: 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 = '-';}elseSign = ' ';Buf = 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] = ' ';}

author: jesuscf
author_rank: Frequent Contributor
timestamp: July 01, 2019, 04:54:07 pm
content: Quote from: Doctorandus_P on June 29, 2019, 09:31:56 pmIf 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:;----------------------------------------------------; Converts the 32-bit hex number in 'x' to a; 10-digit packed BCD in 'bcd' using the; double-dabble algorithm.;----------------------------------------------------hex2bcd:clr amov bcd+0, a ; Initialize BCD to 00-00-00-00-00mov bcd+1, amov bcd+2, amov bcd+3, amov bcd+4, amov r2, #32 ; Loop counter.hex2bcd_L0:; Shift binary leftmov a, x+3mov c, acc.7 ; This way x remains unchanged!mov r1, #4mov r0, #(x+0)hex2bcd_L1:mov a, @r0rlc amov @r0, ainc r0djnz r1, hex2bcd_L1; Perform bcd + bcd + carry using BCD arithmeticmov r1, #5mov r0, #(bcd+0)hex2bcd_L2:mov a, @r0addc a, @r0da amov @r0, ainc r0djnz r1, hex2bcd_L2djnz r2, hex2bcd_L0ret