Routines to convert binary to BCD in C code - Page 1
Forum section: electronics / general

author: Picuino
author_rank: Super Contributor
timestamp: March 27, 2024, 12:37:41 pm
content: Binary to decimal BCD conversion routines have many applications. The first application I give them in new systems is to send by UART binary numbers to the computer in decimal format.The code is written in C for Arduino, but is easily translatable to another environment.16 bits binary to 5 digits BCD:Code: (C)#define BINARY_DIGITS 16#define DECIMAL_DIGITS 5void bin16_to_str(char *str, unsigned int bin) {char maxdig, digcnt, bitcnt;static char *p, carry;// Clear stringp = str;digcnt = DECIMAL_DIGITS + 1;do { *p++ = 0; } while (--digcnt);// Transform binary to BCDbitcnt = BINARY_DIGITS;maxdig = (DECIMAL_DIGITS - 1);str += (DECIMAL_DIGITS - 1);do {// Shift left binary number with carrycarry = 0;if (bin & (1L<<(BINARY_DIGITS-1)))carry |= 1;bin <<= 1;// Shift left decimal number with carryp = str;digcnt = DECIMAL_DIGITS - maxdig;do {carry = (*p<<1) + carry;if (carry >= 10) {*p-- = carry - 10;carry = 1;if (digcnt == 1) {maxdig--;digcnt++;}}else {*p-- = carry;carry = 0;}} while(--digcnt);} while(--bitcnt);// Transform BCD to ASCIIdigcnt = DECIMAL_DIGITS;do *str-- += '0'; while (--digcnt);}void setup() {Serial.begin(9600);}void loop() {unsigned long timeit;unsigned long bin;char strnum[DECIMAL_DIGITS + 1];strnum = 0; // End of chartimeit = millis();for(bin=10000; bin<11000; bin++) {bin16_to_str(strnum, bin);}timeit = millis() - timeit;Serial.print("1 conversion in ");Serial.print(timeit);Serial.println(" microseconds");delay(5000);for(bin=0; bin<50000; bin++) {bin16_to_str(strnum, bin);Serial.println(strnum);}}Output:Code:1 Conversion in 61 microseconds000000000100002000030000400005000060000700008000090001000011000120001300014000150001600017000180001900020000210002200023000240002500026000270002800029000300003100032000330003400035000360003700038000390004000041000420004300044000450004600047000480004900050000510005200053000540005500056000570005800059000600006100062000630006400065000660006700068000690007000071000720007300074000750007600077000780007900080000810008200083000840008500086000870008800089000900009100092000930009400095000960009700098000990010000101001020010300104001050010600107001080010900110001110011200113001140011500116001170011800119001200012100122001230012400125001260012700128001290013000131001320013300134001350013600137001380013900140Size of flash memory (function bin16_to_str) = 2084 - 1916 = 168 Bytes=======================================================32 bits binary to 10 digits BCD:Code: (C)#define BINARY_DIGITS 32#define DECIMAL_DIGITS 10void bin32_to_str(char *str, unsigned long bin) {char maxdig, digcnt, bitcnt;static char *p, carry;// Clear stringp = str;digcnt = DECIMAL_DIGITS + 1;do { *p++ = 0; } while (--digcnt);// Transform binary to BCDbitcnt = BINARY_DIGITS;maxdig = (DECIMAL_DIGITS - 1);str += (DECIMAL_DIGITS - 1);do {// Shift left binary number with carrycarry = 0;if (bin & (1L<<(BINARY_DIGITS-1)))carry |= 1;bin <<= 1;// Shift left decimal number with carryp = str;digcnt = DECIMAL_DIGITS - maxdig;do {carry = (*p<<1) + carry;if (carry >= 10) {*p-- = carry - 10;carry = 1;if (digcnt == 1) {maxdig--;digcnt++;}}else {*p-- = carry;carry = 0;}} while(--digcnt);} while(--bitcnt);// Transform BCD to ASCIIdigcnt = DECIMAL_DIGITS;do *str-- += '0'; while (--digcnt);}void setup() {Serial.begin(9600);}void loop() {unsigned long timeit;unsigned long bin;char strnum[DECIMAL_DIGITS + 1];strnum = 0; // End of chartimeit = millis();for(bin=0x80000000; bin<0x80000000+1000; bin++) {bin32_to_str(strnum, bin);}timeit = millis() - timeit;Serial.print("1 Conversion in ");Serial.print(timeit);Serial.println(" microseconds");delay(5000);for(bin=0; bin<100000; bin++) {bin32_to_str(strnum, bin);Serial.println(strnum);}}Output:Code:1 Conversion in 234 microseconds00000000000000000001000000000200000000030000000004000000000500000000060000000007000000000800000000090000000010000000001100000000120000000013000000001400000000150000000016000000001700000000180000000019000000002000000000210000000022000000002300000000240000000025000000002600000000270000000028000000002900000000300000000031000000003200000000330000000034000000003500000000360000000037000000003800000000390000000040000000004100000000420000000043000000004400000000450000000046000000004700000000480000000049000000005000000000510000000052000000005300000000540000000055Size of flash memory (function bin32_to_str)= 2154 - 1926 = 228 Bytes

author: Picuino
author_rank: Super Contributor
timestamp: March 27, 2024, 01:02:09 pm
content: 8 bits binary to 3 digits BCD:Code:#define BINARY_DIGITS 8#define DECIMAL_DIGITS 3void bin8_to_str(char *str, unsigned char bin) {char maxdig, digcnt, bitcnt;static char *p, carry;// Clear stringp = str;digcnt = DECIMAL_DIGITS + 1;do { *p++ = 0; } while (--digcnt);// Transform binary to BCDbitcnt = BINARY_DIGITS;maxdig = (DECIMAL_DIGITS - 1);str += (DECIMAL_DIGITS - 1);do {// Shift left binary number with carrycarry = 0;if (bin & (1L<<(BINARY_DIGITS-1)))carry |= 1;bin <<= 1;// Shift left decimal number with carryp = str;digcnt = DECIMAL_DIGITS - maxdig;do {carry = (*p<<1) + carry;if (carry >= 10) {*p-- = carry - 10;carry = 1;if (digcnt == 1) {maxdig--;digcnt++;}}else {*p-- = carry;carry = 0;}} while(--digcnt);} while(--bitcnt);// Transform BCD to ASCIIdigcnt = DECIMAL_DIGITS;do *str-- += '0'; while (--digcnt);}void setup() {Serial.begin(9600);}void loop() {unsigned long timeit;unsigned char bin;char strnum[DECIMAL_DIGITS + 1];strnum = 0; // End of chartimeit = millis();for(bin=0; bin<255; bin++) {bin8_to_str(strnum, bin);}timeit = millis() - timeit;Serial.print("1 conversion in ");Serial.print(timeit/2.55);Serial.println(" microseconds");delay(5000);for(bin=0; bin<255; bin++) {bin8_to_str(strnum, bin);Serial.println(strnum);}}Code:1 conversion in 2.75 microseconds000001002003004005006007008009010011012013014015016017018019020021022023024025026027028029030031032033034035036037038039040041042043044045046047048049050051052053054055056057058059060061062063064065066067068069070071072073074075076077078079080081082083084085086087088089090091092093094095096097098099100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254

