As Jerry explained, all math operations treat numbers as floating point... even numbers stored as integers. This was my first attempt:
1 rem
2 rem fizzbuzz in commodore basic
3 rem
10 t = time
20 for i = 1 to 100
30 n = i / 15 :if n = int(n) then print "fizzbuzz" :goto 70
40 n = i / 3 :if n = int(n) then print "fizz" :goto 70
50 n = i / 5 :if n = int(n) then print "buzz" :goto 70
60 print i
70 next
80 print "ran for" (time - t) / 60 "seconds"
Skipping the rest of the conditions when a condition passes speeds it up significantly.
Assigning the value of X/Y
to a variable and reusing it is faster than calculating it twice. Some places around the internet mention the formula X-INT(X/Y)*Y
, but that was slower than N=INT(N)
in my tests.
According to this article you can write your own modulo "operator" in assembly and use it in BASIC. The assembly program is available here. Seems like overkill to me.
You might think that you could replace the goto 70
with next
, but it doesn't quite work. Once the loop is complete, it will go to the next line, and call the next
on that line, and you'll get a "next without for" error. Technically it still does what it's supposed to do, but it doesn't exit cleanly.
You could do something like this as a compromise, but it's hackish:
1 rem
2 rem fizzbuzz in commodore basic
3 rem
10 t = time
20 for i = 1 to 100
30 n = i / 15 :if n = int(n) then print "fizzbuzz" :next
40 n = i / 3 :if n = int(n) then print "fizz" :next
50 n = i / 5 :if n = int(n) then print "buzz" :next :goto 70
60 print i: next
70 print "ran for" (time - t) / 60 "seconds"
Edward had a good idea: keep separate counters for fizz and buzz instead of doing division. Here's a variation on his approach that sacrifices speed for readability. It doesn't use goto
and is about as readable as the original code in the question. It's faster than my examples above, but slower than Edward's solution (since it doesn't skip other conditions after passing one, and doesn't have a combined "fizzbuzz" case).
1 rem
2 rem fizzbuzz in commodore basic
3 rem
10 t = time
20 fizz = 0 :buzz = 0
30 for i = 1 to 100
40 fizz = fizz + 1 :buzz = buzz + 1
50 print
60 if fizz = 3 then print "fizz"; :fizz = 0
70 if buzz = 5 then print "buzz"; :buzz = 0
80 if fizz > 0 and buzz > 0 then print i;
90 next
100 print ,"ran for" (time - t) / 60 "seconds"
Incidentally, the biggest performance bottleneck seems to be printing carriage returns and moving everything up the screen. If you try the original code in the question without printing any carriage returns, it will run about three times faster.
Assuming the screen has 40 columns by 25 rows, it's just big enough to fit the fizzbuzz output neatly into columns without scrolling. This version does just that, shaving off about a second from the previous version.
1 rem
2 rem fizzbuzz in commodore basic
3 rem
10 t = time
15 print chr$(147);
20 fizz = 0 :buzz = 0 :row = 0 :col = 0
30 for i = 1 to 100
40 fizz = fizz + 1 :buzz = buzz + 1
50 row = row + 1
60 poke 211, col
70 if fizz = 3 then print "fizz"; :fizz = 0
80 if buzz = 5 then print "buzz"; :buzz = 0
90 if fizz > 0 and buzz > 0 then print i;
100 if row = 25 then print chr$(19); :col = col + 10 :row = 0
110 if row > 0 then print
120 next
130 secs = (time - t) / 60
140 poke 198, 0 :wait 198, 1
150 print chr$(147) "ran for" secs "seconds"
Notes:
chr$(147)
is CLR
, a form feed character which clears the screen.
poke 211, col
manipulates system memory at the address where the cursor column is stored.
chr$(19)
is HOME
; it moves the cursor to the top row.
poke 198, 0 :wait 198, 1
waits for the user to press a key before continuing, so the timing message and READY prompt don't overwrite any of the output at the end.
Here's what the output from the final version looks like:
