patternMajor
16-bit FizzBuzz in x86 NASM assembly
Viewed 0 times
bitx86fizzbuzzassemblynasm
Problem
Since this problem involves small numbers (particularly with a small loop count of 100), it's possible to ease the modulo operation setup by simply working with 16-bit and 8-bit registers:
$$\dfrac{\text{[AX] (16-bit register)}}{\text{[other 8-bit register]}} = \text{[AH] (remainder)}$$
My main concern is with the layout. Every "basic" high-level implementation I've seen has a check and print together for each case. I've found it easier to do the same thing here, but I'm not sure if that would also be too readable in assembly.
I'm also aware that it's good to minimize register-moving. Unfortunately, I still do that with every case since I'm incrementing with one register (
Macros used:
It's not necessary to address the macros; they do work properly.
```
%include "macros.s"
.DATA
fizz_lbl: DB "Fizz", 0
buzz_lbl: DB "Buzz", 0
fizzbuzz_lbl: DB "FizzBuzz", 0
.CODE
.STARTUP
xor CX, CX ; counter
main_loop:
inc CX
cmp CX, 100
jg done
fizzbuzz_check:
mov AX, CX ; dividend = counter
mov BH, 15 ; divisor
div BH ; (counter / 15)
cmp AH, 0 ; counter divisible by 15?
je print_fizzbuzz ; if so, proceed with printing
jmp fizz_check ; if not, try checking for fizz
print_fizzbuzz:
PutStr fizzbuzz_lbl
nwln
jmp main_loop
fizz_check:
mov AX, CX ; dividend = counter
mov BH, 3 ; divisor
div BH ; (counter / 3)
cmp AH, 0 ; counter divisible by 3?
je print_fizz ; if so, proceed with printing
jmp
$$\dfrac{\text{[AX] (16-bit register)}}{\text{[other 8-bit register]}} = \text{[AH] (remainder)}$$
My main concern is with the layout. Every "basic" high-level implementation I've seen has a check and print together for each case. I've found it easier to do the same thing here, but I'm not sure if that would also be too readable in assembly.
I'm also aware that it's good to minimize register-moving. Unfortunately, I still do that with every case since I'm incrementing with one register (
CX) and using another (AX) for the dividend. I can stick to AX for both, but that may involve keeping a copy of the current counter value, which may just make the code a bit more complicated. I suppose it's not much of a problem here anyway.Macros used:
nwln- prints a newline
PutStr- prints a defined string
PutInt- prints a 16-bit integer value
It's not necessary to address the macros; they do work properly.
```
%include "macros.s"
.DATA
fizz_lbl: DB "Fizz", 0
buzz_lbl: DB "Buzz", 0
fizzbuzz_lbl: DB "FizzBuzz", 0
.CODE
.STARTUP
xor CX, CX ; counter
main_loop:
inc CX
cmp CX, 100
jg done
fizzbuzz_check:
mov AX, CX ; dividend = counter
mov BH, 15 ; divisor
div BH ; (counter / 15)
cmp AH, 0 ; counter divisible by 15?
je print_fizzbuzz ; if so, proceed with printing
jmp fizz_check ; if not, try checking for fizz
print_fizzbuzz:
PutStr fizzbuzz_lbl
nwln
jmp main_loop
fizz_check:
mov AX, CX ; dividend = counter
mov BH, 3 ; divisor
div BH ; (counter / 3)
cmp AH, 0 ; counter divisible by 3?
je print_fizz ; if so, proceed with printing
jmp
Solution
Since we're doing this in assembly language, it makes sense to do it much more efficiently than is typically done in high level languages. Otherwise, why bother with assembly language? So with that said, there are ways that this can be made much, much more efficient.
Avoid division
The
It could be easily expanded to say:
Then instead of dividing, simply decrement:
Naturally the various
Improve formatting
Generally speaking, assembly language code is not indented in the way you have your code indented. It's much more linear, with the only indentation for assembly language statements or directives.
Consider better I/O
Your output routines are not shown, but it's likely that it would be more efficient to keep the numeric output in string form, incrementing each ASCII digit and emitting the string, rather than repeatedly converting from binary register contents to a string value.
Avoid division
The
div instruction in x86 is one of the slower instructions possible. Since we already know that we're looking for numbers divisible by 3, 5 or both, what would make far more sense is to simple keep countdown counters for both. Your initialization currently says:xor cx, cxIt could be easily expanded to say:
xor cx, cx
mov bx, 0503h ; set bh = 5 counter, bl = 3 counterThen instead of dividing, simply decrement:
inc cx
cmp cx, 100
jg done
; instead of this...
; dec bh
; dec bl
; cmp bx, 0
; per suggestion from @Chris Jester-Young use this:
sub bx, 0101h
je print_fizzbuzz
test bl, bl
je print_fizz
test bh, bh
je print_buzz
print_other:Naturally the various
print_... routines would have to reset bh, bl or both as well as printing.Improve formatting
Generally speaking, assembly language code is not indented in the way you have your code indented. It's much more linear, with the only indentation for assembly language statements or directives.
Consider better I/O
Your output routines are not shown, but it's likely that it would be more efficient to keep the numeric output in string form, incrementing each ASCII digit and emitting the string, rather than repeatedly converting from binary register contents to a string value.
Code Snippets
xor cx, cx
mov bx, 0503h ; set bh = 5 counter, bl = 3 counterinc cx
cmp cx, 100
jg done
; instead of this...
; dec bh
; dec bl
; cmp bx, 0
; per suggestion from @Chris Jester-Young use this:
sub bx, 0101h
je print_fizzbuzz
test bl, bl
je print_fizz
test bh, bh
je print_buzz
print_other:Context
StackExchange Code Review Q#56884, answer score: 37
Revisions (0)
No revisions yet.