I am trying to figure out how to convert a 32 bit integer into its hex representation. I know that I need to separate the last 4 bits, use a switch case to find which hex character it corresponds to. And then I need to repeat this for each of the 4 bits. I am struggling to find out how to get each of the 4 bits from LSB to MSB. Can anyone help? Thanks!
-
Can you call `printf`? – Fiddling Bits Nov 20 '18 at 20:19
-
No, I want to implement it on my own, I would only like to use putchar() – Nov 20 '18 at 20:20
-
Just mask out all but 4 bits (use an AND instruction). Then shift by 4 bits and repeat... – Paul R Nov 20 '18 at 20:23
-
don't know why i didn't think of shifting. thanks. – Nov 20 '18 at 20:27
1 Answers
Masking and shifting, as suggested in the comments, will get you the individual nibbles (half-bytes) that make up the word. But to print the number using putchar
or equivalent, you'll need to convert these nibbles to ASCII characters. You don't need a 'switch-case' for this (or rather its equivalent in assembly language, a 'jump table'). You could use a lookup table, given that there will only be 16 entries, or you could conditionally add to the nibble to form a character.
The ASCII digits 0-9 have character codes 48-57. The uppercase letters have character codes starting at 65 for A (the lowercase letters start from 97) and of course 0xA represents the decimal value 10. So you can turn a nibble into a hex string by adding 55 to it if its value is 10 or more, and 48 otherwise. (Equivalently, add 48 unconditionally and add a further 7 to it if the resulting value is 58 or more.)
You'll want to print the hex digits out starting with the most significant digit, which is the nibble in bits 28-31 of the word. If you shift the whole word left by 4 bits each time you output a character then bits 28-31 will always contain the next nibble to process.
The following is untested and written off the top of my head, so no warranty, but it might set you on the right track:
; Assume number to be converted is in r4. Note r4 is clobbered
; Initialise loop counter
MOV r5, #8
.loop
; Take most significant nibble from r4 into r0
MOV r0, r4, LSR #28
; Shift r4 for next time
MOV r4, r4, LSL #4
; For each nibble (now in r0) convert to ASCII and print
ADD r0, r0, #48
CMP r0, #58 ; did that exceed ASCII '9'?
ADDHS r0, r0, #7 ; add 'A' - ('0'+10) if needed
BL putchar
; Decrement loop counter, loop if not zero
SUBS r5, r5, #1
BNZ .loop

- 2,404
- 1
- 15
- 18
-
For bases that aren't powers of 2, you don't really get a choice about starting from the LSD with repeated division. The usual way to deal with this is to start at the *end* of a buffer and decrement a pointer. When you're done, print from the current position. (example in C and x86 asm here: [How do I print an integer in Assembly Level Programming without printf from the c library?](https://stackoverflow.com/a/46301894)). But yeah, given the choice, starting with the high nibble is easiest. – Peter Cordes Nov 20 '18 at 23:49
-
You don't need to AND before right shifting. Your `0xFF000000` mask keeps the high byte (2 nibbles = 2 hex digits = 2 Fs). But then right-shifting by 28 keeps only the high one. The AND was useless. :/ I think you can optimize down to `add r0, r6, r4 LSR #28`, if you have `'0'` in `r6`. I don't think ARM can barrel-shift a register in an instruction with an immediate. Or if you rotate `r4` with `mov r4, r4, ROL #4`, you can `and r0, r4, #0xF` to use only 1 shift. And BTW, it would be clearer to use character literals like `add r0,r0, #'0'`, if that's the right syntax. – Peter Cordes Nov 20 '18 at 23:52
-
Thanks - corrected. The `0xFF000000` was a typo, and the unnecessary `AND` was an oversight. :-) Not sure about the syntax for character literals so I didn't include them - didn't have the time to test! Agreed that there may be optimisations, but I wanted my code to be clear and to demonstrate the principles rather than to be optimal in any sense. – cooperised Nov 21 '18 at 12:50