by Jon Ripley, August 2006

BBC BASIC for Windows provides a pseudo-random number generator for use in BASIC programs but has no built-in support for generating pseudo-random numbers from assembly language programs. This article contains code to add support for generating pseudo-random numbers from assembly language that can be added to any program.

Rand


The following routine generates a 32-bit pseudo-random number when called, the pseudo-random number is returned in the eax register:

        .seed     dd 1 :dd 0
 
        .Rand
        mov    eax, &4C957F2D       ; |
        mul    dword [seed]         ; |
        push   edx                  ; |
        mov    edx, [seed]          ; |
        imul   ebx, edx, &5851F42D  ; |\ 
        pop    edx                  ; | - seed = seed * &5851F42D4C957F2D
        add    edx, ebx             ; |/
        mov    ebx, [seed+4]        ; |
        imul   ebx, ebx, &4C957F2D  ; |
        add    edx, ebx             ; |
        add    eax, 1               ; seed = seed + 1
        adc    edx, 0
        mov    [seed], eax          ; store the new seed
        mov    [seed+4], edx
        shrd   eax, edx, 21         ; shift right 21 bits
        ret                         ; return

Rand(N)


The following routine generates a pseudo-random number in the range 1 to N:

        .RandRange
        call   Rand                 ; get random number
        mov    ebx, [esp+4]         ; read range limit
        xor    edx,edx              ; clear edx register
        div    ebx                  ; do integer division; eax / ebx
        mov    eax,edx              ; read remainder of division
        inc    eax                  ; add one
        ret    4                    ; restore stack and return

Call RandRange using code similar to the following:

      push N%
      call RandRange

The pseudo-random number is returned in eax. Here N% can be a register eax, read from a pointer to a 4 byte block in memory [addr], a BASIC constant N% defined at assemble time, a BASIC variable [^N%] or a numeric constant 1234. If the assembler reports a Size needed error add the dword prefix to the pushed parameter.

To change the range of numbers returned to 0 to N remove the 'inc eax' instruction.

Rand(0)


The following routine generates a pseudo-random number in the range 0.0 to 1.0, exclusive of 1.0:

        .RandFloat
        call   Rand                 ; get random number
        and    eax, &7FFFFFFF       ; clear top bit
        push   eax                  ; push random number on stack
        call   Rand                 ; get random number
        push   eax                  ; push random number on stack
        push   &80000000            ; \ Put &8000000000000000
        push   0                    ; / on the stack
        fild   qword [esp]          ; load &8000000000000000
        fild   qword [esp+8]        ; load random number
        fdivrp st1,st0              ; divide random number by &8000000000000000
        fabs                        ; convert result to absolute value
        mov    eax,[esp+20]         ; read location to store result
        fstp   qword [eax]          ; store result
        add    esp,16               ; free local variables
        ret    4                    ; restore stack and return

To call this routine use code similar to the following:

      push ^N#
      call RandFloat

The result is stored in the memory pointed to by the parameter. Here ^N# is a pointer to a 64-bit floating point BASIC variable but can be a pointer to an 8 byte block of memory [addr]. If the assembler reports a Size needed error add the dword prefix to the pushed parameter.

Seeding the pseudo-random number generator


The following routine is called to seed the pseudo-random number generator with a 64-bit seed:

        .RandSeed
        mov    eax, [esp+4]         ; load new seed
        mov    edx, [esp+8]        
        mov    dword [seed], eax    ; store new seed
        mov    dword [seed+4], edx 
        ret    8                    ; restore stack and return

To seed the random number generator use the following code:

      push hN
      push lN
      call RandSeed

Here we pass a 64-bit integer value to seed the pseudo-random number, hN is the top 32-bits of the seed and lN is the bottom 32-bits of the seed. By default the seed is set to the value of TIME when the code was assembled. If the assembler reports a Size needed error add the dword prefix to the pushed parameter.

Calling the pseudo-random number generator from BASIC


These routines may be called from BASIC, instead of using the RND function, if you really want to:

      result% = USR Rand :REM Return a pseudo-random number in result%
      SYS RandFloat, ^N# :REM Return a pseudo-random float in N#
      SYS RandRange, N TO result% :REM Return a range limited pseudo-random number in result%
      SYS RandSeed, TIME, TIME :REM Seed the pseudo-random generator using TIME

References


The multiplier 6364136223846793005 was obtained from Knuth, D.E., "The Art of Computer Programming," Vol 2, Seminumerical Algorithms, Third edition, Addison-Wesley, 1998, p. 106 (line 26) & p. 108.