Alternative+pseudo-random+numbers

//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:

code format="asm" .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 code

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

code format="asm" .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 code

Call RandRange using code similar to the following:

code format="asm" push N%     call RandRange code

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 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:

code format="asm" .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 code

To call this routine use code similar to the following:

code format="asm" push ^N# call RandFloat code

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 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:

code format="asm" .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 code

To seed the random number generator use the following code:

code format="asm" push hN     push lN      call RandSeed code

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 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:

code format="bb4w" 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 code