Binary and decimal are two different number bases. The normal way1 to have an integer in a register is pure binary, no decimal involved. A decimal or hex representation of that binary number is convenient for humans to look at, but that doesn't change what's actually in the register.
In your case, 0b0101'0000
is decimal 80
, hex 0x50
; you could write the constant any of those 3 ways in C++ source code. Note that the high 4 bits are 0101
, binary for 5
, and the low 4 are 0000
(0
).
Base 16 is a power of 2, so 4-bit groups of bits map to hex digits. To get ASCII codes, you could use a lookup table, or add '0'
or 'A'
depending on whether the nibble value is >= 10 or not. (Or to add an extra 7
('A' - '0'
) for numbers above 10, so it's just an if, not if/else). To get 7-segment-display codes, you might use a lookup table indexed by 4-bit integers.
In AVR assembly, you can isolate the two 4-bit halves of an 8-bit integer with and
and swap
instructions, like a compiler would make from this source
void foo(unsigned char in, unsigned char *out)
{
out[0] = in>>4; // high hex digit is first in printing order
out[1] = in & 0x0f; // low hex digit.
// Actually just 0..15 integers, not mapped to ASCII or 7-segment codes
}
(Godbolt)
foo(unsigned char, unsigned char*):
mov r30,r22
mov r31,r23 # output pointer
mov r25,r24 # copy the first input
swap r25
andi r25, 15 # emulate >> 4 with SWAP + ANDI with 0xf
st Z,r25
andi r24, 15 # isolate the low 4 bits
std Z+1,r24
ret
The storing is of course optional; if you want to use the values right away, you don't need an array to store them in. That just made it possible to write a C function that produced both outputs so we could look at the compiler-generated asm.
So really it just took mov
+ swap
+ 2x andi
instructions to isolate the two nibbles which we can then map to hex digits. AVR only has shifts by 1 bit at a time, except for swap
which rotates by 4. (Thus we have to mask away the high 4 bits to emulate shifting them out.)
You never want to have 0011 0010
(decimal 50) in a register in the first place: you could get that by multiplying 0b0101
by ten (0b1010
), but the only way you could split it up again into 5
and 0
would be dividing by ten (0b1010
), which is computationally much more difficult. And it makes no sense, you have hex 0x50
, not decimal 50
.
Don't do base-conversion by creating a binary integer whose decimal digits are the digits you want for some other base. That won't work for numbers like 0xa5
since decimal only allows digits up to 9, and for smaller bases like 8
or 2
you can only store at most 1111111111
in a 32-bit integer, so that's only 10 bits. And it gives you no easy way to get the digits back out when you want to do anything with them.
Footnote 1: Computers store everything in bits because they use binary logic, on or off, not 10 or 16 voltage levels. Another way you can use those bits is BCD, Binary Coded Decimal. That's a format where you use 4 bits for every decimal digit, and you have to manually do wrapping and carry-out for digits that become greater than 10 when adding. Or unpacked BCD where you store one decimal digit per byte, saving the unpacking work.