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.

 

 

One Comment

  • 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

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.