author: tridac
author_rank: Regular Contributor
timestamp: March 27, 2024, 03:05:19 pm
content: This is one of my standrd library functions. Edit as required.../* Convert Binary to Bcd, Msd First *//* -------------------------------- */void vBin2Bcd (U32 u32Value, U8 *pu8BcdArray, U8 u8Count) { /* BcdArray has msd at array on return */U8 u8Index;if (u32Value > 0) {for (u8Index = 1; u8Index <= u8Count; u8Index++) {pu8BcdArray [u8Count - u8Index] = (U8) (u32Value % 10); /* Buffer remainder */u32Value /= 10; /* Next decade */}}else { /* Don't convert zero */for (u8Index = 0; u8Index < u8Count; u8Index++) {pu8BcdArray  = 0; /* Clear output */}}}A printing version:/* Convert and Display 32 Bit Value as Decimal *//* ------------------------------------------- */void vPutDecimal (U32 u32Value) {U8 u8Count = BUF_SIZE - 1;u8Buffer [u8Count--] = 0x00; /* Terminate string */do { /* First, split number into individual digits */u8Buffer [u8Count--] = (U8) ((u32Value % 10) + 0x30); /* Get remainder, asciify & buffer */} while ((u32Value /= 10) > 0 && u8Count > 0); /* Next decade, trap buffer overflow */u8Count++; /* Correct count */vPutString (&u8Buffer ); /* Display, left justified */}

author: Picuino
author_rank: Super Contributor
timestamp: March 27, 2024, 03:30:24 pm
content: Quote from: tridac on March 27, 2024, 03:05:19 pmGood and very short and simple routine.My rutine, on the other hand, it does not make use of integer arithmetic ( value%10 and value /= 10) it only makes use of binary number shifts.This means that it can generate shorter and faster assembly code, especially on small micros that do not have large ALUs nor FPUs.

author: Picuino
author_rank: Super Contributor
timestamp: March 27, 2024, 03:41:37 pm
content: My routine of 32 bits use at Arduino: 228 bytes of flash, 234us/conversionOther routine of 32 bits use at Arduino: 156 bytes of flash, 380us/conversionIt seems that in the end the routine with divisions by 10 is shorter, despite the integer arithmetic required.

author: tridac
author_rank: Regular Contributor
timestamp: March 27, 2024, 04:03:04 pm
content: Iirc, originally used that sort of idea in 6502 asm, but yes, like the old debate around integer divide and multiply, the old shift and add / subtract method can save a lot of memory, math lib inclusion etc. Modern micros are not so encumbered though, orders or magnitude faster, perhaps integer multiply and divide, and more flash space, so tend to think clarity rather than memory economy.

author: radiolistener
author_rank: Super Contributor
timestamp: March 27, 2024, 05:31:33 pm
content: Here is simple 32-bit binary to 8-digit BCD (32-bit) conversion:Code:uint32_t bin2bcd(uint32_t bin) {uint32_t bcd = 0;uint32_t factor = 1;while (bin) {bcd += (bin % 10) * factor;bin /= 10;factor <<= 4;}return bcd;}just wonder how much memory it will consume on Arduino and what is execution time in comparison to previously posted code.Does your microcontroller supports hardware multiplication and divide for integer?

author: Picuino
author_rank: Super Contributor
timestamp: March 27, 2024, 07:37:52 pm
content: 32 bits binary to 10 digits BCD (with division value/10):Code:#define DECIMAL_DIGITS 10void bin32_to_str(char *bcdArray, unsigned long value) {unsigned char index;if (value > 0) {for (index = 1; index <= DECIMAL_DIGITS; index++) {bcdArray[DECIMAL_DIGITS - index] = ((unsigned char) (value % 10)) + '0';value /= 10;}}else {for (index = 0; index < DECIMAL_DIGITS; index++) {bcdArray = '0';}}}void setup() {Serial.begin(9600);}void loop() {unsigned long timeit;unsigned long bin;char strnum[DECIMAL_DIGITS + 1];strnum = 0; // End of chartimeit = millis();for (bin = 0x80000000; bin < 0x80000000 + 1000; bin++) {bin32_to_str(strnum, bin);}timeit = millis() - timeit;Serial.print("1 Conversion in ");Serial.print(timeit);Serial.println(" microseconds");delay(5000);for (bin = 0; bin < 100000; bin++) {bin32_to_str(strnum, bin);Serial.println(strnum);}}Code:1 Conversion in 386 microseconds0000000000000000000100000000020000000003000000000400000000050000000006000000000700000000080000000009000000001000000000110000000012000000001300000000140000000015000000001600000000170000000018000000001900000000200000000021000000002200000000230000000024000000002500000000260000000027000000002800000000290000000030000000003100000000320000000033000000003400000000350000000036000000003700000000380000000039000000004000000000410000000042000000004300000000440000000045000000004600000000470000000048000000004900000000500000000051000000005200000000530000000054000000005500000000560000000057000000005800000000590000000060000000006100000000620000000063000000006400000000650000000066000000006700000000680000000069000000007000000000710000000072000000007300000000740000000075000000007600000000770000000078000000007900000000800000000081000000008200000000830000000084000000008500000000860000000087000000008800000000890000000090000000009100000000920000000093000000009400000000950000000096000000009700000000980000000099000000010000000001010000000102000000010300000001040000000105000000010600000001070000000108000000010900000001100000000111000000011200000001130000000114000000011500000001160000000117000000011800000001190000000120000000012100000001220000000123000000012400000001250000000126000000012700000001280000000129000000013000000001310000000132000000013300000001340000000135000000013600000001370000000138000000013900000001400000000141000000014200000001430000000144000000014500000001460000000147000000014800000001490000000150000000015100000001520000000153000000015400000001550000000156000000015700000001580000000159000000016000000001610000000162000000016300000001640000000165000000016600000001670000000168000000016900000001700000000171000000017200000001730000000174Total flash memory code (function bin32_to_str): 2092-1926 = 166 bytes

