# Intel x86 Assembly Language & Microarchitecture MS-DOS, TASM/MASM function to print a 16-bit number in binary, quaternary, octal, hex

## Print a number in binary, quaternary, octal, hexadecimal and a general power of two

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.

## Parameters

The parameters must be pushed on the stack.
Each one is 16-bit wide.
They are shown in order of push.

ParameterDescription
NThe number to convert
BaseThe base to use expressed using the constants `BASE2`, `BASE4`, `BASE8` and `BASE16`
Print leading zerosIf zero no non-significant zeros are print, otherwise they are. The number 0 is printed as "0" though

## Usage

``````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.

## Code

``````;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
``````

## Data

``````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
``````

## NASM porting

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]`)

## Extending the function

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:

1. Define a new constant `BASEx` where x is 2n.
The lower byte, named D, is D = n.
The upper byte, named S, is the position, in bits, of the higher group. It can be calculated as S = n · (⌈16/n⌉ - 1).
2. Add the necessary digits to the string `DIGITS`.

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 `DIGITS` string.

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. PDF - Download Intel x86 Assembly Language & Microarchitecture for free