Stupid question about converting large binary values to dec - Page 1
Forum section: electronics / beginners

author: Moriambar
author_rank: Unknown
timestamp: May 04, 2021, 02:32:03 pm
content: Hi,I currently have counters "storing a number" in some parallel to serial shift register ('165 iirc).There are 5 of those registers, effectively representing a 40bit number.I would like to use an arduino or attiny or atmega chip, effectively an 8 bit mcu to translate these 40 bits into a decimal number and print it on a small screen.I think it's conceptually easy to read the values from the shift registers and store them into uint8_t variables. I think it's kinda easy to send the digits of the final decimal number to the screen.The problem is that I don't actually know how to convert those 5 8-bit unsigned integer to a sequence of decimal values obtaining the decimal representation of the number.I know this may sound kind of stupid, but I'd like to understand how to do it: this is my "fail big" project, and I already learned much from it, please let me learn more!Cheers

author: mvs
author_rank: Frequent Contributor
timestamp: May 04, 2021, 04:41:59 pm
content: There are multiple ways to convert binary number to its decimal representation.Most obvious one is to use modulo and divide by 10, but it is not very efficient on MCUs since this operations are quite expensive.Code:uint8_t buffer;uint8_t i;for (i=0; i < NUMBER_OF_DIGITS; i++) {buffer = number % 10;number /= 10;}More faster algorithm is double dabble. It will convert a binary number to its BCD representation (each nibble (4 bits) will encode one decimal digit).https://en.wikipedia.org/wiki/Double_dabble

author: rhodges
author_rank: Frequent Contributor
timestamp: May 04, 2021, 04:48:36 pm
content: Over the years, I have used a method that shifts the binary value (over N bytes) into a BCD (binary coded decimal), and then BCD to ASCII.Here is the basic idea:BBBB is the binaryDDDD is BCDShift BBBB left one bit, top bit goes into carry.Shift DDDD left one bit, carry goes into lowest bit.Do Decimal Adjust Accumulator (DAA):... If carry from low BCD digit, Add 0x06... If carry from high BCD digit, Add 0x60Repeat for every bit of original binary.That is the idea. There is an improvement by doing the DAA before doubling the BCD digits.New method:... Assume correction is needed on both BCD digits, so add 0x33 (0x66 before doubling).... Add to itself to double it.... If low BCD digit is < 8, undo the DAA by subtracting 0x03... If high BCD digit is < 8, undo the DAA by subtracting 0x30... Shift binary left by one bit into carry... Add carry to lowest BCD.This works best in assembly language, but you can probably fake it in C.

author: Benta
author_rank: Super Contributor
timestamp: May 04, 2021, 04:58:54 pm
content: It's pretty easy actually, but you need to know the trick.Integer divide (DIV) your number by 10, store remainder (MOD) in LSD.DIV 10 again, store remainder in next most LSD.keep dividing by 10 and storing the remainder in the following digits.Continue until your original number is zero. Or until so many iterations have run that fit to the 402equivalent number of decimal digits.Very nice for a tight little loop.

author: Manul
author_rank: Super Contributor
timestamp: May 04, 2021, 05:16:47 pm
content: In C you probably want to declare a 64 bit unsigned integer for that. Use uint64_t type, so it is always 64 bits, independent of architecture. I presume, your bytes will come one by one from SPI. Now one option would be to shift left every byte (by 0, 8, 16 and so on) and OR them to the uint64_t variable:Code:uint64_t Value;Value = byte1<<0 | byte2<<8 | byte3<<16 | byte4<<24 | byte5<<32;Another option is to use pointers. Something like below (be warned, I'm not a C expert):Code:uint64_t Value = 0;byte *p = (byte*)&Value; //Define a byte pointer, so individual bytes can be addressed*p = byte1; //Store first bytep++; //Increment pointer*p = byte2; //Store second bytep++; //Increment pointer//...Now you just need some library for your screen to support 64bit integers. I'm sure you can find one.

author: Doctorandus_P
author_rank: Super Contributor
timestamp: May 04, 2021, 05:20:54 pm
content: I always find these "binary" to "decimal" threads a bit abhorrent.Computers that work with "decimal" numbers do not really exist anymore. There were some back in the '70-ies and '80-ies.Some are capable to work with "BCD", and some programming languages have support for arbitrary big numbers, and use whatever format they fancy internally for that. For some programming languages there are (standardized) libraries for handling these big number formats.But for the most part there are just two's complement binary integers (Usually 8, 16, 32 or 64 bits long), floating point numbers (with different precision) and numbers that are converted to text strings.So what you probably want is to convert your "number" into a base-10 text string, and a common way to do that in C is to use functions such as itoa() (I'm not sure if that does 64 bits, but whatever programming langue you use, it probably has some kind of equivalent for it built in)

author: ledtester
author_rank: Super Contributor
timestamp: May 04, 2021, 05:54:28 pm
content: QuoteThe uint64_t type and operations are supported on the 8-bit AVR processors in the Arduino environment.Here is some code showing how to pack 5 single byte values into a 64-bit int and how to convert a 64-bit int to decimal digits:Code:#include <Arduino.h>uint8_t x0, x1, x2, x3, x4;void setup() {// put your setup code here, to run once:Serial.begin(115200);x0 = x1 = x2 = x3 = x4 = 0;}uint8_t base10(char *buf, uint64_t x) {uint8_t n = 0;while (x) {uint8_t d = x % 10;*buf++ = d + '0';n++;x /= 10;}return n;}uint64_t pack(uint8_t x1, uint8_t x2, uint8_t x3, uint8_t x4, uint8_t x5) {static uint8_t packed_y;packed_y = x0;packed_y = x1;packed_y = x2;packed_y = x3;packed_y = x4;return * ((uint64_t*) packed_y);}void loop() {x3 += 1;uint64_t y = pack(x0, x1, x2, x3, x4);Serial.print("packed y = ");char buf;uint8_t n = base10(buf, y);while (n--) {Serial.print(buf);}Serial.print('\n');delay(1000);}

author: Moriambar
author_rank: Unknown
timestamp: May 04, 2021, 07:00:41 pm
content: Thanks everybody for all of your helpful answers.I'll try and understand all of them and implement the best for my purpose.Cheers

author: rstofer
author_rank: Super Contributor
timestamp: May 04, 2021, 09:07:26 pm
content: I use the itoa() code in "The C Programming Language" by Kernighan and Ritchie. It still leaves the issue of divide and modulo 64 bit arithmetic to the user but it is a clean way to do things.Note that, by definition, the code produces the digits in reverse order and that needs to be fixed by the reverse() function.https://en.wikibooks.org/wiki/C_Programming/stdlib.h/itoa

author: Zero999
author_rank: Super Contributor
timestamp: May 04, 2021, 09:49:32 pm
content: What do you mean by decimal? BCD or ASCII?240= 1.1*1012, very big, so you'll need to put separators in to make it readable.It can be done without any division instructions. Keep subtracting the largest power of 10, in a loop, keeping count, until the number gets below the power, then move on to the next power. I've done this before in assembly. If I remember rightly, I used a stack to store the numbers, before converting them to ASCII values by adding 48 to each byte.

author: Cerebus
author_rank: Super Contributor
timestamp: May 04, 2021, 11:21:17 pm
content: Quote from: ledtester on May 04, 2021, 05:54:28 pmI can immediately see two problems with that. What happens if it's not on the little-endian processor that's an unstated assumption of that code? What happens if you try to use 'base10' to convert zero? (Hint, your loop termination conditions are wrong.)The safe way to pack bytes into an integer, that doesn't care about endian-ness and doesn't rely on type punning is to do repeated left shifts and ORs. Something like this:Code:#include <stdio.h>#include <stdint.h>#define BITS_PER_BYTE 8uint64_t pack_array_to_uint64_t (uint8_t* array, int lsb_byte_index, int msb_byte_index){uint64_t temporary = 0;if (lsb_byte_index <= msb_byte_index){for (int index = msb_byte_index; index >= lsb_byte_index; index--){temporary = temporary << BITS_PER_BYTE;temporary = temporary | array;}}else{for (int index = msb_byte_index; index <= lsb_byte_index; index++){temporary = temporary << BITS_PER_BYTE;temporary = temporary | array;}}return temporary;}int main (int argc, char const *argv[]){uint8_t source = {1, 2, 3, 4, 5, 6, 7, 8};uint64_t destination;destination = pack_array_to_uint64_t (source, 0, 7);printf ("destination = 0x%016llX\n", destination);destination = pack_array_to_uint64_t (source, 7, 0);printf ("destination = 0x%016llX\n", destination);return 0;}// Results:// cerebus@shu:~/Desktop$ gcc -o endian-agnostic endian-agnostic.c// cerebus@shu:~/Desktop$ ./endian-agnostic// destination = 0x0807060504030201// destination = 0x0102030405060708Also, another assumption in your code that can turn around to bite you is '0' + 1 = '1' and so on. This is not true for all character sets (e.g. EBCDIC), and is not true if you're trying to do something like a conversion to ASCII printing in another base such as hexidecimal. Again, the safe option is something like:Code:char output_char = "0123456789"; // For decimalchar output_char = "0123456789ABCDEF"; // For hexidecimalThat form has the advantage if you're using something other than the roman alphabet it's easy to just use a variable or a macro instead:Code:#ifdef ENGLISH#define DIGIT_ALPHABET "0123456789`'#endif#ifdef ARABIC#define DIGIT_ALPHABET "٠١٢٣٤٥٦٧٨٩"#endifchar output_char = DIGIT_ALPHABET; // For decimal

author: ledtester
author_rank: Super Contributor
timestamp: May 05, 2021, 02:09:19 am
content: QuoteGood point. Once you get the endian of the processor figured out, though, it'll be a lot more efficient to just store the bytes in the correct order. Recall that the OP wants to use this on an "8 bit mcu".

author: Cerebus
author_rank: Super Contributor
timestamp: May 05, 2021, 10:21:54 am
content: Quote from: ledtester on May 05, 2021, 02:09:19 amWhen you've been banging code out for as many years as I have you learn that "safe", "reusable", "understandable" and "maintainable" count for more than efficiency in nearly all cases, and "tested correct" in all cases. If efficiency proves to be a problem in a particular use case, that's the time to start fussing over CPU cycles, not before. There's a reason that the phase"premature optimization"has entered the lexicon of software engineering. Donald Knuth said "We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%".

author: mikerj
author_rank: Super Contributor
timestamp: May 05, 2021, 12:12:55 pm
content: Quote from: Cerebus on May 05, 2021, 10:21:54 amYou've never used assembly then?

author: Zero999
author_rank: Super Contributor
timestamp: May 05, 2021, 01:58:49 pm
content: Further to my earlier comment. Here's some BASIC-like pseudo code, which works for numbers up to 99 999. It can be copy+pasted to work up to any arbitrarily large number. After it ends, the number variable contains the units, so back it up, if the original value needs to be preserved. It has no division, or multiplication instructions and can easilly be implemented in assembly. The downside is it's bulky, especially for a 40-bit number, which could be up to 13 digits long.Code:intlong number ; The big numberint 10pow4 ; Ten thousandsint 10pow3 ; Thousandsint 10pow2 ; Hundredsint 10pow1 ; Tensdo while number > 10^4number = number - 10^410pow4 = 10pow4 + 1loopdo while number > 10^3number = number - 10^310pow3 = 10pow3 + 1loopdo while number > 10^2number = number - 10^210pow2 = 10pow2 + 1loopdo while number > 10^1number = number - 10^110pow1 = 10pow1 + 1loopHint: in assembly, the comparason and subtraction can be done at the same time, but the number needs to be backed up first.

author: Cerebus
author_rank: Super Contributor
timestamp: May 05, 2021, 02:08:39 pm
content: Quote from: mikerj on May 05, 2021, 12:12:55 pmI've used lots, because my beginnings hail from when optimizing compilers didn't much, and you were lucky if the machine you were sharing with 50 other people simultaneously stretched to 1 MIPS. The second real, as opposed to instructional, language I learned was PLAN, ICL 1900 assembler. To misquote my father, "I've forgotten more about efficient coding that the kids of today will ever learn!".

author: Doctorandus_P
author_rank: Super Contributor
timestamp: May 05, 2021, 03:12:38 pm
content: These days PC processors have multiple threads, cach management, branch prediction, out of order exectution and other weirness of which I don't even know their names.I am fairly certain though that people who claim to write better software in ASM are from a different era where such a thing may have been true. If you want to do a better job then a modern compiler you will have a tough task at hands.I can not even imagine writing a GUI application in ASM.Task switching for an RTOS is one of the niche applications where ASM has still been used by lack of better tools, but even that is shifting as the latest C++ revisions are getting baked-in support for thread management.One of the very few things left for ASM in these days is probably for the people who are writing and optimizing the C and C++ (and other) compilers.There are of course also people who just do ASM for fun, (for example in the retro-computing communities), and writing some ASM at school is a enlightening task for school kids who have to learn how real low stuff computer architecture works, but for other then such things writing ASM has gone the way of the Dodo.There simply is no place for writing regular programs in ASM anymore. Yes, it's possible, but it simply is not a good idea. The world is just changing too fast, and writing any non trivial program in ASM is just folly these days. I could take Elm-Chan's FatFs written for any microntroller in C and port it to another in some reasonable amount of time, but if such a library was written in ASM, I would not even snif at it.