author: Nominal Animal
author_rank: Super Contributor
timestamp: March 27, 2024, 07:41:37 pm
content: For 8-bit microcontrollers like AVRs, I'd suggest repeated substraction instead. For example,Code:// SPDX-License-Identifier: CC0-1.0#include <stdint.h>static const uint32_t u32_dec = {UINT32_C(1),UINT32_C(3),UINT32_C(10),UINT32_C(30),UINT32_C(100),UINT32_C(300),UINT32_C(1000),UINT32_C(3000),UINT32_C(10000),UINT32_C(30000),UINT32_C(100000),UINT32_C(300000),UINT32_C(1000000),UINT32_C(3000000),UINT32_C(10000000),UINT32_C(30000000),UINT32_C(100000000),UINT32_C(300000000),UINT32_C(1000000000),UINT32_C(3000000000),};// Little-endian byte order#define BYTEINDEX(n, m) (n)// Big-endian byte order// #define BYTEINDEX(n, m) ((m)-(n))// Little-endian byte orderstatic const uint8_t u32_bcd = {{ BYTEINDEX(0, 4), 0x01 },{ BYTEINDEX(0, 4), 0x03 },{ BYTEINDEX(0, 4), 0x10 },{ BYTEINDEX(0, 4), 0x30 },{ BYTEINDEX(1, 4), 0x01 },{ BYTEINDEX(1, 4), 0x03 },{ BYTEINDEX(1, 4), 0x10 },{ BYTEINDEX(1, 4), 0x30 },{ BYTEINDEX(2, 4), 0x01 },{ BYTEINDEX(2, 4), 0x03 },{ BYTEINDEX(2, 4), 0x10 },{ BYTEINDEX(2, 4), 0x30 },{ BYTEINDEX(3, 4), 0x01 },{ BYTEINDEX(3, 4), 0x03 },{ BYTEINDEX(3, 4), 0x10 },{ BYTEINDEX(3, 4), 0x30 },{ BYTEINDEX(4, 4), 0x01 },{ BYTEINDEX(4, 4), 0x03 },{ BYTEINDEX(4, 4), 0x10 },{ BYTEINDEX(4, 4), 0x30 },};void binary32_to_bcd(uint8_t dst, uint32_t src){dst = dst = dst = dst = dst = 0;int8_t i = 20;while (i-->0)while (src >= u32_dec) {src -= u32_dec;dst[u32_bcd] += u32_bcd;}}takes about 258 bytes of Flash on AVR (gcc-5.4.0-O2 -mmcu=atmega32u4), with the slowest case being 2888888888, 38 iterations of the inner loop total.If you don't mind increasing the slowest case to 3999999999, 85 iterations of the inner loop total, you can useCode:// SPDX-License-Identifier: CC0-1.0#include <stdint.h>static const uint32_t u32_dec = {UINT32_C(1),UINT32_C(10),UINT32_C(100),UINT32_C(1000),UINT32_C(10000),UINT32_C(100000),UINT32_C(1000000),UINT32_C(10000000),UINT32_C(100000000),UINT32_C(1000000000),};// Little-endian byte order#define BYTEINDEX(n, m) (n)// Big-endian byte order// #define BYTEINDEX(n, m) ((m)-(n))// Little-endian byte orderstatic const uint8_t u32_bcd = {{ BYTEINDEX(0, 4), 0x01 },{ BYTEINDEX(0, 4), 0x10 },{ BYTEINDEX(1, 4), 0x01 },{ BYTEINDEX(1, 4), 0x10 },{ BYTEINDEX(2, 4), 0x01 },{ BYTEINDEX(2, 4), 0x10 },{ BYTEINDEX(3, 4), 0x01 },{ BYTEINDEX(3, 4), 0x10 },{ BYTEINDEX(4, 4), 0x01 },{ BYTEINDEX(4, 4), 0x10 },};void binary32_to_bcd(uint8_t dst, uint32_t src){dst = dst = dst = dst = dst = 0;int8_t i = sizeof u32_dec / sizeof u32_dec;while (i-->0)while (src >= u32_dec) {src -= u32_dec;dst[u32_bcd] += u32_bcd;}}which compiles to 198 bytes of flash (gcc-5.4.0,-O2 -mmcu=atmega32u4).

author: cv007
author_rank: Frequent Contributor
timestamp: March 27, 2024, 08:48:35 pm
content: QuoteThe first thing I get any new mcu to do is to use the pins, then use the uart via printf or similar. Once in place you are set for any kind of formatting you want, with no need to create bits and pieces of code that attempts to do the same. The size, once in place, remains the same so the cost is one time and 'free' to use after the first use so use it everywhere you can.https://godbolt.org/z/cMffYMzf4A little over 2k in size, of which printf is probably 3/4 of that. If my avr couldn't handle the extra 1.5k, I would move up to the next avr flash size. Having this is place is much more valuable than the loss of 1.5k (of which you replace with your bits/pieces of code anyway, so more like 1k).I also have a C++ header for cout style printing, and the example above would be like so (and be about 500 bytes smaller in size)-Code:uart, '[', dec_(10,millis()), "] i: ", i++, endl; // using , instead of << and dec_ is decimal with width of 10 and leading spacesa header with about 500 lines of code, but is basically 2 relatively simple functions that do the work (excluding floating point, which is another function)- format a number to something readable using already set formatting options and output a string, again using width/align options already in place (everything ends up in this).Unless you like the challenge of fitting max code into a small mcu (like 2k), just get a printf in place and output whatever you desire and you can spend more time doing things like learning printf syntax.

author: SiliconWizard
author_rank: Super Contributor
timestamp: March 27, 2024, 11:34:07 pm
content: Note that unless you have to deal with a lot of digits, on small targets, a very simple approach is often not just much simpler, but also actually faster.You just need to count each digit by powers of ten. It's a very crude division algorithm, but for this purpose it works quite well. Nominal showed an improved version of that, but one that will take less flash (likely) is just to directly count in decimal.If you just need a few digits, no need to get more complicated than this. You just need an array of the powers of 10 corresponding to the number of digits you need to deal with. It can even be unrolled if the number of digit is small. Say for 4 digits:nDigit1000 = 0;while (n >= 1000){nDigit1000++;n -= 1000;}nDigit100 = 0;while (n >= 100){nDigit100++;n -= 100;}... and so on. If the number of digits is greater than this, or you want something more compact and easier to scale up, you can use arrays instead of this loop unrolling.You can use Nominal's approach for something a bit more efficient.But the direct decimal subtraction is often the simplest and fastest one on small targets that don't even have a hardware multiplier (or that have one that's pretty slow) with which optimizing compilers will usually implement the dvision by 10, if you go for the mod 10, divide by 10 approach. It may look more appropriate, but it rarely is on small embedded targets.

author: Nominal Animal
author_rank: Super Contributor
timestamp: March 28, 2024, 06:54:11 am
content: There are several threads on related interesting topics in theProgrammingchild board; just browse the thread titles for a few dozen pages past.

