Programming the 80’s way #4
Random Numbers in 16 bits
The ZX Spectrum Next’s manual explains in Chapter 11 how random numbers generator works.
There is a useful disassembly (https://skoolkid.github.io/rom/dec/asm/9720.html) about the standard ROM routine, that using the “ROM calculator” in short uses the following formula
NEXT_SEED := ( ( 75 * ( SEED + 1 ) ) % 65537 ) – 1
where % is the “modulo operator”.
So starting with any SEED between 0 and 65535, the NEW_SEED is always between 0 and 65535, and the series that emerges is a fairly random sequence of integers that repeats itself after 65536 iterations.
Spectrum’s RND function simply returns NEW_SEED divided by 65536.
Since a number larger than 65536 is involved (i.e. modulo base 65537), and Spectrum’s Forth usually provides only quotient/remainder routine that can divide a 32-bits integer giving a 16-bits integer result and a 16-bits remainder, for a long time I deemed impossible performing such an operation without “large divisor” quotient/remainder routine. And, this meant that it was impossible in my vForth too.
In this post, I show a clever achievement that circumvents the classic 16-bits limitation, in this case.
First of all, since Forth provides words that can handle 32-bits numbers addition and subtraction, I thought that I could “emulate” a quotient/remainder routine by “repeated subtractions”, and that was a fairly right path.
Immediately I saw that sutracting 65537 from a 32-bit integer, i.e. $10001 in hexadecimal, was roughly equivalent to decrement both higher and lower 16-bits parts of it,
and that the reminder was simply some kind of “difference” between these two parts.
This was a good start, since instead of performing some huge division I just had to subtract two integers, and if the subtraction gave a negative number I just had to re-add 65537 to have it positive again.
Then I had to resolve these two limit conditions inherent using 16-bits integer Arithmetics :
1. when SEED is 65535, the first inner addition +1 produces a ZERO instead of a 65536 giving a wrong result.
2. 65537 is equivalent to 1.
So I exploited the following Forth final optimized definition that I listed below.
Here is a few seconds video (https://youtu.be/4Wh7r0ZHbSY) that shows how fast this routine is and how fast we can plot 65536 pixels using LAYER 1,2 graphics-mode.
decimal 23670 constant SEED
: next_seed ( n1 — n2 )
1+ ?dup
if
75 UM* 2dup U< if – else – 1- then
else
-75 \ = 65461
then
;needs graphics layer12
: tester
256 0 do
256 0 do
SEED @ next_seed SEED !
SEED @ 9 rshift 24 + SEED @ 511 and plot
loop
loop
;cls tester
_M.
When the MGT SAM Coupe was being developed I roped my friend Dr Andy Wright into writing the BASIC interpreter, drawing on his Spectrum BetaBasic extension. He worked out a way to generate the same random number sequence as Stephen Vickers documented in the ZX-81 and Spectrum ROMs, revisited in Forth above, but in a tiny fraction of the time. Here it is:
SEED DW 1
LD B,0
LD HL,(SEED)
LD E,0FDH
LD D,L
LD A,H
ADD HL,HL
SBC A,B
EX DE,HL
SBC HL,DE
SBC A,B
LD C,A
SBC HL,BC
JR NC,+1
INC HL
LD (SEED),HL
Every time you call that it puts a new random number in SEED and returns it in HL
Amazingly concise and compatible – the Sam ROM source is freely distributable and packed with ingenious Z80 routines along similar lines, all extending the ZX ROM concepts. While billed as a ‘disassembly’ it is actually the original source, from floppies used on Andy’s Amstrad CPC, with a helpful overview at the start. I also have most but not all the source for the MasterDOS and MasterBASIC add-ons (one of the latter discs has been lost). Read this first: http://ftp.nvg.ntnu.no/pub/sam-coupe/docs/SAM%20Coupe%20ROM%20v3.0%20Annotated%20Disassembly.pdf