All the bases that are a power of two, like the binary (21), quaternary (22), octal (23), hexadecimal (24) bases, have an integral number of bits per digit1.
Thus to retrieve each digit2 of a numeral we simply break the number intro group of n bits starting from the LSb (the right).
For example for the quaternary base, we break a 16-bit number in groups of two bits. There are 8 of such groups.
Not all power of two bases have an integral number of groups that fits 16 bits; for example, the octal base has 5 groups of 3 bits that account for 3·5 = 15 bits out of 16, leaving a partial group of 1 bit3.
The algorithm is simple, we isolate each group with a shift followed by an AND operation.
This procedure works for every size of the groups or, in other words, for any base power of two.
In order to show the digits in the right order the function start by isolating the most significant group (the leftmost), thereby it is important to know: a) how many bits D a group is and b) the bit position S where the leftmost group starts.
These values are precomputed and stored in carefully crafted constants.
The parameters must be pushed on the stack.
Each one is 16-bit wide.
They are shown in order of push.
|N||The number to convert|
|Base||The base to use expressed using the constants |
|Print leading zeros||If zero no non-significant zeros are print, otherwise they are. The number 0 is printed as "0" though|
push 241 push BASE16 push 0 call print_pow2 ;Prints f1 push 241 push BASE16 push 1 call print_pow2 ;Prints 00f1 push 241 push BASE2 push 0 call print_pow2 ;Prints 11110001
Note to TASM users: If you put the constants defined with
EQU after the code that uses them, enable multi-pass with the
/m flag of TASM or you'll get Forward reference needs override.
;Parameters (in order of push): ; ;number ;base (Use constants below) ;print leading zeros print_pow2: push bp mov bp, sp push ax push bx push cx push dx push si push di ;Get parameters into the registers ;SI = Number (left) to convert ;CH = Amount of bits to shift for each digit (D) ;CL = Amount od bits to shift the number (S) ;BX = Bit mask for a digit mov si, WORD PTR [bp+08h] mov cx, WORD PTR [bp+06h] ;CL = D, CH = S ;Computes BX = (1 << D)-1 mov bx, 1 shl bx, cl dec bx xchg cl, ch ;CL = S, CH = D _pp2_convert: mov di, si shr di, cl and di, bx ;DI = Current digit or WORD PTR [bp+04h], di ;If digit is non zero, [bp+04h] will become non zero ;If [bp+04h] was non zero, result is non zero jnz _pp2_print ;Simply put, if the result is non zero, we must print the digit ;Here we have a non significant zero ;We should skip it BUT only if it is not the last digit (0 should be printed as "0" not ;an empty string) test cl, cl jnz _pp_continue _pp2_print: ;Convert digit to digital and print it mov dl, BYTE PTR [DIGITS + di] mov ah, 02h int 21h _pp_continue: ;Remove digit from the number sub cl, ch jnc _pp2_convert pop di pop si pop dx pop cx pop bx pop ax pop bp ret 06h
This data must be put in the data segment, the one reached by `DS`. DIGITS db "0123456789abcdef" ;Format for each WORD is S D where S and D are bytes (S the higher one) ;D = Bits per digit --> log2(BASE) ;S = Initial shift count --> D*[ceil(16/D)-1] BASE2 EQU 0f01h BASE4 EQU 0e02h BASE8 EQU 0f03h BASE16 EQU 0c04h
To port the code to NASM remove the PTR keyword from memory accesses (e.g.
mov si, WORD PTR [bp+08h] becomes
mov si, WORD PTR [bp+08h])
The function can be easily extended to any base up to 2255, though each base above 216 will print the same numeral as the number is only 16 bits.
To add a base:
BASExwhere x is 2n.
Example: adding base 32
We have D = 5 and S = 15, so we define
BASE32 EQU 0f05h.
We then add sixteen more digits:
DIGITS db "0123456789abcdefghijklmnopqrstuv".
As it should be clear, the digits can be changed by editing the
1 If B is a base, then it has B digits per definition. The number of bits per digit is thus log2(B). For power of two bases this simplifies to log2(2n) = n which is an integer by definition.
2 In this context it is assumed implicitly that the base under consideration is a power of two base 2n.
3 For a base B = 2n to have an integral number of bit groups it must be that n | 16 (n divides 16). Since the only factor in 16 is 2, it must be that n is itself a power of two. So B has the form 22k or equivalently log2(log2(B)) must be an integer.