author: rhodges
author_rank: Frequent Contributor
timestamp: April 04, 2024, 02:48:41 am
content: For every architecture, I write binary to BCD and ASCII in the native assember. After three decades, I don't find the idea too hard. My approach is to use or emulate the DAA. I recently posted my code on the thread called "comments".I consider the divide a "no go" unless the CPU has a quick hardware divide. For example, the STM8 has a hardware divide that makes the conversion fast for small numbers. Otherwise, I suggest the shift and "DAA" method.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: April 04, 2024, 05:04:36 am
content: Have probably posted this before somewhere in the past, but as long as it's topical...You can divide by multiplying by a reciprocal, assuming the reciprocal is known of course. This even allows obtaining the remainder.Something I wrote years ago:Code:;; uDivTen;; Unsigned division by ten. 45 words, takes 60 cycles (plus rcall).;; Input:; r17:r16 = operand; Output:; r1:r0 = quotient; r3:r2 = remainder; (r16 = 10, r17 = 0);uDivTen:push r7; register usage:push r6; r7:r6 = operand (in[0..1])push r5; r5:r4:r3:r2 = 40 bit accumulator, D[1..3] (byte 0 discarded)push r4; r1:r0 = partial productsmovw r6, r16; r16 = temp, r17 = zeroclr r17; (but first, save operand); multiply by K = 2^20 * 16 / 10 to make D[0..3] = in[0..1] * K[0..2]; (implicitly discarding D)ldi r16, (((exp2(24) / 10) + 1) & (0x0000ff << 16)) >> 16mul r7, r16; in * Kmovw r4, r0; save high wordldi r16, (((exp2(24) / 10) + 1) & (0x0000ff << 0)) >> 0mul r7, r16; in * Kmovw r2, r0; save low wordmul r6, r16; in * Kadd r2, r1; accumulate to D (discard lowest byte)adc r3, r17adc r4, r17adc r5, r17ldi r16, (((exp2(24) / 10) + 1) & (0x0000ff << 8)) >> 8mul r6, r16; in * Kadd r2, r0; accumulate to D[1..2]adc r3, r1adc r4, r17adc r5, r17mul r7, r16; in * Kadd r3, r0; accumulate to D[2..3]adc r4, r1adc r5, r17ldi r16, (((exp2(24) / 10) + 1) & (0x0000ff << 16)) >> 16mul r6, r16; in * Kadd r3, r0; accumulate to D[2..3]adc r4, r1adc r5, r17; dig remainder out of the fractional partldi r16, 0x10; rounding bitadd r3, r16ldi r16, 10mul r3, r16; frac * 10mov r2, r1clr r3movw r0, r4; quotient out; r3 = 0, r2 = [0...9], r1:r0 = [0...6553]pop r4pop r5pop r6pop r7ret; END PROC uDivTenUsing an ad-hoc / non-standard ABI, but that's easily adapted by those in the know. That "40 bit" accumulator in the middle seems intimidating, or over-the-top, but IIRC, one or two extra bits are required to complete the full result (including remainder) and making use of the register is most likely better than any bit twiddling hacks you might do otherwise. (And, good luck getting the compiler to generate that..!)I've never cared for BCD, and just do the x / 10, x % 10 loop to generate decimals. You'd have to be running a lot of digits to need any kind of speed. You're most likely doing it in the main() loop at a low refresh rate, for which any method will do -- my most recent codebases just let the C compiler figure it out, or use a library call such as itoa(). I haven't ran out of Flash or SRAM in a long time (...or ever?), not that I do very large projects, but still, a typical 64k AVR can hold a lot of crufty code no problem.Tim

author: bson
author_rank: Unknown
timestamp: April 08, 2024, 09:10:06 pm
content: For ARM, shift-and-add/subtract is probably the most efficient since it can do both in a single instruction.I have a small utility athttps://github.com/bson/divfacwhich factors unsigned integer division by a constant into shift-and-add. For example:Code:$ divfac.py 10 div10template <typename T>static inline T div10(const T& val) {return (val >> 3) - (val >> 5) + (val >> 7) - (val >> 9) + (val >> 11) - (val >> 13) + (val >> 15) - (val >> 17) + (val >> 19) - (val >> 21) + (val >> 23) - (val >> 25) + (val >> 27) - (val >> 29) + (val >> 31);}Obviously it spits out C++ in a way to allow running it from makefiles to easily generate header files to be included for common divisors.There's clearly a pattern here that would make it easy to manually extend to 64 bits, but personally I've never had a use for that. For smaller T's like uint16_t, many of the terms are 0 and get eliminated during optimization.With inline ARM assembly, a 32-bit divide-by-10 could look something like:Code:#include <stdint.h>static inline uint32_t div10(const uint32_t value) {uint32_t result;asm("lsr %0, %1, #3\n\t""sub %0, %1, lsr #5\n\t""add %0, %1, lsr #7\n\t""sub %0, %1, lsr #9\n\t""add %0, %1, lsr #11\n\t""sub %0, %1, lsr #13\n\t""add %0, %1, lsr #15\n\t""sub %0, %1, lsr #17\n\t""add %0, %1, lsr #19\n\t""sub %0, %1, lsr #21\n\t""add %0, %1, lsr #23\n\t""sub %0, %1, lsr #25\n\t""add %0, %1, lsr #27\n\t""sub %0, %1, lsr #29\n\t""add %0, %1, lsr #31": "+r" (result): "r" (value));return result;}Of course, a 16-bit divide would be shorter since half the terms can be removed.

author: Doctorandus_P
author_rank: Super Contributor
timestamp: April 16, 2024, 01:31:08 pm
content: I would use ldiv( ) It does a division, and returns both the answer, and the remainer in a structure. You can also use a standard function such as itoa( ) (Integer to Ascii). Also studying the code in the standard libraries (from an Open Source compiler such as GCC) can be an excellent educational tool for leaning different types of algorithms used in programming.Below a function I wrote years ago. Most of it is house keeping and ascii formatting (sings, dots, etc) to convert an integer into a fixed point interpretated ascii string. The while loop in the middle does the actual conversion with ldiv( ). Do note it writes the result "backwards" to the destination string (array).// String Print a number with Fixed width and precision (decimal point).// Signed version.char* SPrintFixed( char* Buf, int32_t n, int8_t length, int8_t Precision) {char Sign, *pEnd = Buf + length;ldiv_t Number;if( n < 0) {n = -n;Sign = '-';}elseSign = ' ';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] = ' ';}return pEnd;}

author: cv007
author_rank: Frequent Contributor
timestamp: April 16, 2024, 06:06:59 pm
content: QuoteYou will be better off letting the compiler handle it. It can figure out when you are needing quotient/remainder and then call a divmod library function which is already optimized for the particular mcu. Forcing the use of ldiv when the compiler could have done something better (much better depending on mcu) means you are crippling the more capable mcu's.So like most things, write code that is clear and easy to understand and let the compiler optimize, which it does quite well.

author: Doctorandus_P
author_rank: Super Contributor
timestamp: April 23, 2024, 02:46:29 am
content: Do you mean with this that the compiler will optimize separate remeainer and division operations but not an ldiv?And how do you know that ldiv is not optimized?Saying this as a blanket statement is not very trustworthy. Modern compilers do weird things to code during optimisation, and if performance really is that important you should do some benchmarking on the uC / compiler you use.And as for readability, I'd say my posted code is very readable, but I'm biased of course.At least ldiv is interesting enough to mention in this thread.

author: cv007
author_rank: Frequent Contributor
timestamp: April 24, 2024, 07:18:22 pm
content: QuoteI mean compiler writers are not unaware of these things and may already generate optimal code given simple directions in the form of user code. In the case of an mcu with hardware division, letting the compiler decide means you will be letting the compiler use the hardware division instead of forcing a call to an ldiv library function, or some other user created division function, which will be much slower and therefore defeats the purpose of the extra effort to optimize on your own.Simple example for an avr-https://godbolt.org/z/TsvWeTof4They both make a single call to __divmodhi4 and the compiler sees the need for both q/r in the 'normal' version even though the div/mod statements were separated by code. Writing straight forward code in this case results in (almost) exactly the same code.A second example, but for a cortex-mhttps://godbolt.org/z/GEbY8n1nnIn this case, the compiler for whatever reason needs the divisor in a global var before it will do only the single __aeabi_idivmod call, else will get an additional __aeabi_idiv call. Even if you do not know this quirk and end up with 2 division calls its not something to worry about- you will not notice any effects that your formatting code is not optimal. Once you move over to an M3 and above, the compiler will start using hardware division and you had to do nothing to get that, where the ldiv (or any custom code) user will still be using the slow library call as the compiler will be forced to use it. (You can change the command line option to cortex-m3,m4,m33,etc. and see what you get for both functions).So as I suggested, spend your time getting the high level code to be both readable and reliable and let the compiler handle optimization. In the case of formatting, there is little need to worry about perfect optimization.

author: Nominal Animal
author_rank: Super Contributor
timestamp: April 24, 2024, 08:42:59 pm
content: Quote from: Doctorandus_P on April 23, 2024, 02:46:29 amAbsolutely!Quote from: Doctorandus_P on April 23, 2024, 02:46:29 amConsidering the abstract machine model the C standard is based on, I'd say C compilers have more opportunities to optimize the integer-divide-and-modulo expressions compared to thediv()/ldiv()/lldiv()/imaxdiv()function call.If you use e.g. Compiler Explorer to examine the code generated at different optimization levels for different architectures fromCode:typedef unsigned int uint;typedef struct {uint quotient;unsigned char remainder;} pair;pair div10(uint n) { return (pair){ .quotient = n / 10, .remainder = n % 10 }; }you'll see it varies depending on optimization options.For example, for 8-bit AVR (ATmega32U4) using GCC 13.2.0 and-Os, it compiles to a simple__udivmodhi4call. With-O2, it is open-coded. For 32-bit architectures using GCC and Clang and-O2, it typically compiles to a multiplication by 0xcccccccd ("0.1"); with-Os, to a division by 10 (wherever the instruction set has a 32-bit unsigned divide instruction).This does not mean that/%producesbettercode, though; only that the compiler has more opportunities for optimization.I do not believe in micro-optimizations, or optimizing specific functions. Hot functions should get inlined, and there the opportunities for optimization tend to matter more. It is not just the operation itself, you see, but everything else surrounding and supporting the computation operation.The best illustration of a similar issue I know of, is Radix-sorting IEEE 754 double-precision floating point numbers (double). When their byte order is the same as unsigned integers, one only needs a preprocessing pass of flipping all bits if the sign bit is set, or only the sign bit if it is not set, to make all finite values sort in the same order as the 64-bit unsigned integer representation (after the bit flipping). Depending on the L1-cache/D-cache size, you do four to eight passes, plus the final unflipping-bits pass. This scales linearly: as it is a radix sort, it indeed sorts the finite floating-point values inO(N)time complexity. However, its cache footprint is horrible, evicting basically everything else from the L1/D-cache, so using itslows all other code running after the sort, because it ends up having to refill the cache. Thus, you need to have hundreds of millions of numbers or more, before it ends up being faster/better than QuickSort and otherO(N²)andO(NlogN)time complexity (but typically fast) sorting algorithms. And if the other stuff is cache-sensitive, maybe not even then; while the sort may be done faster, the other code needed to do the actual real-world task may take correspondingly longer time (due to the cache misses). (Similarly, when multiplying a pair of dense matrices, you don't need very large matrices before it becomes worthwhile to copy the data so that they are both accessed in as compact order as possible, for maximum cache locality.)

author: T3sl4co1l
author_rank: Super Contributor
timestamp: April 25, 2024, 04:58:19 am
content: Quote from: Nominal Animal on April 24, 2024, 08:42:59 pmRelated perspective shift: CPU time is essentially free these days, and often this is the case on more historic platforms too (most anything with slow RAM, caches, etc.). Doing float comparisons might be relatively onerous in raw cycle count (depending on platform; they've certainly improved over the years), and the integer hack is so greedily tempting. But if you're spending all your time fetching and storing, that... doesn't matter at all.Tim

author: John Coloccia
author_rank: Super Contributor
timestamp: April 26, 2024, 01:00:38 am
content: Depends on the processor. Some processors fall flat on their face with floating point calculations.But yes, many cases of poor performance are due to IO bounds, not computation.Is there an actual problem that's trying to be solved here?

author: Nominal Animal
author_rank: Super Contributor
timestamp: April 26, 2024, 02:15:52 am
content: Quote from: John Coloccia on April 26, 2024, 01:00:38 amNot really. Binary to decimal conversion is considered "slow" and requiring "surprisingly much" machine-level code, compared to arithmetic operations, so these can be useful in some cases, but that's about it.Library implementations, especially variousprintf()implementations, are written for correctness and not for efficiency, so when you work with smaller microcontrollers, especially 8-bit ones (AVRs, PICs, 8051s), a custom implementation can be useful. More annoyingly, most standard library functions use arbitrary precision arithmetic and dynamic memory allocation during conversion of floating-point numbers.Even on fully hosted C environments, it turns out that if you read numeric data from text files, the standard C library string-to-number conversions (strtod(),strtol(),scanf()and their variants) become the bottleneck, when you have enough (megabytes) of data. Then, too, "optimized" conversion functions can reduce the load times to a fraction of what they would be using standard C conversion functions.

author: SiliconWizard
author_rank: Super Contributor
timestamp: April 26, 2024, 03:39:04 am
content: Quote from: Nominal Animal on April 26, 2024, 02:15:52 amYep. scanf() being the absolute dog of them all.And sometimes the "simplest" solution is actually also the most efficient. As I said before, the simple approach to count powers of ten, requiring "only" up to 9 iterations per digit with just a subtraction and an increment per iteration, will be faster on most targets that do not have a hardware multiplier (with which most optimizing compiler will implement the divide by 10), or on which the hardware multiplier is a multi-cycle operation. In particular, I've used the solution I mentioned earlier on AVR targets and it led to a lot smaller code size, and much faster too. Don't let the fact that it looks too "naive" deter you - looking at the generated assembly if in doubt will help picking the most efficient approach, in simple cases like this.

author: Picuino
author_rank: Super Contributor
timestamp: April 26, 2024, 11:49:49 am
content: Performing the binary-to-decimal conversion with custom routines has helped me to be able to dump huge numbers (48 bits long) from the counter of an 8-bit microcontroller that counted 10MHz pulses for long periods of time and dumped the count by UART every second.

author: cv007
author_rank: Frequent Contributor
timestamp: April 26, 2024, 06:51:49 pm
content: QuoteThat is one of the points I am making- speed of conversion does not matter at all if outputting only every second. Even when you want to dump lots of (formatted) data it matters very little.You original post was focused on the conversion speed, but if you had actually output all the data at your specified 9600 baud (960 bytes/sec) you would have found out that any formatting you can come up with will be blocking on the uart as it sends out the data, whether you used the arduino print which you had access to or your own custom bcd conversion.Do what you desire, but using existing code for formatting such as printf or arduino print will be plenty fast. Code size will also be of little concern unless you have a 4k or less mcu. In the case of an avr, the printf code is about 1.5k and once brought in you can use it as much as you want (so use it for everything that needs formatting). For arduino (which I don't use) I imagine no one using that will concern themselves with how the details of formatting are done as they just use print when the need arises.printf( "%lu\n", my10MHzCounter() ); //%lu for 32bits on avrSerial.println( my10MHzCounter() );

author: Picuino
author_rank: Super Contributor
timestamp: April 26, 2024, 07:10:21 pm
content: In this project, the problem was to print 48-bit numbers (16 bits of the main uC counter + 32 bits of the carry counter) in decimal (15 decimal digits).In that case the speed was not important, but to be able to do conversions of many bits. I solved it with custom subroutines for 48bit bit-shift and the algorithm I have previously published.With a 32-bit microcontroller it is simple, but with an 8-bit microcontroller you have to do the job differently.

author: cv007
author_rank: Frequent Contributor
timestamp: April 26, 2024, 10:29:25 pm
content: QuoteDifferently, but can still use the tools already in the toolbox.uint64_t v = readCount();uint32_t vH = v / 1000000000u;uint32_t vL = v % 1000000000u;//skip leading 0's, avr lu is uint32_tif( vH ) print( "%lu%09lu\n", vH, vL ); else print ( "%lu\n", vL );

author: jesuscf
author_rank: Frequent Contributor
timestamp: May 09, 2024, 11:16:33 pm
content: Here is a version that seems to be a bit faster for larger numbers:Code:void bin32_to_str2(unsigned long int val, char * s){unsigned char res0, res1, res2, res3, res4;unsigned char j, cy1, cy2;res0=res1=res2=res3=res4=0x00;for(j=0; j<32; j++){cy1=(val&0x80000000)?1:0;val<<=1;if(res0>0x04){if((res0&0x0f)>0x04) res0+=0x03;if((res0&0xf0)>0x40) res0+=0x30;}if(res1>0x04){if((res1&0x0f)>0x04) res1+=0x03;if((res1&0xf0)>0x40) res1+=0x30;}if(res2>0x04){if((res2&0x0f)>0x04) res2+=0x03;if((res2&0xf0)>0x40) res2+=0x30;}if(res3>0x04){if((res3&0x0f)>0x04) res3+=0x03;if((res3&0xf0)>0x40) res3+=0x30;}if(res3>0x04){if((res4&0x0f)>0x04) res4+=0x03;if((res4&0xf0)>0x40) res4+=0x30; // may not be needed}cy2=(res0&0x80)?1:0;res0<<=1;res0|=cy1;cy1=(res1&0x80)?1:0;res1<<=1;res1|=cy2;cy2=(res2&0x80)?1:0;res2<<=1;res2|=cy1;cy1=(res3&0x80)?1:0;res3<<=1;res3|=cy2;res4<<=1;res4|=cy1;}s=(res4>>4) | '0';s=(res4&0x0f)| '0';s=(res3>>4) | '0';s=(res3&0x0f)| '0';s=(res2>>4) | '0';s=(res2&0x0f)| '0';s=(res1>>4) | '0';s=(res1&0x0f)| '0';s=(res0>>4) | '0';s=(res0&0x0f)| '0';s=0;}Some quick tests:Code:Picuino bin32_to_str() ----------T: 244.187500 usNumber: 3114455555T: 121.000000 usNumber: 0000055555T: 83.250000 usNumber: 0000000005jesuscf bin32_to_str2() ----------T: 160.624980 usNumber: 3114455555T: 124.625000 usNumber: 0000055555T: 113.937510 usNumber: 0000000005My version uses 342 bytes, while Picuino's version uses only 164 bytes. The code was compiled with AVR-gcc and ran in a ATMega328p with a 16MHz crystal. I think Picuino's version can be made faster if it works with packed BCD numbers.

author: Nominal Animal
author_rank: Super Contributor
timestamp: May 10, 2024, 04:28:44 am
content: Quote from: jesuscf on May 09, 2024, 11:16:33 pmCare to compare to theu32_bcd(uint8_t bcd, uint32_t val)oru32_str(char *dst, uint32_t val)below?Code:// SPDX-License-Identifier: CC0-1.0#include <stdint.h>const uint32_t u32_bcd_bit = {800000000,80000000,8000000,800000,80000,8000,800,80,8,};void u32_bcd(uint8_t *dst, uint32_t val){const uint32_t *ptr = u32_bcd_bit;const uint8_t end = sizeof u32_bcd_bit + (uint8_t)(uintptr_t)u32_bcd_bit;uint32_t b10 = 4000000000;uint8_t bit = 0x40;uint8_t bcd = 0;while (1) {while (1) {if (val >= b10) {val -= b10;bcd |= bit;}bit >>= 1;if (!bit)break;elseif (bit == 0x08)b10 = *(ptr++);elseb10 >>= 1;}*(dst++) = bcd;if ((uint8_t)(uintptr_t)ptr == end)break;b10 = *(ptr++);bit = 0x80;bcd = 0x00;}}void u32_str(uint8_t *dst, uint32_t val){const uint32_t *ptr = u32_bcd_bit;const uint8_t end = sizeof u32_bcd_bit + (uint8_t)(uintptr_t)u32_bcd_bit;uint32_t b10 = 4000000000;uint8_t bit = 4;uint8_t dig = 0;while (1) {while (1) {if (val >= b10) {val -= b10;dig += bit;}bit >>= 1;if (!bit)break;b10 >>= 1;}if (dig) {if (dig < 10)dig += '0';*(dst++) = dig;dig = '0';}if ((uint8_t)(uintptr_t)ptr == end)break;b10 = *(ptr++);bit = 8;}if (!dig)*(dst++) = '0';*dst = '\0';}The table takes 36 bytes of Flash,u32_bcd()132 bytes, andu32_str()130 bytes, for a total of 298 bytes usingavr-gcc-5.4.0 -Os -mmcu=atmega328p; 150 and 154 bytes if using-O2. Note thatu32_str()omits leading zeroes, producing the same output assnprintf(buf, 12, "%lu", val);. They have both been verified to work correctly for all possibleuint32_tvalues, too.u32_bcd()returns the bcd digits in most significant byte first. You can useCode:void u32_bcd(uint8_t *dst, uint32_t val){const uint32_t *ptr = u32_bcd_bit;const uint8_t end = sizeof u32_bcd_bit + (uint8_t)(uintptr_t)u32_bcd_bit;uint32_t b10 = 4000000000;uint8_t bit = 0x40;uint8_t bcd = 0;dst += 5;while (1) {while (1) {if (val >= b10) {val -= b10;bcd |= bit;}bit >>= 1;if (!bit)break;elseif (bit == 0x08)b10 = *(ptr++);elseb10 >>= 1;}*(--dst) = bcd;if ((uint8_t)(uintptr_t)ptr == end)break;b10 = *(ptr++);bit = 0x80;bcd = 0x00;}}for least significant byte first.I don't think these are the fastest ones, especially for smaller numbers (because they do not short-circuit large numbers and always do the same number of iterations), but I found the approach interesting, and they might be useful if one needs both BCD and no-leading-zeroes decimal conversion.

author: jesuscf
author_rank: Frequent Contributor
timestamp: May 10, 2024, 07:48:35 am
content: Quote from: Nominal Animal on May 10, 2024, 04:28:44 amHere it is:Code:Picuino bin32_to_str() ----------T: 235.00usNumber: 3114455555T: 118.44usNumber: 0000055555T: 82.69usNumber: 0000000005Code:jesuscf bin32_to_str2() ----------T: 163.44usNumber: 3114455555T: 89.25usNumber: 0000055555T: 45.25usNumber: 0000000005Code:Nominal Animal u32_str() ----------T: 52.75usNumber: 3114455555T: 49.06usNumber: 55555T: 45.31usNumber: 5I slightly improved the speed of my function, butNominal Animal's function is clearly faster! Not only faster but much smaller as well!For reference, this is what I did:Code:void bin32_to_str2(unsigned long int val, char * s){static unsigned char res0, res1, res2, res3, res4;static unsigned char j, cy1, cy2;res0=res1=res2=res3=res4=0x00;for(j=0; j<32; j++){if (val&0x80000000) break;val<<=1;}for(; j<32; j++){cy1=(val&0x80000000)?1:0;val<<=1;if(res0>0x04){if((res0&0x0f)>0x04) res0+=0x03;if((res0&0xf0)>0x40) res0+=0x30;}if(res1>0x04){if((res1&0x0f)>0x04) res1+=0x03;if((res1&0xf0)>0x40) res1+=0x30;}if(res2>0x04){if((res2&0x0f)>0x04) res2+=0x03;if((res2&0xf0)>0x40) res2+=0x30;}if(res3>0x04){if((res3&0x0f)>0x04) res3+=0x03;if((res3&0xf0)>0x40) res3+=0x30;}if(res4>0x04){if((res4&0x0f)>0x04) res4+=0x03;//if((res4&0xf0)>0x40) res4+=0x30; // Never larger than 0x40}cy2=(res0&0x80)?1:0;res0=(res0<<1)|cy1;cy1=(res1&0x80)?1:0;res1=(res1<<1)|cy2;cy2=(res2&0x80)?1:0;res2=(res2<<1)|cy1;cy1=(res3&0x80)?1:0;res3=(res3<<1)|cy2;res4<<=1;res4|=cy1;}s=(res4>>4) | '0';s=(res4&0x0f)| '0';s=(res3>>4) | '0';s=(res3&0x0f)| '0';s=(res2>>4) | '0';s=(res2&0x0f)| '0';s=(res1>>4) | '0';s=(res1&0x0f)| '0';s=(res0>>4) | '0';s=(res0&0x0f)| '0';s=0;}

author: Nominal Animal
author_rank: Super Contributor
timestamp: May 10, 2024, 09:23:16 am
content: I'm surprised it did that well; but having roughly same duration regardless of the value converted was expected.Anyway, the idea behindu32_bcd()andu32_str()is the good ol' repeated subtraction.I simply realized that the AVR instruction set is such that it can shift the 32-bit value down by one bit in four cycles; and if we start with a power of ten multiplied by 8, we can shift it down in four steps and cover all possible values at that decimal position. So, we only need eight multiplied by all powers of ten. The most significant position cannot support an eight, but it can a four, so the loop starts with that special value.It also happens that decimal digits 0 through 9 have at most three bits set, so each decimal position simplifies to at most three 32-bit subtractions (but four shifts of the 32-bit value). The worst case value for both functions it is 3777777777 = 0xe12c5071, which does the most 32-bit subtractions (29).In essence, it is one of those "funky" combinations of powers of two and powers of ten logic.

author: jesuscf
author_rank: Frequent Contributor
timestamp: May 10, 2024, 05:33:18 pm
content: On a totally related question: how do you guys find out the size of a function in bytes? I was checking the .map file generated by the linker, but it only gives the start of the function, not the size. Based on the map file, I think I calculated the size of my function incorrectly. Now I am trying to find the size of a function by doing something like this in the code:Code:void u32_str(uint8_t *dst, uint32_t val){...lbl_end:sub_start=((unsigned int)u32_str)*2;sub_end=((unsigned int)&&lbl_end)*2;}Then I print (sub_end-sub_start) where sub_start and sub_end are global variables. Does it make sense?I managed to improve a little bit my function, but not near Nominal Animal's u32_str() yet:Code:Picuino bin32_to_str() ----------T: 234.31usNumber: 3114455555T: 117.75usNumber: 0000055555T: 82.00usNumber: 0000000005Start: 0x0222, End: 0x02be, Bytes: 156Code:jesuscf bin32_to_str2() ----------T: 124.88usNumber: 3114455555T: 63.69usNumber: 0000055555T: 30.25usNumber: 0000000005Start: 0x03b8, End: 0x0444, Bytes: 140Code:Nominal Animal u32_str() ----------T: 51.88usNumber: 3114455555T: 48.19usNumber: 55555T: 44.44usNumber: 5Start: 0x02fe, End: 0x037a, Bytes: 124And for reference here is my slightly improved function:Code:void bin32_to_str2(char * s, unsigned long int val){unsigned char res0, res1, res2, res3, res4;unsigned char j;res0=res1=res2=res3=res4=0x00;for(j=0; j<32; j++){if (val&0x80000000) break;val<<=1;}for(; j<32; j++){if(res0>0x04){if((res0&0x0f)>0x04) res0+=0x03;if((res0&0xf0)>0x40) res0+=0x30;}if(res1>0x04){if((res1&0x0f)>0x04) res1+=0x03;if((res1&0xf0)>0x40) res1+=0x30;}if(res2>0x04){if((res2&0x0f)>0x04) res2+=0x03;if((res2&0xf0)>0x40) res2+=0x30;}if(res3>0x04){if((res3&0x0f)>0x04) res3+=0x03;if((res3&0xf0)>0x40) res3+=0x30;}if(res4>0x04){if((res4&0x0f)>0x04) res4+=0x03;//if((res4&0xf0)>0x40) res4+=0x30; // Never larger than 0x40}res4<<=1;if(res3&0x80) res4|=1;res3<<=1;if(res2&0x80) res3|=1;res2<<=1;if(res1&0x80) res2|=1;res1<<=1;if(res0&0x80) res1|=1;res0<<=1;if (val&0x80000000) res0|=1;val<<=1;}s=(res4>>4) | '0';s=(res4&0x0f)| '0';s=(res3>>4) | '0';s=(res3&0x0f)| '0';s=(res2>>4) | '0';s=(res2&0x0f)| '0';s=(res1>>4) | '0';s=(res1&0x0f)| '0';s=(res0>>4) | '0';s=(res0&0x0f)| '0';s=0;lbl_end:sub_start=((unsigned int)bin32_to_str2)*2;sub_end=((unsigned int)&&lbl_end)*2;}

author: Nominal Animal
author_rank: Super Contributor
timestamp: May 10, 2024, 06:00:06 pm
content: Quote from: jesuscf on May 10, 2024, 05:33:18 pmFind the object file, or compile your saystuff.cto an object file using e.g.avr-gcc -Wall Os -mmcu=atmega328p -cstuff.cand then look at the symbol sizes in the symbol table:avr-objdump -tstuff.oThe size is the second hexadecimal number just left to the symbol name at extreme right. For example, on the code I posted, the output isCode:8.o: file format elf32-avrSYMBOL TABLE:00000000 l df *ABS*00000000 stuff.c00000000 l d .text00000000 .text00000000 l d .data00000000 .data00000000 l d .bss00000000 .bss0000003e l *ABS*00000000 __SP_H__0000003d l *ABS*00000000 __SP_L__0000003f l *ABS*00000000 __SREG__00000000 l *ABS*00000000 __tmp_reg__00000001 l *ABS*00000000 __zero_reg__00000000 l d .rodata00000000 .rodata00000000 l d .comment00000000 .comment00000000 g F .text0000008c u32_bcd00000000 g O .rodata00000024 u32_bcd_bit0000008c g F .text00000088 u32_str00000000 *UND*00000000 __do_copy_datawhich tells us thatu32_bcdFunction is 0x8c = 140 byes long,u32_stris 0x88 = 136 bytes long, and the read-onlyu32_bcd_bitdataObject is 0x24 = 36 bytes long.If you don't like hexadecimals, you can useavr-readelf -sstuff.oinstead, which outputsCode:Symbol table '.symtab' contains 16 entries:Num: Value Size Type Bind Vis Ndx Name0: 00000000 0 NOTYPE LOCAL DEFAULT UND1: 00000000 0 FILE LOCAL DEFAULT ABS stuff.c2: 00000000 0 SECTION LOCAL DEFAULT 13: 00000000 0 SECTION LOCAL DEFAULT 34: 00000000 0 SECTION LOCAL DEFAULT 45: 0000003e 0 NOTYPE LOCAL DEFAULT ABS __SP_H__6: 0000003d 0 NOTYPE LOCAL DEFAULT ABS __SP_L__7: 0000003f 0 NOTYPE LOCAL DEFAULT ABS __SREG__8: 00000000 0 NOTYPE LOCAL DEFAULT ABS __tmp_reg__9: 00000001 0 NOTYPE LOCAL DEFAULT ABS __zero_reg__10: 00000000 0 SECTION LOCAL DEFAULT 511: 00000000 0 SECTION LOCAL DEFAULT 612: 00000000 140 FUNC GLOBAL DEFAULT 1 u32_bcd13: 00000000 36 OBJECT GLOBAL DEFAULT 5 u32_bcd_bit14: 0000008c 136 FUNC GLOBAL DEFAULT 1 u32_str15: 00000000 0 NOTYPE GLOBAL DEFAULT UND __do_copy_dataIf you wonder why one has two different tools,readelfandobjdump, in the same toolchain, the main reason is thatobjdumpuses the GNU Binary File Descriptor library (BFD), as does e.g. the GNUasassembler; butreadelfparses ELF files directly. If their output ever disagrees, then it is a bug in the BFD library, and the differences will help pinpoint the bug it.

author: jesuscf
author_rank: Frequent Contributor
timestamp: May 12, 2024, 09:34:21 pm
content: It is worth to compare the proposed solutions with ltoa() from stdlib:Code:stdlib ultoa() ----------T: 223.44usNumber: 3114455555T: 101.00usNumber: 55555T: 4.75usNumber: 5Code:66: 000013be 68 FUNC GLOBAL DEFAULT 2 __ultoa_ncheck80: 000014e4 188 FUNC GLOBAL DEFAULT 2 __ultoa_invert104: 000013c0 0 NOTYPE GLOBAL DEFAULT 2 __ultoa_common

author: Nominal Animal
author_rank: Super Contributor
timestamp: May 12, 2024, 10:41:41 pm
content: EDIT: the first version produced digits in the opposite order, i.e. least significant digit leftmost, not rightmost. Fixed.ultoa()etc. are typically based on a divide-by-ten (or divide-by-radix) approach, i.e.Code:const char digit[] = "0123456789abcdefghijklmnopqrstuvwxyz";char *ultoa(unsigned long val, char *dst, int radix){char *d = dst;do {*(d++) = digit[val % radix];val /= radix;} while (val);*d = '\0';// Swap digit order{char *b = dst;char *q = d;while (b < q) {char *cb = *b;char *cq = *(--q);*(b++) = cq;*q = cb;}}return d;}Here are equivalent 32-bit unsigned and signed integer versions, using a 32-bit:8-bit division with remainder (instead of the ABI __udivmodsi4 32-bit:32-bit division with remainder):Code:#include <stdint.h>typedef struct {uint8_t q;uint8_t r;} div8bit;div8bit divmod8bit_loop(uint8_t r, uint8_t b, const uint8_t v, const uint8_t d){uint8_t q = 0;while (b) {r <<= 1;if (b & v)r++;if (r >= d) {r -= d;q |= b;}b >>= 1;}return (div8bit){ .q = q, .r = r };}static uint8_t highbit(uint8_t v){uint8_t b = 0x80;while (!(b & v))b >>= 1;return b;}// Divide *vptr by d, d >= 1, d < 128. Returns remainder.uint8_t divmod8bit(uint32_t *const vptr, const uint8_t d){const uint32_t val = *vptr;if (!val)return 0;uint32_t q = 0;uint8_t b, w;div8bit t = { .q = 0, .r = 0 };if ((w = (uint8_t)(val >> 24))) {b = highbit(w);goto b3;} elseif ((w = (uint8_t)(val >> 16))) {b = highbit(w);goto b2;} elseif ((w = (uint8_t)(val >> 8))) {b = highbit(w);goto b1;} else {b = highbit((uint8_t)(val));goto b0;}b3:t = divmod8bit_loop(t.r, b, (uint8_t)(val >> 24), d);q |= (uint32_t)(t.q) << 24;b = 0x80;b2:t = divmod8bit_loop(t.r, b, (uint8_t)(val >> 16), d);q |= (uint32_t)(t.q) << 16;b = 0x80;b1:t = divmod8bit_loop(t.r, b, (uint8_t)(val >> 8), d);q |= (uint32_t)(t.q) << 8;b = 0x80;b0:t = divmod8bit_loop(t.r, b, (uint8_t)(val), d);q |= (uint32_t)(t.q);*vptr = q;return t.r;}char *u32_str(char *dst, uint32_t val, const char *base, uint8_t radix){uint32_t v = val;char *d = dst;uint8_t n = 0;do {*(d++) = base[divmod8bit(&v, radix)];n++;} while (v);*d = '\0';// Swap digit ordern >>= 1;{char *b = dst;char *q = d;while (n-->0) {const char cb = *b;const char cq = *(--q);*(b++) = cq;*(q) = cb;}}return d;}char *i32_str(char *dst, int32_t val, const char *base, uint8_t radix){if (val < 0) {*(dst++) = '-';return u32_str(dst, -val, base, radix);} elsereturn u32_str(dst, val, base, radix);}u32_str(dst,value, "0123456789", 10)has been verified to produce correct results for all 32-bit unsigned integervalues.In addition to the radix characters string,divmod8bit()anddivmod8bit_loop()take 346 bytes,u32_str()194 bytes, andi32_str()additional 38 bytes, for a total of 572 bytes, usingavr-gcc-5.4.0 -Os -mmcu=atmega328p.Standard 32-bit:32-bit unsigned integer binary division with remainder can be written asCode:#include <stdint.h>typedef struct {uint32_t quotient;uint32_t remainder;} u32_qr;u32_qr u32_divmod(uint32_t n, uint32_t d){// Check for division by zero.if (!d)return (u32_qr){ .quotient = 0, .remainder = 0 };// Zero numerator must be handled separately. We combine all n < d cases here.if (n < d)return (u32_qr){ .quotient = 0, .remainder = n };// Find highest bit set.uint32_t bit = (uint32_t)(1) << 31;while (!(bit & n))bit >>= 1;// One iteration for each bit left.u32_qr result = { .quotient = 0, .remainder = 0 };while (bit) {result.remainder <<= 1;result.remainder |= !!(n & bit); // 1 if bit set, 0 otherwise.if (result.remainder >= d) {result.remainder -= d;result.quotient |= bit;}bit >>= 1;}return result;}Because remainder is at most one bit larger than divisor, and the divisor in these radix calculations is always less than 128, the AVR version ofdivmod8bit()splits the calculation into four steps, each step working on a single byte. It should speed up the division, without increasing the function size much.