IGNORED

# rnd XB PseudoRandom Number Generator Discussion—RND and RANDOMIZE...updated 09MAY2024

## Recommended Posts

It is my intention, here, to explain the details of the pseudorandom number generator (PRNG) in TI Extended Basic (XB). Over the next few days, I will expand this first post to include my analysis to date in an effort to make it as nearly complete as I am able. I will also make any updates relevant to further insights from the ensuing discussion:

═══XB RND function explanation═══Part 1═══════════22APR2024═══

XB’s PRNG is a linear congruential  generator (LCG) with the following recurrence equation:

xnew = (ax + c) mod m                                                                                                                          (1)

where

x = current seed, which is initialized at power-up to 335212624223

a = 14389820420821

c = 21132486540519

m = 1014

xnew = pseudorandom number (PRN) sought and the new seed for the next go-round.

The above converts the above PRNG to the following equation:

xnew = (14389820420821x + 21132486540519) mod 1014                                                           (2)

To preserve precision for the radix-100, floating point calculations, all of the above numbers were split into a least-significant half (LSH), with subscript 1, and a most-significant half (MSH), with subscript 2, such that

x = 107x2 + x1                                                                                                                                          (3)

a = 107a2 + a1                                                                                                                                         (4)

c = 107c2 + c1                                                                                                                                           (5)

which converts (1) to

xnew = ((107a2 + a1)(107x2 + x1) + 107c2 + c1) mod 1014

xnew = (1014a2x2 + 107a2x1 + 107a1x2 + a1x1 +107c2 + c1) mod 1014                                             (6)

At the very least, the first term drops out because

1014a2x2 mod 1014 = 0

simplifying (6) to

xnew = (107a2x1 + 107a1x2 + a1x1 +107c2 + c1) mod 1014                                                                 (7)

═══XB RND function explanation═══Part 2═══════════26APR2024═══

Before we go any further, the following is among the comments the TI programmers included ahead of the code for calculating (7), with the t variables being intermediate programming results discussed later:

Assumptions:

1. All numbers are integers. Fractional parts are truncated.
2. If the variables listed below start in the ranges specified, they will also end in the ranges specified.

Constants:

0 ≤ a2 < 5 106
0 ≤ a1 < 5
106
0 ≤ c2 < 107
0 ≤ c1 < 107

Variables:

0 ≤ x2 < 107
0 ≤ x1 < 107
0 ≤ t1 ≤ 1014
0 ≤ t2 < 107
0 ≤ t3 ≤ 1014
0 ≤ t4 < 107

We must remember that the recurrence equation (7) starts and ends with integers. Because the recurrence equation preserves only the remainder of division by 1014, we must preserve all of the LS digits, but we don’t care about any MS digits above 1014, because they do not contribute to the remainder. To further aid preserving precision during calculation, the terms of (7) were grouped by their contributions to the MSH of xnew (x2new) and the LSH of xnew (x1new). All terms can contribute to x2new, but only a1x1 and c1 contribute to x1new. To see why this is so, consider that

• Each factor of the terms in the parenthetical expression contributes a factor up to 107 or as many as 7 digits to their term,
• All terms with 107 as factor cannot contribute to x1new because that factor guarantees the LSH is all zeros, leaving only a1x1 and c1 with digits that can appear in the x1new.

FP operations that can result in more than 14 digits must be avoided or we will lose precision, i.e., digits will fall off the right side of the result. We must also avoid changing the radix-10 exponent of a 14-digit number from its even exponent to an odd one because this will shift the radix-100 number (centimal) one decimal digit right, resulting in a loss of precision from 14 digits to 13. The reason for this is that non-zero FP numbers are always stored in normalized centimal notation, i.e., the first centimal digit (equivalent to 2 decimal digits) in the mantissa must be 1..99. This  means that, for an odd number of decimal digits before the centimal point, the first byte has only one significant decimal digit.

This is what would happen with the shift in even to odd number of pre-centimal digits:  Integer 12345678911234 is 12.345678911234 1006 in normalized centimal notation and >460C >2238 >4E5B >0C22 in hexadecimal. The first byte is the sign bit and the 7-bit, excess-64 exponent. If we multiplied this by 10-7, the result would be 1234567.891123, with the normalized result as 1.234567891123 1003 and stored as >4301 >172D >4359 >0B17, costing us the least significant decimal digit. Of course, had we started with 0 as the rightmost digit, it would not have mattered, as you will see below.

If we define MSH(x) as a function meaning “MSH of x and similarly define LSH(x), where x is a 14-digit integer, we have

MSH(x) = int(10-7 x)                                                                                                                                                            (8)
LSH(x) = x - 10int(10-7 x)                                                                                                                                                 (9)

xnew = (107 LSH(a2x1 + a1x2 + c2 + MSH(a1x1 + c1))) + LSH(a1x1 + c1)) mod 1014             from (7),(8),(9)
\------------------x2new-------------------/       \----x1new----/

xnew = 107 x2new + x1new                                                                                                                   from (3)

To make unwieldy equations more tractable and to incorporate the same intermediate variables (shown above) used by the TI programmers,

t1 = a1x1 + c1
t2 = int(10-7 t1)
x1new = (t1 - 107 t2)
t3 = a2x1 + a1x2 + c2 + t2
t4 = int(10-7 t3)
x2new = t3 - 107 t4

and using the maximum values from the assumptions above,

t1 = a1x1 + c1 = 4999999(9999999) + 9999999 = 49999995000000
t2 = int(10-7 t1) = int(4999999.5000000) = 4999999
x1new = (t1 - 107 t2) = 49999995000000 - 49999990000000 = 5000000
t3 = a2x1 + a1x2 + c2 + t2 = 4999999(9999999) + 4999999(9999999) +9999999 + 4999999 = 99999985000000
t4 = int(10-7 t3) = int(9999998.5000000) = 9999998
x2new = t3 - 107 t4 = 99999985000000 - 99999980000000 = 5000000

which shows that at no time do the FP calculations exceed 14 digits, thus preserving precision, regardless of the values. Note that, even though there are a couple of places where a 14-digit number is multiplied by 10-7, which causes a loss of the last digit, that last digit is always 0.

═══GPL code for XB RND and RANDOMIZE functions════26APR2024═══

The spoiler has the GPL code for XB RND and RANDOMIZE along with the ALC of the console ROM code for the GPL RAND function. I have augmented the comments to make them clearer, hopefully. My additional comments include the stack contents on the right, indicated by S:.... The comments following <--NO!! YES-->, on four lines, are mine—whoever commented those lines before I got to it had it wrong.

You can probably figure out what is going on in RANDOMIZE without my explanation, but I plan to post my explanation in a few days. First, I want to convert the GPL code for RND and RANDOMIZE into ALC, which will likely be easier to understand for those of you unfamiliar with GPL.

Spoiler
```*******************************************************************
* ...at Extended Basic startup                                    *
* Initialize Seed for Pseudorandom Number Generator               *
*******************************************************************
*
INTRND MOVE 10,G@X2SEED,V@RNDX2  * copy from GROM to VRAM
RTN
X2SEED BYTE >42,>03,>23,>15,>00  * =   33521, X2 initial value
X1SEED BYTE >43,>02,>3E,>2A,>17  * = 2624223, X1 initial value

*******************************************************************
* Pseudorandom Number Generator                                   *
*******************************************************************
*                                                                 *
*     X[N+1] = (A*X[N]+C) MOD M;  RND = X/M                       *
*                                                                 *
*     where:  X = X2 * 1E7 + X1                                   *
*             A = A2 * 1E7 + A1                                   *
*             C = C2 * 1E7 + C1                                   *
*             M = 1E14                                            *
*                                                                 *
* Assumptions:                                                    *
*  (1) All numbers are integers; fractional parts are             *
*      truncated                                                  *
*  (2) If the variables listed below start in the ranges          *
*      specified. They will also end in the ranges specified      *
*                                                                 *
* Constants: 0 <= A2 <  5E6  ; 0 <= A1 < 5E6                      *
*            0 <= C2 <  1E7  ; 0 <= C1 < 1E7                      *
* Variables: 0 <= X2 <  1E7  ; 0 <= X1 < 1E7                      *
*            0 <= T1 <= 1E14 ; 0 <= T2 < 1E7                      *
*            0 <= T3 <= 1E14 ; 0 <= T4 < 1E7                      *
*                                                                 *
* Stack usage:                                                    *
*            Constant Refs      Constant Refs   Constant Refs     *
* +---------+      In/Out            In/Out          In/Out       *
* | STACK+4 | X2*A1(F)(H)       --    ----      --    ----        *
* +---------+                                                     *
* | STACK+3 |   T2 (C)(J)       --    ----      --    ----        *
* +---------+                                                     *
* | STACK+2 |   T1 (B)(D)      X1new (E)(N)     --    ----        *
* +---------+                                                     *
* | STACK+1 |old X1(A)(G)       T3   (K)(L)    X2new (M)(P)       *
* +---------+                                                     *
*******************************************************************
* Compute new value for X1..save it in V@RNDX1
*
*                          Stack usage....................................
*                                 FAC/ARG                Stack:  {} shows
*                          Srefs  Contents               hidden stack items
*                          -----  -----------------      ------------------
NRND   MOVE 5,V@RNDX1,@FAC        FAC = X1
CLR  @FAC5                FAC5 = >00
DCLR @FAC6          FAC6..FAC7 = >0000
XML  VPUSH          (A)    FAC = X1                 S:x1
MOVE 8,G@RNDA1,@ARG        ARG = A1
XML  FMUL                  FAC = X1*A1
MOVE 8,G@RNDC1,@ARG        ARG = C1
XML  VPUSH          (B)    FAC = T1                 S:x1 t1
MOVE 8,G@RNDEM,@ARG        ARG = 1/1E7
XML  FMUL                  FAC = T1/1E7
CALL GRINT              T2=FAC = INT(T1/1E7)
XML  VPUSH          (C)    FAC = T2                 S:x1 t1 t2
MOVE 8,G@RNDEP,@ARG        ARG = 1E7
XML  FMUL                  FAC = T2*1E7             S:x1 t1 t2
DSUB 8,@VSPTR                                       S:x1 t1 {t2}
XML  SSUB        (D) X1new=FAC = T1-T2*1E7          S:x1 {t1 t2}
MOVE 5,@FAC,V@RNDX1        FAC = X1new
XML  VPUSH          (E)    FAC = X1new              S:x1 x1new {t2}
*
* Compute new value for X2, save it in V@RNDX2
*
MOVE 5,V@RNDX2,@FAC        FAC = X2
CLR  @FAC5                FAC5 = >00
DCLR @FAC6          FAC6..FAC7 = >0000
MOVE 8,G@RNDA1,@ARG        ARG = A1
XML  FMUL                  FAC = X2*A1
XML  VPUSH          (F)    FAC = X2*A1            S:x1 x1new t2 x2*a1
DSUB 24,@VSPTR                                    S:x1 {x1new t2 x2*a1}
XML  VPOP           (G)    FAC = X1               S:{x1 x1new t2 x2*a1}
DADD 32,@VSPTR                                    S:x1 x1new t2 x2*a1
MOVE 8,G@RNDA2,@ARG        ARG = A2
XML  FMUL                  FAC = X1*A2
XML  SADD           (H)    FAC = X2*A1+X1*A2        S:x1 x1new t2
MOVE 8,G@RNDC2,@ARG        ARG = C2
XML  SADD           (J) T3=FAC = X2*A1+X1*A2+C2+T2  S:x1 x1new
DSUB 16,@VSPTR                                      S:{x1 x1new}
XML  VPUSH          (K)    FAC = T3                 S:t3 {x1new}
MOVE 8,G@RNDEM,@ARG        ARG = 1/1E7
XML  FMUL                  FAC = T3/1E7
CALL GRINT              T4=FAC = INT(T3/1E7)
MOVE 8,G@RNDEP,@ARG        ARG = 1E7
XML  FMUL                  FAC = T4*1E7
XML  SSUB        (L) X2new=FAC = T3-T4*1E7          S:t3 {x1new}
MOVE 5,@FAC,V@RNDX2        FAC = X2new
*
* Compute new value for RND, leave it in FAC for return to caller
*     0 <= RND < 1
*
MOVE 8,G@RNDEM,@ARG        ARG = 1/1E7
XML  FMUL                  FAC = X2new/1E7
XML  VPUSH          (M)    FAC = X2new/1E7          S:x2new/1e7 {x1new}
XML  VPOP           (N)    FAC = X1new              S:x2new/1e7
XML  FMUL                  FAC = X1new/1E7
XML  FMUL                  FAC = X1new/1E14
XML  CONT   * Return

***********************************************************
* Constants in GROM for the Random Number Routine
RNDA2  BYTE >43,>01,>2B,>59,>52,>00,>00,>00 * = 1438982
RNDA1  BYTE >42,>2A,>08,>15,>00,>00,>00,>00 * = 0420821
RNDC2  BYTE >43,>02,>0B,>20,>30,>00,>00,>00 * = 2113248
RNDC1  BYTE >43,>06,>36,>05,>13,>00,>00,>00 * = 6540519
RNDEP  BYTE >43,>0A,>00,>00,>00,>00,>00,>00 * = 1E7
RNDEM  BYTE >3C,>0A,>00,>00,>00,>00,>00,>00 * = 1/1E7
***********************************************************

***********************************************************
*                   RANDOMIZE Statement
***********************************************************
NRNDMZ CALL CHKEND            Seed provided?
BS   RNDM1             No
* RANDOMIZE given a seed value
* (99,000,000,000,001 possible starting positions)
* (Place-value is ignored in the input number)
XML  PARSE             Parse the seed
BYTE TREMZ           * Up to end of statement
CALL CKSTNM
DCZ  @FAC              Check FAC for zero
BS   GA3B6
ST   >46,@FAC          0 < FAC < 1E14
XML  VPUSH             Let FAC = X2*1E7+X1
MOVE 8,G@RNDEM,@ARG        ARG = 1/1E7
XML  FMUL                  FAC = X2+X1/1E7
CALL GRINT                 FAC = X2
MOVE 5,@FAC,V@RNDX2        FAC = X2
MOVE 8,G@RNDEP,@ARG        ARG = 1E7
XML  FMUL                  FAC = X2*1E7
XML  SSUB                  FAC = X1
MOVE 5,@FAC,V@RNDX1        FAC = X1
XML  CONT   * Return       FAC = X1

GA3B6  DST  @FAC,V@RNDX2          FAC = 0
DST  @FAC,V@RNDX1          FAC = 0
XML  CONT   * Return

**************************************************************************
* RANDOMIZE given number seed value (uses GPL RAND function)
* (16K possible starting positions)
*
RANDOM EQU  >8378

RNDM1  DST  >4201,@FAC     FAC = >4201 (sign+exponent+1st digit fixed at 1)
CLR  @FAC4          FAC4= >00
CALL RNDMZ          Set up new seed..
DATA RNDX1          ..for X1
CALL RNDMZ          Set up new seed..
DATA RNDX2          ..for X2
XML  CONT           * Return X: 100000010000 <= X <= 124240012424
* '*'...fixed, '?'...variable:         *????***????         *????***????
* Get next 4 pseudorandom digits
*
RNDMZ  FETCH @FAC8         Fetch address of seed (MSB)
FETCH @FAC9         Fetch address of seed (LSB)
RAND 99             GPL Randomize returns 0<=RANDOM<=99
ST   @RANDOM,@FAC2  >00<=FAC+2<=FF <--NO!! YES--> 0<=FAC+2<=99[63h]
SRL  2,@FAC2        >00<=FAC+2<=3F <--NO!! YES--> 0<=FAC+2<=24[18h]
RAND 99             GPL Randomize returns 0<=RANDOM<=99
ST   @RANDOM,@FAC3  >00<=FAC+3<=FF <--NO!! YES--> 0<=FAC+3<=99[63h]
SRL  2,@FAC3        >00<=FAC+3<=3F <--NO!! YES--> 0<=FAC+3<=24[18h]
MOVE 5,@FAC,V*FAC8  Put in seed..max of >4201181800 = 12424

CKSTNM CEQ  >65,@FAC2      Error exit
BS   ERRSNM

*--ALC for GPL RAND from Console ROM 0---------------------------------*
*----------------------------------------------------------------------*
*--- Generate A Pseudorandom Number -----------------------------------*
*----------------------------------------------------------------------*
WKSC   EQU  >83C0             ISR workspace (R0 = PRN SEED)
WKSE   EQU  >83E0             GPL workspace
R5LSB  EQU  WKSE+R5+R5+1      low byte of R5 of GPL WS
RANDOM EQU  >8378             return location for RAND's PRN return byte

RAND   LI   R4,>6FE5          28645 to R4
MPY  @WKSC,R4          SEED * 28645
AI   R5,>7AB9          PRN = SEED * 28645 + 31417
MOV  R5,@WKSC          PRN is stored as new SEED in >83C0
MOVB *R13,R6           load limit number (byte) from caller to R6
SRL  R6,8              shift to LSB
INC  R6                make modulus (lim+1)
CLR  R4                clear upper half of dividend
SWPB R5                swap bytes of PRN to get PRN'
DIV  R6,R4             perform division by modulus (lim+1)
MOVB @R5LSB,@RANDOM    store LSB of remainder in >8378

═══ALC for XB RND and RANDOMIZE functions═══════06MAY2024═══

The spoiler below has my ALC (Assembly Language Code) translation of the GPL code (spoiler above) for XBs RND and RANDOMIZE functions. RND is 3.3 time faster than XBs RND.

Spoiler
```*******************************************************************
* PRNG_FBF exposes 5 functions as ALC equivalents for XB's        *
*     GPL code:                                                   *
* Opcode Fxn     XB fxn  Description of XB function               *
* ------ ------  ------- ---------------------------------------- *
*    0   NRND....NRND....GPL code called by RND                   *
*    1   RNZNEW..NRNDMZ..GPL code called by RANDOMIZE <user seed> *
*    2   RNZOLD..NRNDMZ..GPL code called by RANDOMIZE             *
*    3   SEEDIN..Initialize FP seed (done at XB startup           *
*    4   RAND99..RAND...Console ROM ALC for GPL RAND <max>...     *
*                    ...returns pseudorandom, 1-byte value in...  *
*                    ...MSB of R0                                 *
*******************************************************************

*******************************************************************
* Necessary EQUates
*
FPSTK  EQU  R13      FP stack pointer
RSTK   EQU  R15      return stack pointer
FPSTK0 EQU  >8378    bottom of FP stack in scratchpad RAM (grows up)
RSTK0  EQU  >83BE    bottom of return stack in scratchpad RAM (grows down)

FSUB   EQU  >0D7C    console ROM FP SUBtraction subroutine
FMUL   EQU  >0E88    console ROM FP MULtiplication subroutine
FAC    EQU  >834A
ARG    EQU  >835C
GPLWS  EQU  >83E0

* RNDX1, RNDX2, SAVWP are at top of low-level support RAM for fbForth,
* just above system PPBs. If using for some other system, these can
* be anywhere in modifiable RAM.
RNDX1  EQU  >3C1A    PRNG seed (lower half)
RNDX2  EQU  >3C22    PRNG seed (upper half)
PRNGWS EQU  >3C2A    PRNG workspace (only using R13,R14,R15)

AORG >A032    HERE at fbForth 3.0 startup..
*++                  ..put wherever is convenient

******************************************************************
*++ PRNG: Vector for BLWP to the FP PRNG functions.
*++   Input...R0: opcode
*++
*++   Called with
*++      BLWP @PRNG
*
PRNG   DATA PRNGWS,PRNGEN
*
*++ Entry point (PRNGEN) and exit (PRNGEX) are farther below to
*++ facilitate JuMPs instead of Branches.
*++
******************************************************************
* Subroutine call table:
*
PRNGTB DATA NRND,RNZNEW,RNZOLD,SEEDIN,RAND99
*++ opcode:  0     1      2      3      4
*++
*******************************************************************
* SPUSH pushes 8 bytes, usually a Floating Point Number (FPN),    *
* from FAC to Scratchpad stack, pointed to by FPSTK. There is only*
* room for 4 FPNs. The user must check for overflow/underflow.    *
*******************************************************************
*     FPSTK: FP stack pointer
*
SPUSH  DECT RSTK           reserve return stack space
MOV  R11,*RSTK      save return
AI   FPSTK,8        reserve FP stack space
LI   R0,FAC         source (FAC)
MOV  FPSTK,R1       destination
BL   @FPMOVE        copy FAC to top of stack
MOV  *RSTK+,R11     pop return

*******************************************************************
* SPOP pops 8 bytes, usually an FPN, from Scratchpad stack,       *
* pointed to by FPSTK, to FAC. There is only room for 4 FPNs. The *
* user must check for overflow/underflow.                         *
*******************************************************************
*     FPSTK: FP stack pointer
*
SPOP   DECT RSTK           reserve return stack space
MOV  R11,*RSTK      save return
MOV  FPSTK,R0       source
LI   R1,FAC         destination
BL   @FPMOVE        copy top of stack to FAC
AI   FPSTK,-8       pop FP stack
MOV  *RSTK+,R11     pop return

*******************************************************************
* SSUB first negates FPN in FAC, then proceeds with SADD. SADD    *
* pops FP value from FP stack to ARG and runs FADD to add FAC to  *
* ARG. Result is in FAC: ARG - FAC if SSUB or ARG + FAC if SADD.  *
*******************************************************************
*
SSUB   NEG  @FAC           negate FP number in FAC
SADD   DECT RSTK           reserve return stack space
MOV  R11,*RSTK      save return
MOV  FPSTK,R0       source
LI   R1,ARG         destination
BL   @FPMOVE        copy top of stack to ARG
AI   FPSTK,-8       pop FP stack
MOV  *RSTK+,R11     pop return

*******************************************************************
* INTPOS truncates a positive FPN in FAC. Zero or negative FPNs   *
* remain unchanged.                                               *
*     Exponent Fraction Bytes                                     *
*     -------- --------------                                     *
*     > 46h       0                                               *
*     = 46h       0                                               *
*     < 46h       1..7                                            *
*******************************************************************
*     uses R0,R1
*
INTPOS MOVB @FAC,R0        get sign+exp byte..negative?
JLT  INTPSX         yes..we're outta here
SRL  R0,8           to LSB, clearing MSB
LI   R1,>0046       load exp comparison value
S    R0,R1          fraction part?                R1=46h-exp
JGT  INTPS1         yes..zero it
INTPS1 CI   R1,6           integer part?
JGT  INTPS3         no..go zero number
LI   R0,FAC+7       start at last byte
INTPS2 MOVB R1,*R0         zero next byte (R1 MSB = 0)
DEC  R0             next byte left
DEC  R1             done?
JNE  INTPS2         no..zero another byte
INTPS3 CLR  @FAC           return 0..no integer part

*******************************************************************
* FPMOVE copies 8 bytes from source in R0 to destination in R1.   *
* FRMFAC copies 8 bytes from FAC to destination passed via R11,   *
*        bumping R11 once.                                        *
* TOARG  copies 8 bytes from source passed via R11 to ARG,        *
*        bumping R11 once.                                        *
* TOFAC  copies 8 bytes from source passed via R11 to FAC,        *
*        bumping R11 once.                                        *
*******************************************************************
*
FRMFAC LI   R0,FAC      FAC is source
JMP  FPMV1       do it
TOARG  LI   R1,ARG      ARG is destination
JMP  FPMOVE      do it
TOFAC  LI   R1,FAC      FAC is destination
TOFAC1 MOV  *R11+,R0    get source address
JMP  FPMOVE      do it
FPMV1  MOV  *R11+,R1    get destination address
FPMOVE MOV  *R0+,*R1+   copy bytes 0,1
MOV  *R0+,*R1+   copy bytes 2,3
MOV  *R0+,*R1+   copy bytes 4,5
MOV  *R0,*R1     copy bytes 6,7

*************************************************************************
* Pseudorandom Number Generator: NRAND                                  *
*************************************************************************
*                                                                       *
*     X[n+1] = (A*X[n]+C) mod M;  RND = X/M                             *
*     X[n+1] = (14389820420821 * X[n] + 21132486540519) mod 1E14        *
*                                                                       *
*     where:  X = X2 * 1E7 + X1                                         *
*             A = A2 * 1E7 + A1                                         *
*             C = C2 * 1E7 + C1                                         *
*             M = 1E14                                                  *
* Initially:                                                            *
*     X[n+1] = (14389820420821(335212624223) + 21132486540519) mod 1E14 *
*                                                                       *
* Assumptions:                                                          *
*  (1) All numbers are integers; fractional parts are                   *
*      truncated                                                        *
*  (2) If the variables listed below start in the ranges                *
*      specified. They will also end in the ranges specified            *
*                                                                       *
* Constants: 0 <= A2 <  5E6  ; 0 <= A1 < 5E6                            *
*            0 <= C2 <  1E7  ; 0 <= C1 < 1E7                            *
* Variables: 0 <= X2 <  1E7  ; 0 <= X1 < 1E7                            *
*            0 <= T1 <= 1E14 ; 0 <= T2 < 1E7                            *
*            0 <= T3 <= 1E14 ; 0 <= T4 < 1E7                            *
*                                                                       *
* Stack usage:                                                          *
*            Constant Refs      Constant Refs   Constant Refs           *
* +---------+      In/Out            In/Out          In/Out             *
* | STACK+4 | X2*A1(F)(H)       --    ----      --    ----              *
* +---------+                                                           *
* | STACK+3 |   T2 (C)(J)       --    ----      --    ----              *
* +---------+                                                           *
* | STACK+2 |   T1 (B)(D)      X1new (E)(N)     --    ----              *
* +---------+                                                           *
* | STACK+1 |old X1(A)(G)       T3   (K)(L)    X2new (M)(P)             *
* +---------+                                                           *
*************************************************************************

*=======================================================================*
*==~~~~~~~~~~~~~~~~~~~~~~~~~ NRND Opcode = 0 ~~~~~~~~~~~~~~~~~~~~~~~~~==*
*=======================================================================*
*== NRND is functionally equivalent to XB's GPL NRND. It is called    ==*
*== from GPL workspace(>83E0) by PRNG. It leaves the FP result in FAC.==*
*=======================================================================*
*
* Compute new value for X1..save it in @RNDX1
*
*                          Stack usage....................................
*                                 FAC/ARG
*                          Srefs  Contents               Stack
*                          -----  -----------------      -----------------
NRND   BL   @TOFAC                FAC = X1
DATA RNDX1
BL   @SPUSH         (A)    FAC = X1                 S:x1
BL   @TOARG                ARG = A1
DATA RNDA1
BL   @FMUL                 FAC = X1*A1
BL   @TOARG                ARG = C1
DATA RNDC1
BL   @SPUSH         (B)    FAC = T1                 S:x1 t1
BL   @TOARG                ARG = 1/1E7
DATA RNDEM
BL   @FMUL                 FAC = T1/1E7
BL   @INTPOS            T2=FAC = INT(T1/1E7)
BL   @SPUSH         (C)    FAC = T2                 S:x1 t1 t2
BL   @TOARG                ARG = 1E7
DATA RNDEP
BL   @FMUL                 FAC = T2*1E7             S:x1 t1 t2
AI   FPSTK,-8    hide T2 so T1 TOS for SSUB         S:x1 t1 {t2}
BL   @SSUB       (D) X1new=FAC = T1-T2*1E7          S:x1 {t1 t2}
BL   @FRMFAC               FAC = X1new
DATA RNDX1
BL   @SPUSH          (E)   FAC = X1new              S:x1 x1new {t2}
*
* Compute new value for X2, save it in @RNDX2
*
BL   @TOFAC                FAC = X2
DATA RNDX2
CLR  @FAC+6         FAC6..FAC7 = >0000
MOVB @FAC+6,@FAC+5        FAC5 = >00
BL   @TOARG                ARG = A1
DATA RNDA1
BL   @FMUL                 FAC = X2*A1
AI   FPSTK,8     expose T2 for later              S:x1 x1new t2
BL   @SPUSH         (F)    FAC = X2*A1            S:x1 x1new t2 x2*a1
AI   FPSTK,-24   hide top 3 FPNs on stack         S:x1 {x1new t2 x2*a1}
BL   @SPOP          (G)    FAC = X1               S:{x1 x1new t2 x2*a1}
AI   FPSTK,32    expose all four FPNs on stack    S:x1 x1new t2 x2*a1
BL   @TOARG                ARG = A2
DATA RNDA2
BL   @FMUL                 FAC = X1*A2
BL   @SADD           (H)   FAC = X2*A1+X1*A2        S:x1 x1new t2
BL   @TOARG                ARG = C2
DATA RNDC2
BL   @SADD          (J) T3=FAC = X2*A1+X1*A2+C2+T2  S:x1 x1new
AI   FPSTK,-16   hide FPNs on stack                 S:{x1 x1new}
BL   @SPUSH         (K)    FAC = T3                 S:t3 {x1new}
BL   @TOARG                ARG = 1/1E7
DATA RNDEM
BL   @FMUL                 FAC = T3/1E7
BL   @INTPOS            T4=FAC = INT(T3/1E7)
BL   @TOARG                ARG = 1E7
DATA RNDEP
BL   @FMUL                 FAC = T4*1E7
BL   @SSUB          (L) X2new=FAC = T3-T4*1E7       S:{x1new}
BL   @FRMFAC               FAC = X2new
DATA RNDX2
*
* Compute new value for RND, leave it in FAC for return to caller
*     0 <= RND < 1
*
BL   @TOARG                ARG = 1/1E7
DATA RNDEM
BL   @FMUL                 FAC = X2new/1E7
BL   @SPUSH         (M)    FAC = X2new/1E7          S:x2new/1e7 {x1new}
AI   FPSTK,8     expose X1new on stack              S:x2new/1e7 x1new
BL   @SPOP          (N)    FAC = X1new              S:x2new/1e7
BL   @FMUL                 FAC = X1new/1E7
BL   @FMUL                 FAC = X1new/1E14

***********************************************************
* Constants for the Random Number Routine
RNDA2  DATA >4301,>2B59,>5200,>0000    ; = 1438982
RNDA1  DATA >422A,>0815,>0000,>0000    ; = 0420821
RNDC2  DATA >4302,>0B20,>3000,>0000    ; = 2113248
RNDC1  DATA >4306,>3605,>1300,>0000    ; = 6540519
RNDEP  DATA >430A,>0000,>0000,>0000    ; = 1E7
RNDEM  DATA >3C0A,>0000,>0000,>0000    ; = 1/1E7
X2SEED DATA >4203,>2315,>0000,>0000    ; =   33521, X2 initial value
X1SEED DATA >4302,>3E2A,>1700,>0000    ; = 2624223, X1 initial value
***********************************************************

******************************************************************
* PRNGEN is the entry point for the 5 PRNG routines,
*     NRND: opcode = 0
*   RNZNEW: opcode = 1 and user's seed in FAC
*   RNXOLD: opcode = 2
*   SEEDIN: opcode = 3 initializes FP seed to XB startup value
*   RAND99: opcode = 4 calls RAND with "DATA 99" internally
*
PRNGEN LIMI 0                 disable interrupts
MOV  *R13,@GPLWS+2     save opcode from caller's R0 to GPLWS R1
LWPI GPLWS             switch to GPL workspace
MOV  R1,R1             negative?
CI   R1,4              no..too high?
LI   FPSTK,FPSTK0      initialize FP stack (grows up)
LI   RSTK,RSTK0        initialize return stack (grows down)
SLA  R1,1              make opcode an index
MOV  @PRNGTB(R1),R1    get branch address of opcode
B    *R1               do it

*=======================================================================*
*==~~~~~~~~~~~~~~~~~~~~~~~~ SEEDIN Opcode = 3 ~~~~~~~~~~~~~~~~~~~~~~~~==*
*=======================================================================*
*== SEEDIN is functionally equivalent to XB's GPL INTRND. It is       ==*
*== called from GPL workspace(>83E0) by PRNG. It initializes the FP   ==*
*== PRNG seed by copying X1SEED and X2SEED to RNDX1 and RNDX2. SEEDIN ==*
*== should be run at system startup.                                  ==*
*=======================================================================*
*
BL   @FPMOVE        copy startup seed X2
BL   @FPMOVE        copy startup seed X1
* Fall through to PRNGEX to clean up and return to caller...

*******************************************************************
*
PRNGEX LI   FPSTK,>9800       restore R13
LI   RSTK,>8C02        restore R15
LWPI PRNGWS
RTWP

*************************************************************************
*                   RANDOMIZE Statement (2 subroutines)
*************************************************************************
*
*=======================================================================*
*==~~~~~~~~~~~~~~~~~~~~~~~~ RNZNEW Opcode = 1 ~~~~~~~~~~~~~~~~~~~~~~~~==*
*=======================================================================*
*== RNZNEW is functionally equivalent to XB's GPL NRNDMZ when called  ==*
*== from XB with "RANDOMIZE <seed>". RNZNEW is called from GPL work-  ==*
*== space (>83E0) by PRNG and expects the new seed in FAC. XB did not ==*
*== check for negative numbers, but RNZNEW forces them positive.      ==*
*== Place value is ignored in the input number by overstoring the     ==*
*== sign+exponent byte with >46.                                      ==*
*== TI comment: 99,000,000,000,001 possible starting positions.       ==*
*== [LES note: Actually, 100^7 = 100,000,000,000,000 are possible,    ==*
*== but who's counting?!]                                             ==*
*=======================================================================*
*
RNZNEW MOV  @FAC,R0           FAC = 0?
JEQ  GA3B6             yes..we're done
JGT  RNZNW1            > 0..get on with it
NEG  @FAC              first make it positive
RNZNW1 LI   R0,>4600          exponent for integer only
MOVB R0,@FAC           0 < FAC < 1E14
BL   @SPUSH            Let FAC = X2*1E7+X1           S:x2*1e7+x1
BL   @TOARG                ARG = 1/1E7
DATA RNDEM
BL   @FMUL                 FAC = X2+X1/1E7
BL   @INTPOS               FAC = X2
BL   @FRMFAC               FAC = X2
DATA RNDX2
BL   @TOARG                ARG = 1E7
DATA RNDEP
BL   @FMUL                 FAC = X2*1E7              S:x2*1e7+x1
BL   @SSUB              X1=FAC = X2*1E7+X1-X2*1E7    S:
BL   @FRMFAC               FAC = X1
DATA RNDX1

GA3B6  MOV  @FAC,@RNDX2           FAC = 0
MOV  @FAC,@RNDX1           FAC = 0

*=======================================================================*
*==~~~~~~~~~~~~~~~~~~~~~~~~ RNZOLD Opcode = 2 ~~~~~~~~~~~~~~~~~~~~~~~~==*
*=======================================================================*
*== RNZOLD is functionally equivalent to XB's GPL NRNDMZ when called  ==*
*== from XB with RANDOMIZE. RNZOLD is called from GPL workspace       ==*
*== (>83E0) by PRNG. RNZOLD will calculate a new seed from the old    ==*
*== 16-bit seed at >83C0 via 4 calls to RAND with "DATA 99". >83C0 is ==*
*== actually different for every program start. RNZOLD calls RAND     ==*
*== with "DATA 99" twice each for RNDX1 and RNDX2.                    ==*
*== TI comment-->(16K possible starting positions)<--NOPE             ==*
*== TI probably meant "16M" because 64^4 = 16MiB, however, that is    ==*
*== also wrong:                                                       ==*
*==   ACTUALLY-->(25^4 = 390,625 possible positions)<--ACTUALLY       ==*
*== If TI had not added the "SRL R0,2" for each call to "RAND 99", it ==*
*== would have been 100^4 = 100,000,000.                              ==*
*=======================================================================*
*
RANDOM EQU  >8378

RNZOLD LI   R0,>4201
MOV  R0,@FAC     FAC = >4201 (sign+exponent+1st digit fixed at 1)
CLR  @FAC+4      FAC+4..
CLR  @FAC+6      ..FAC+7 = >00000000
BL   @RNDMZ      Set up new seed..
DATA RNDX1       ..for X1
BL   @RNDMZ      Set up new seed..
DATA RNDX2       ..for X2
*                       ..Xnew = 100000010000..124240012424

* Get next 2 pseudorandom centimal digits
*
RNDMZ  DECT RSTK        reserve return stack space
MOV  *R11+,R1    Get address of seed-half destination for FPMOVE
MOV  R11,*RSTK   save new return
BL   @RAND       perform GPL RAND equivalent..
DATA 99          ..to return 1 centimal digit (2 decimal)
* XB comments say >00<=R0<=FF <--WRONG!! ACTUALLY--> 0<=R0<=99[63h]
SRL  R0,2        [LES Note: To be removed in future use]
* XB comments say >00<=R0<=3F <--WRONG!! ACTUALLY--> 0<=R0<=24[18h]
* [LES Note: The SRLs above and below, limiting the returned centimal
*   digit to 24, were added by someone who apparently did not under-
*   stand what "RAND 99" was actually doing! The XB comments imply
*   the TI programmers thought 99 was a seed for RAND, rather than
*   the limit for the returned byte that it is!]
MOVB R0,@FAC+2   0<=FAC+2<=24[18h]!!
BL   @RAND       perform GPL RAND equivalent..
DATA 99          ..to return 1 centimal digit (2 decimal)
* XB comments say >00<=R0<=FF <--WRONG!! ACTUALLY--> 0<=R0<=99[63h]
SRL  R0,2        [LES Note: To be removed in future use]
* XB comments say >00<=R0<=3F <--WRONG!! ACTUALLY--> 0<=R0<=24[18h]
MOVB R0,@FAC+3   0<=FAC+3<=24[18h]!!
LI   R0,FAC      source
BL   @FPMOVE     Put in seed half..max: >4201181800 = 12424
MOV  *RSTK+,R11  pop return

*-----------------------------------------------------------------
*-- RAND is functionally equivalent to Console ROM 0's ALC for GPL
*-- RAND, except that input is via *R11+ instead of *R13 and output
*-- is MSB of R0, rather than the byte at RANDOM (>8378).
*-----------------------------------------------------------------
*------ Generate A Pseudorandom, Centimal Number in R0 MSB -------
*-----------------------------------------------------------------
WKSC   EQU  >83C0             ISR workspace (R0 = PRN SEED)
WKSE   EQU  >83E0             GPL workspace
R5LSB  EQU  WKSE+R5+R5+1      low byte of R5 of GPL WS

RAND   LI   R4,>6FE5          28645 to R4
MPY  @WKSC,R4          SEED * 28645
AI   R5,>7AB9          PRN = SEED * 28645 + 31417
MOV  R5,@WKSC          PRN is stored as new SEED in >83C0
MOV  *R11+,R6          get maximum byte-value to be returned
SB   R6,R6             clear MSB, insuring R6 < 256
INC  R6                make it a modulus (max+1)
CLR  R4                clear upper half of dividend
SWPB R5                swap bytes of PRN to get PRN'
DIV  R6,R4             perform division by modulus (lim+1)
MOVB @R5LSB,R0         LSB of remainder to MSB of R0
*++
*++ In the future, the SWPB may be removed in favor of adding
*++ "SRC R5,5" just before storing in WKSC per TI Forth.

RT      Return random centimal digit to caller

*=======================================================================*
*==~~~~~~~~~~~~~~~~~~~~~~~~ RAND99 Opcode = 4 ~~~~~~~~~~~~~~~~~~~~~~~~==*
*=======================================================================*
*== RAND99 is called from GPL workspace (>83E0) by PRNG to run RAND   ==*
*== with "DATA 99" to return a random centimal digit in FAC.          ==*
*=======================================================================*
*
RAND99 BL   @RAND       perform GPL RAND equivalent..
DATA 99          ..to return 1 centimal digit (2 decimal)
SRL  R0,8        MSB to LSB, zeroing MSB
MOV  R0,@FAC     return value in FAC

END```

I wrote it as a library of five opcode-callable functions with the following call sequence:

```       LI   R0,<opcode>
BLWP @PRNG```

Feel free to use this code however you like. You may need to change the AORG and some EQUates. The SEEDIN function should be run at least once to initialize the starting seed (XB does this at startup):

```       LI   R0,3   <--SEEDIN opcode
BLWP @PRNG```

XBs RANDOMIZE is actually two distinct functions chosen by whether the programmer includes a seed. The above ALC has them as:

• RMZNEW, for a seed passed in FAC (>834A)
• RMZOLD, for computing the next FP seed from the console ISRs 16-bit seed at >83C0

═══XB RANDOMIZE <seed> function explanation═════════════09MAY2024═══

In XB, using “RANDOMIZE <seed>” sets your own FP seed to start a PRN sequence for RND. In the ALC version,  RNZNEW, this is accomplished by first passing the new FP seed in FAC (8 bytes). If the value passed (FPN) is 0, x2 and x1 (upper and lower halves of the seed) are both set to 0. If the FPN is negative, it is first negated to make it positive before further processing. The seed, x, is composed of its two halves as in equation (3) above. The following steps are then performed:

1. Overstore sign+exponent byte with >46, forcing it to an integer with, at most, 7 centimal (14 decimal) digits
2. Multiply by 10-7
3. Remove any fractional partx2 = int(10-7x)         <==correction
4. Save x2
5. Multiply by 107
6. Subtract from original number to get x1 = x - 107x2
7. Save x1

═══XB RANDOMIZE function explanation═══════════════════09MAY2024═══

In XB, using RANDOMIZE, with no user FP seed supplied, runs the equivalent of the ALC version’s RNZOLD, which will calculate a new seed from the old 16-bit seed at >83C0 via 4 calls to ALC RAND with DATA 99 (same as XB’s GPL call to RAND 99). >83C0 is actually different for every program start in XB. RNZOLD calls RAND with DATA 99 twice each for x1 and x2. TI programmers commented that this provides “16K possible starting positions”. TI probably meant “16M” because their assumed 0..63 possible values for each of the four randomized bytes would provide 644 = 16MiB possibilities. However, that is also wrong because their manipulations actually result in values of 0..24 for 254 = 390,625 possible numbers because they added SRL R0,2 for each call to RAND 99. Without those 2-bit right shifts, it would have been values of 0..99 for 1004 = 100,000,000 possible numbers.

It is fairly obvious from their program comments how the TI programmers came to their erroneous conclusion that the 2-bit right shifts were necessary. First off, they somehow thought the GPL call to RAND 99 was passing 99 as a seed to RAND. But that is wrong—it is passing 99 as a maximum for the pseudorandom digit RAND is to return after using and changing the value at >83C0 as its seed. Due to their misconception, they thought RAND always returned a pseudorandom byte (0..255) as evidenced from the comment “>00 <= FAC+2 <= FF”. Rather than perform an expensive DIV, they opted to shift right as many bits as necessary to bring the returned byte into legal-centimal-digit range. One bit is not enough (127) and 2 bits is too much (63), but within range. The obvious problem with this is that we know that RAND 99 already returns a legal centimal digit, which means their first comment should have been, “>00 <= FAC+2 <= >63”. At that point, they would have realized that the returned value needed no further processing. However, we cannot change the XB cartridge, so, using FAC to hold the working number, here is what RANDOMIZE does when no user seed is supplied:

1. Force an integer with 3 centimal (6 decimal) digits followed by zeros:      >4201 >hhhh >0000 >0000     01dddd.00000000   (‘h’ is hex and ‘d’ is decimal)
2. Fill >hhhh with 2 calls to RAND 99”:     019999.00000000   (max value)
3. Right shift each >hh by 2 bits:                  012424.00000000   (max value)
4. Save as x1
5. Repeat (1)..(4) above for x2

...lee

• 6
• 3
##### Share on other sites

Are you going to compare the difference in randomizer math between like TI Basic and XB and other Basic's like Myarc, etc.

##### Share on other sites

3 hours ago, Gary from OPA said:

Are you going to compare the difference in randomizer math between like TI Basic and XB and other Basic's like Myarc, etc.

More or less—I have in mind to make some sort of comparison with TI Basic’s PRNG, which is discussed here, and TI Forth and fbForths PRNGs. As to the Geneve ABasics PRNG, it is virtually identical to XB, with GPL code converted to ALC. I understand per @InsaneMultitasker that Myarc Basic II was the source for Abasic, in which case the PRNG is certainly the same. I would need that source code to be sure. That is, currently, the extent of my comparison plans.

...lee

• 2
• 1
##### Share on other sites

It would be good to also include UCSD Pascal in the comparison. Not sure what type of randomizer function it uses. I can help with the test code.

##### Share on other sites

11 hours ago, Lee Stewart said:

It is my intention, here, to explain the details of the pseudorandom number generator (PRNG) in TI Extended Basic (XB). Over the next few days, I will expand this first post to include my analysis to date in an effort to make it as nearly complete as I am able. I will also make any updates relevant to further insights from the ensuing discussion:

XB’s PRNG is a linear congruential  generator (LCG) with the following recurrence equation:

xnew = (ax + c) mod m                                                                                                                         (1)

where

x = current seed, which is initialized at power-up to 335212624223

a = 14389820420821

c = 21132486540519

m = 1014

xnew = pseudorandom number (PRN) sought and the new seed for the next go-round.

The above converts the above PRNG to the following equation:

xnew = (14389820420821x + 21132486540519) mod 1014                                                           (2)

To preserve precision for the radix-100, floating point calculations, all of the above numbers were split into a least-significant half (LSH), with subscript 1, and a most-significant half (MSH), with subscript 2, such that

x = 107x2 + x1

a = 107a2 + a1

c = 107c2 + c1

which converts (1) to

xnew = ((107a2 + a1)(107x2 + x1) + 107c2 + c1) mod 1014

xnew = (1014a2x2 + 107a2x1 + 107a1x2 + a1x1 +107c2 + c1) mod 1014                                             (3)

At the very least, the first term drops out because

1014a2x2 mod 1014 = 0

simplifying (3) to

xnew = (107a2x1 + 107a1x2 + a1x1 +107c2 + c1) mod 1014                                                                  (4)

...lee

It's nice to have a smart person around the house.

That's really neat to see that explained so clearly.

Your work gives me the impression that this should be a very good PRNG.

My burning question:  When we dumb it all down to a >300 byte screen position or a random direction vector,

is there a way to know that the BASIC or XB PRNGs would provide a material benefit versus a simpler solution?

(You helped me with a simple analysis tool a few years back, but I am a humble business weanie so the amount of rigour involved was slightly less.

One might say it was completely devoid of rigour.)

##### Share on other sites

4 hours ago, Vorticon said:

It would be good to also include UCSD Pascal in the comparison. Not sure what type of randomizer function it uses. I can help with the test code.

The principal reason for this thread is to explain what XBs PRNG is and how it is calculated—mainly, because there seems to have been a lot of misunderstanding over the years about it. I (we) will certainly be comparing it to other PRNGs, but it is not the primary purpose.

...lee

##### Share on other sites

2 minutes ago, Lee Stewart said:

The principal reason for this thread is to explain what XBs PRNG is and how it is calculated—mainly, because there seems to have been a lot of misunderstanding over the years about it. I (we) will certainly be comparing it to other PRNGs, but it is not the primary purpose.

...lee

Maybe add some timing also long doing a rnd or randomize in original XB takes for those using it often in their games.

One final thought does converting your XB program into compiled XB to run faster does it change the functionality in anyway or is it still the same PRNG math magic.

##### Share on other sites

Posted (edited)
2 hours ago, Gary from OPA said:

Maybe add some timing also long doing a rnd or randomize in original XB takes for those using it often in their games.

One final thought does converting your XB program into compiled XB to run faster does it change the functionality in anyway or is it still the same PRNG math magic.

The RND in BASIC is over 5x faster than RND in XB. Both RXB and XB2.9 G.E.M. now use the faster TI BASIC random number routine. I have a feeling that it may not be as random as the XB one though.

Random numbers in a compiled XB program are different than they are in a BASIC/XB program. Compiled code can only use integer arithmetic. That is a problem, because in BASIC a random number is from 0 to .9999999

The work around is to multiply the random number by another number. i.e. instead of:

10 IF RND<.5 THEN 100 ELSE 200    you would use

10 IF RND*2<1 THEN 100 ELSE 200

RND*2 gives a random integer from 0 to 1

RND*6 gives a random integer from 0 to 5

and so on....

Edited by senior_falcon
##### Share on other sites

Interesting. That's basically the same style RND generator that Tombstone City had. A quick test in Classic99 though shows it avoids the "even/odd" alternating, which is nice.

##### Share on other sites

Posted (edited)

Interesting. That's basically the same style RND generator that Tombstone City had. A quick test in Classic99 though shows it avoids the "even/odd" alternating, which is nice.

yes, I read your blog last Saturday, as well, as this whole topic about "random" started during last zoom call thingy, and got into it for a bit. But it is good to see how it all works out, looking forward to Lee updating his first post with more info. I used similar RND code in my "3d string art" program I released the other day, which is similar to the math in Tombstone City as well, with the fixed integer math values.

Edited by Gary from OPA
##### Share on other sites

3 hours ago, TheBF said:

It's nice to have a smart person around the house.

That's really neat to see that explained so clearly.

Thank you.

3 hours ago, TheBF said:

Your work gives me the impression that this should be a very good PRNG.

Well, you might be disappointed. It may well be good enough for things TI-99/4A, but LCGs are, generally, not considered good PRNGs, anymore. Here is a comment made by the authors (W. H. Press, S. A. Teukolsky, W. T. Vetterling, B. P. Flannery) of Numerical Recipes: The Art of Scientific Computing, 3rd Edition, 2007, in Chapter 7, “Random Numbers”

With hindsight, it seems clear that the whole field of random number generation was mesmerized, for far too long, by the simple recurrence equation

Ij+1 = aIj  +  c    (mod m)                                          (7.1.1)

You can read the entire chapter (or book, for that matter) at the Numerical Recipes website. The above-referenced chapter as well as Chapter 3, “Random Numbers” in D. E. Knuths The Art of Computer Programming, Volume 2: Seminumerical Algorithms, 3rd Edition, 1998, are very good, comprehensive discussions and references on the subject.

3 hours ago, TheBF said:

My burning question:  When we dumb it all down to a >300 byte screen position or a random direction vector,

is there a way to know that the BASIC or XB PRNGs would provide a material benefit versus a simpler solution?

There are, certainly, tests for assessing the quality of PRNGs, but, again, my primary mission in this thread is to explain the XB PRNG.

...lee

• 1
• 1
##### Share on other sites

Posted (edited)

Well you can add XB3 to that list of ways to get RND working I am currently disassembling all of XB3 GPL GROMs and ROMs.

Reposting my times:

```
90 ! ROUTINE SET UP SPRITES TEST SO NOT PART OF TIMING ROUTINE
100 CALL CLEAR
110 OPEN #1:"CLOCK"
120 INPUT #1:A\$,B\$,C\$
130 FOR C=1 TO 10000
140 ! ROUTINE INSERTED HERE TO TEST
150 NEXT C
160 INPUT #1:D\$,E\$,F\$
170 PRINT A\$,D\$:B\$,E\$,C\$,F\$
180 END

XB       XB3     XBGEM   MyarcXB2   RXB   command
-------------------------------------------------------------------------
11:5      9:10     3:11      5:6     3:21  CALL CHAR(65,"FFFFFFFF")
2:57:15    34:18     5:33      6:19    4:42  CALL CHAR(65,Z\$) ! LENGTH 64
11:32     7:19     4:1       5:14    4:5   CALL CHARPAT(65,Z\$)
32:44     33:6  1:11:47      5:1     4:8   CALL CHARSET
3:49     3:29     3:43      8:48    3:41  CALL CLEAR
6:25     6:25     6:24      7:22    6:19  CALL COINC(#1,20,20,8,Z)
3:0      3:0      3:0       4:3     3:4   CALL COINC(ALL,Z)
4:33     4:15     4:15      5:4     4:24  CALL COLOR(1,2,8)
3:9      3:9      3:9       4:6     2:36  CALL DELSPRITE(#1)
6:22     6:23     6:22      4:18    5:33  CALL DELSPRITE(ALL)
6:5      6:6      6:6       6:0     6:1   CALL DISTANCE(#1,20,20,Z)
6:6      3:7      4:10      5:9     4:2   CALL GCHAR(1,1,Z)
4:11     3:1      4:5       5:5     4:17  CALL HCHAR(1,1,65)
4:37:35  4:39:17     9:31     14:12    7:33  CALL HCHAR(1,1,65,768)
5:0      3:2      5:6       6:43    5:7   CALL JOYST(1,X,Y)
4:37     3:25     4:38      5:16    5:2   CALL KEY(1,K,S)
4:4      4:4      4:4       5:13    4:17  CALL LOCATE(#1,20,20)
2:17     2:11     2:14      3:45    2:14  CALL MAGNIFY(1)
4:44     4:44     4:44      6:42    4:41  CALL MOTION(#1,20,20)
3:8      3:17     3:17      5:27    3:4   CALL PATTERN(#1,66)
5:22     5:22     5:31      5:25    5:9   CALL POSITION(#1,X,Y)
3:44     2:11     2:6       4:3     2:17  CALL SCREEN(5)
5:9      5:8      5:9       6:30    5:7   CALL SPRITE(#1,65,2,10,10)
3:55     3.0      4:5       5:5     4:17  CALL VCHAR(1,1,65)
4:30:22  4:30:41    18:29     18:11   21:3   CALL VCHAR(1,1,65,768)
10:2      8:13    10:1       6:18   10:3   DISPLAY AT(9,9):C
21:10    13:41    11:49     11:29   12:13  DISPLAY AT(9,9):RND
8:22     6:43     8:22      6:17    8:21  DISPLAY AT(9,9):"TEST"
12:12     5:3      3:8       7:16    3:9   A=RND
******************************************************************************
* RXB COMMANDS
DISPLAY  DISPLAY  DISPLAY  DISPLAY    5:6   CALL HPUT(11,11,C)
DISPLAY  DISPLAY  DISPLAY  DISPLAY    7:12  CALL HPUT(11,11,RND)
DISPLAY  DISPLAY  DISPLAY  DISPLAY    4:29  CALL HPUT(11,11,"TEST")
14:17    10:10    15:42     16:16   11:50  CALL JOYLOCATE(1,X,Y,8,8,#1,RW,CL,K) GOTO
15:53    10:20    15:3      16:1    10:2   CALL JOYMOTION(1,X,Y,#1,9,9,K) GOTO
7:7      5:11     7:7      11:3     9:33  CALL ONKEY("ABC",1,K,S) GOTO 1,2,3
N/A      N/A      N/A       N/A     7:26  CALL COLLIDE(#1,20,20,8,X,Y)
N/A      N/A     10:31      N/A    11:39  CALL MOVES("RR",1024,8192,12288)
******************************************************************************
80 ! JOYLOCATE FOR OTHERS
90 CALL SPRITE(#1,65,2,20,20) :: XL,YL=20
100 CALL CLEAR
110 OPEN #1:"CLOCK"
120 INPUT #1:A\$,B\$,C\$
130 FOR C=1 TO 10000
140 CALL JOYST(1,X,Y) :: CALL LOCATE(#1,X+XL,Y+YL) :: CALL KEY(1,K,S)
:: IF K=18 THEN 150
150 NEXT C
160 INPUT #1:D\$,E\$,F\$
170 PRINT A\$,D\$:B\$,E\$,C\$,F\$
180 END
*****************************************************************************
80 ! JOYMOTION FOR OTHERS
90 CALL SPRITE(#1,65,2,20,20) :: XL,YL=20
100 CALL CLEAR
110 OPEN #1:"CLOCK"
120 INPUT #1:A\$,B\$,C\$
130 FOR C=1 TO 10000
140 CALL JOYST(1,X,Y) :: CALL MOTION(#1,X*9,Y*9) :: CALL KEY(1,K,S)
:: IF K=18 THEN 150
150 NEXT C
160 INPUT #1:D\$,E\$,F\$
170 PRINT A\$,D\$:B\$,E\$,C\$,F\$
180 END
****************************************************************************
80 ! ONKEY FOR OTHERS
90 CALL SPRITE(#1,65,2,20,20) :: XL,YL=20
100 CALL CLEAR
110 OPEN #1:"CLOCK"
120 INPUT #1:A\$,B\$,C\$
130 FOR C=1 TO 10000
140 CALL KEY(1,K,S) :: IF K=65 THEN 150 ELSE IF K=66 THEN 150 ELSE IF
K=67 THEN 150 ELSE 150
150 NEXT C
160 INPUT #1:D\$,E\$,F\$
170 PRINT A\$,D\$:B\$,E\$,C\$,F\$
180 END
****************************************************************************```

Edited by RXB
##### Share on other sites

I updated the first post with Part 2.

Soon, I will post the XB GPL source code for RND accompanied by a translation to ALC.

...lee

• 3
• 1
##### Share on other sites

21 hours ago, Lee Stewart said:

Soon, I will post the XB GPL source code for RND accompanied by a translation to ALC.

Posted the GPL code with additional comments by yours truly—no ALC translation, yet.

...lee

##### Share on other sites

I just discovered a bug in XB’s RANDOMIZE. There is no check for passing it a negative number. If you pass it any negative number, it screws up the seed such that the first call to RND computes the next seed to be 0 and returns 0 to the caller. This happens because the first thing RANDOMIZE does after being passed a value, is to overstore the sign+exponent byte with >46, which, for a positive number, merely forces the mantissa to be an integer, regardless of whether the passed number had a fractional part, but for a negative number, the first word (2 bytes!) must be dealt with or strange things will happen. In this case, the first byte of the mantissa is now greater than 99 (>63)—an illegal radix-100 digit.

I will post a brief (?) discussion of this bug and a fix for my ALC port of the GPL after I finish the code. I just thought I should post it here before I forgot it.

...lee

• 4
• 1
##### Share on other sites

I have completed the translation of XB’s GPL code for RND and RANDOMIZE. I have also tested it and it duplicates XB’s execution. The ALC is 722 bytes so it is definitely too big for the fbForth 3.0 kernel, even if split up over several banks—oh, well. RND is about 3.3x faster than XB’s GPL code.

Eventually, I will post the code in the first post along with a discussion, but, for now, I will just post it here for your delectation:

Spoiler
```*******************************************************************
* PRNG_FBF exposes 5 functions as ALC equivalents for XB's        *
*     GPL code:                                                   *
* opcode fxn     XB fxn  description                              *
* ------ ------  ------- ---------------------------------------- *
*    0   NRND....NRND....GPL code called by RND                   *
*    1   RNZNEW..NRNDMZ..GPL code called by RANDOMIZE <user seed> *
*    2   RNZOLD..NRNDMZ..GPL code called by RANDOMIZE             *
*    3   SEEDIN..Initialize FP seed (done at XB startup           *
*    4   RND99...RAND 99..Console ROM ALC for GPL "RAND 99"...    *
*           ...returns "random" 1-digit centimal number in R0 MSB *
*******************************************************************

*******************************************************************
* Necessary EQUates
*
FPSTK  EQU  R13      FP stack pointer
RSTK   EQU  R15      return stack pointer
FPSTK0 EQU  >8378    bottom of FP stack in scratchpad RAM (grows up)
RSTK0  EQU  >83BE    bottom of return stack in scratchpad RAM (grows down)

FSUB   EQU  >0D7C    console ROM FP SUBtraction subroutine
FMUL   EQU  >0E88    console ROM FP MULtiplication subroutine
FAC    EQU  >834A
ARG    EQU  >835C
GPLWS  EQU  >83E0

* RNDX1, RNDX2, SAVWP are at top of low-level support RAM for fbForth,
* just above system PPBs. If using for some other system, these can
* be anywhere in modifiable RAM.
RNDX1  EQU  >3C1A    PRNG seed (lower half)
RNDX2  EQU  >3C22    PRNG seed (upper half)
PRNGWS EQU  >3C2A    PRNG workspace (only using R13,R14,R15)

AORG >A032    HERE at fbForth 3.0 startup..
*++                  ..put wherever is convenient

******************************************************************
*++ PRNG: Vector for BLWP to the FP PRNG functions.
*++   Input...R0: opcode
*++
*++   Called with
*++      BLWP @PRNG
*
PRNG   DATA PRNGWS,PRNGEN
*
*++ Entry point (PRNGEN) and exit (PRNGEX) are farther below to
*++ facilitate JuMPs instead of Branches.
*++
******************************************************************
* Subroutine call table:
*
PRNGTB DATA NRND,RNZNEW,RNZOLD,SEEDIN,RND99
*++ opcode:  0     1      2      3      4
*++
*******************************************************************
* SPUSH pushes 8 bytes, usually a Floating Point Number (FPN),    *
* from FAC to Scratchpad stack, pointed to by FPSTK. There is only*
* room for 4 FPNs. The user must check for overflow/underflow.    *
*******************************************************************
*     FPSTK: FP stack pointer
*
SPUSH  DECT RSTK           reserve return stack space
MOV  R11,*RSTK      save return
AI   FPSTK,8        reserve FP stack space
LI   R0,FAC         source (FAC)
MOV  FPSTK,R1       destination
BL   @FPMOVE        copy FAC to top of stack
MOV  *RSTK+,R11     pop return

*******************************************************************
* SPOP pops 8 bytes, usually an FPN, from Scratchpad stack,       *
* pointed to by FPSTK, to FAC. There is only room for 4 FPNs. The *
* user must check for overflow/underflow.                         *
*******************************************************************
*     FPSTK: FP stack pointer
*
SPOP   DECT RSTK           reserve return stack space
MOV  R11,*RSTK      save return
MOV  FPSTK,R0       source
LI   R1,FAC         destination
BL   @FPMOVE        copy top of stack to FAC
AI   FPSTK,-8       pop FP stack
MOV  *RSTK+,R11     pop return

*******************************************************************
* SSUB first negates FPN in FAC, then proceeds with SADD. SADD    *
* pops FP value from FP stack to ARG and runs FADD to add FAC to  *
* ARG. Result is in FAC: ARG - FAC if SSUB or ARG + FAC if SADD.  *
*******************************************************************
*
SSUB   NEG  @FAC           negate FP number in FAC
SADD   DECT RSTK           reserve return stack space
MOV  R11,*RSTK      save return
MOV  FPSTK,R0       source
LI   R1,ARG         destination
BL   @FPMOVE        copy top of stack to ARG
AI   FPSTK,-8       pop FP stack
MOV  *RSTK+,R11     pop return

*******************************************************************
* INTPOS truncates a positive FPN in FAC. Zero or negative FPNs   *
* remain unchanged.                                               *
*     Exponent Fraction Bytes                                     *
*     -------- --------------                                     *
*     > 46h       0                                               *
*     = 46h       0                                               *
*     < 46h       1..7                                            *
*******************************************************************
*     uses R0,R1
*
INTPOS MOVB @FAC,R0        get sign+exp byte..negative?
JLT  INTPSX         yes..we're outta here
SRL  R0,8           to LSB, clearing MSB
LI   R1,>0046       load exp comparison value
S    R0,R1          fraction part?                R1=46h-exp
JGT  INTPS1         yes..zero it
INTPS1 CI   R1,6           integer part?
JGT  INTPS3         no..go zero number
LI   R0,FAC+7       start at last byte
INTPS2 MOVB R1,*R0         zero next byte (R1 MSB = 0)
DEC  R0             next byte left
DEC  R1             done?
JNE  INTPS2         no..zero another byte
INTPS3 CLR  @FAC           return 0..no integer part

*******************************************************************
* FPMOVE copies 8 bytes from source in R0 to destination in R1.   *
* FRMFAC copies 8 bytes from FAC to destination passed via R11,   *
*        bumping R11 once.                                        *
* TOARG  copies 8 bytes from source passed via R11 to ARG,        *
*        bumping R11 once.                                        *
* TOFAC  copies 8 bytes from source passed via R11 to FAC,        *
*        bumping R11 once.                                        *
*******************************************************************
*
FRMFAC LI   R0,FAC      FAC is source
JMP  FPMV1       do it
TOARG  LI   R1,ARG      ARG is destination
JMP  FPMOVE      do it
TOFAC  LI   R1,FAC      FAC is destination
TOFAC1 MOV  *R11+,R0    get source address
JMP  FPMOVE      do it
FPMV1  MOV  *R11+,R1    get destination address
FPMOVE MOV  *R0+,*R1+   copy bytes 0,1
MOV  *R0+,*R1+   copy bytes 2,3
MOV  *R0+,*R1+   copy bytes 4,5
MOV  *R0,*R1     copy bytes 6,7

*************************************************************************
* Pseudorandom Number Generator                                         *
*************************************************************************
*                                                                       *
*     X[n+1] = (A*X[n]+C) mod M;  RND = X/M                             *
*     X[n+1] = (14389820420821 * X[n] + 21132486540519) mod 1E14        *
*                                                                       *
*     where:  X = X2 * 1E7 + X1                                         *
*             A = A2 * 1E7 + A1                                         *
*             C = C2 * 1E7 + C1                                         *
*             M = 1E14                                                  *
* Initially:                                                            *
*     X[n+1] = (14389820420821(335212624223) + 21132486540519) mod 1E14 *
*                                                                       *
* Assumptions:                                                          *
*  (1) All numbers are integers; fractional parts are                   *
*      truncated                                                        *
*  (2) If the variables listed below start in the ranges                *
*      specified. They will also end in the ranges specified            *
*                                                                       *
* Constants: 0 <= A2 <  5E6  ; 0 <= A1 < 5E6                            *
*            0 <= C2 <  1E7  ; 0 <= C1 < 1E7                            *
* Variables: 0 <= X2 <  1E7  ; 0 <= X1 < 1E7                            *
*            0 <= T1 <= 1E14 ; 0 <= T2 < 1E7                            *
*            0 <= T3 <= 1E14 ; 0 <= T4 < 1E7                            *
*                                                                       *
* Stack usage:                                                          *
*            Constant Refs      Constant Refs   Constant Refs           *
* +---------+      In/Out            In/Out          In/Out             *
* | STACK+4 | X2*A1(F)(H)       --    ----      --    ----              *
* +---------+                                                           *
* | STACK+3 |   T2 (C)(J)       --    ----      --    ----              *
* +---------+                                                           *
* | STACK+2 |   T1 (B)(D)      X1new (E)(N)     --    ----              *
* +---------+                                                           *
* | STACK+1 |old X1(A)(G)       T3   (K)(L)    X2new (M)(P)             *
* +---------+                                                           *
*************************************************************************
* NRND expects to be called from GPL workspace (>83E0).
*
* Compute new value for X1..save it in @RNDX1
*
*                          Stack usage....................................
*                                 FAC/ARG
*                          Srefs  Contents               Stack
*                          -----  -----------------      -----------------
NRND   BL   @TOFAC                FAC = X1
DATA RNDX1
BL   @SPUSH         (A)    FAC = X1                 S:x1
BL   @TOARG                ARG = A1
DATA RNDA1
BL   @FMUL                 FAC = X1*A1
BL   @TOARG                ARG = C1
DATA RNDC1
BL   @SPUSH         (B)    FAC = T1                 S:x1 t1
BL   @TOARG                ARG = 1/1E7
DATA RNDEM
BL   @FMUL                 FAC = T1/1E7
BL   @INTPOS            T2=FAC = INT(T1/1E7)
BL   @SPUSH         (C)    FAC = T2                 S:x1 t1 t2
BL   @TOARG                ARG = 1E7
DATA RNDEP
BL   @FMUL                 FAC = T2*1E7             S:x1 t1 t2
AI   FPSTK,-8    hide T2 so T1 TOS for SSUB         S:x1 t1 {t2}
BL   @SSUB       (D) X1new=FAC = T1-T2*1E7          S:x1 {t1 t2}
BL   @FRMFAC               FAC = X1new
DATA RNDX1
BL   @SPUSH          (E)   FAC = X1new              S:x1 x1new {t2}
*
* Compute new value for X2, save it in @RNDX2
*
BL   @TOFAC                FAC = X2
DATA RNDX2
CLR  @FAC+6         FAC6..FAC7 = >0000
MOVB @FAC+6,@FAC+5        FAC5 = >00
BL   @TOARG                ARG = A1
DATA RNDA1
BL   @FMUL                 FAC = X2*A1
AI   FPSTK,8     expose T2 for later              S:x1 x1new t2
BL   @SPUSH         (F)    FAC = X2*A1            S:x1 x1new t2 x2*a1
AI   FPSTK,-24   hide top 3 FPNs on stack         S:x1 {x1new t2 x2*a1}
BL   @SPOP          (G)    FAC = X1               S:{x1 x1new t2 x2*a1}
AI   FPSTK,32    expose all four FPNs on stack    S:x1 x1new t2 x2*a1
BL   @TOARG                ARG = A2
DATA RNDA2
BL   @FMUL                 FAC = X1*A2
BL   @SADD           (H)   FAC = X2*A1+X1*A2        S:x1 x1new t2
BL   @TOARG                ARG = C2
DATA RNDC2
BL   @SADD          (J) T3=FAC = X2*A1+X1*A2+C2+T2  S:x1 x1new
AI   FPSTK,-16   hide FPNs on stack                 S:{x1 x1new}
BL   @SPUSH         (K)    FAC = T3                 S:t3 {x1new}
BL   @TOARG                ARG = 1/1E7
DATA RNDEM
BL   @FMUL                 FAC = T3/1E7
BL   @INTPOS            T4=FAC = INT(T3/1E7)
BL   @TOARG                ARG = 1E7
DATA RNDEP
BL   @FMUL                 FAC = T4*1E7
BL   @SSUB          (L) X2new=FAC = T3-T4*1E7       S:{x1new}
BL   @FRMFAC               FAC = X2new
DATA RNDX2
*
* Compute new value for RND, leave it in FAC for return to caller
*     0 <= RND < 1
*
BL   @TOARG                ARG = 1/1E7
DATA RNDEM
BL   @FMUL                 FAC = X2new/1E7
BL   @SPUSH         (M)    FAC = X2new/1E7          S:x2new/1e7 {x1new}
AI   FPSTK,8     expose X1new on stack              S:x2new/1e7 x1new
BL   @SPOP          (N)    FAC = X1new              S:x2new/1e7
BL   @FMUL                 FAC = X1new/1E7
BL   @FMUL                 FAC = X1new/1E14

***********************************************************
* Constants for the Random Number Routine
RNDA2  DATA >4301,>2B59,>5200,>0000    ; = 1438982
RNDA1  DATA >422A,>0815,>0000,>0000    ; = 0420821
RNDC2  DATA >4302,>0B20,>3000,>0000    ; = 2113248
RNDC1  DATA >4306,>3605,>1300,>0000    ; = 6540519
RNDEP  DATA >430A,>0000,>0000,>0000    ; = 1E7
RNDEM  DATA >3C0A,>0000,>0000,>0000    ; = 1/1E7
X2SEED DATA >4203,>2315,>0000,>0000    ; =   33521, X2 initial value
X1SEED DATA >4302,>3E2A,>1700,>0000    ; = 2624223, X1 initial value
***********************************************************

******************************************************************
* PRNGEN is the entry point for the 5 PRNG routines,
*     NRND: opcode = 0
*   RNZNEW: opcode = 1 and user's seed in FAC
*   RNXOLD: opcode = 2
*   SEEDIN: opcode = 3 initializes FP seed to XB startup value
*    RND99: opcode = 4 calls RAND99 internally
*
PRNGEN LIMI 0                 disable interrupts
MOV  *R13,@GPLWS+2     save opcode from caller's R0 to GPLWS R1
LWPI GPLWS             switch to GPL workspace
MOV  R1,R1             negative?
CI   R1,4              no..too high?
LI   FPSTK,FPSTK0      initialize FP stack (grows up)
LI   RSTK,RSTK0        initialize return stack (grows down)
SLA  R1,1              make opcode an index
MOV  @PRNGTB(R1),R1    get branch address of opcode
B    *R1               do it

*******************************************************************
* SEEDIN should be run at system startup                          *
* Initialize Seed for Pseudorandom Number Generator               *
*******************************************************************
*
BL   @FPMOVE        copy startup seed X2
BL   @FPMOVE        copy startup seed X1
* Fall through to PRNGEX to clean up and return to caller...

*******************************************************************
*
PRNGEX LI   FPSTK,>9800       restore R13
LI   RSTK,>8C02        restore R15
LWPI PRNGWS
RTWP

*******************************************************************
*                   RANDOMIZE Statement (2 subroutines)
*******************************************************************
* RNZNEW expects to be called with the user-supplied seed in FAC.
* Place value is ignored in the input number.
* (99,000,000,000,001 possible starting positions)
*++ [LES note: Actually, 100^7 = 100,000,000,000,000 are
*++            possible, but who's counting?!]
*
RNZNEW MOV  @FAC,R0           FAC = 0?
JEQ  GA3B6             yes..we're done
JGT  RNZNW1            > 0..get on with it
NEG  @FAC              first make it positive
RNZNW1 LI   R0,>4600          exponent for integer only
MOVB R0,@FAC           0 < FAC < 1E14
BL   @SPUSH            Let FAC = X2*1E7+X1           S:x2*1e7+x1
BL   @TOARG                ARG = 1/1E7
DATA RNDEM
BL   @FMUL                 FAC = X2+X1/1E7
BL   @INTPOS               FAC = X2
BL   @FRMFAC               FAC = X2
DATA RNDX2
BL   @TOARG                ARG = 1E7
DATA RNDEP
BL   @FMUL                 FAC = X2*1E7              S:x2*1e7+x1
BL   @SSUB              X1=FAC = X2*1E7+X1-X2*1E7    S:
BL   @FRMFAC               FAC = X1
DATA RNDX1

GA3B6  MOV  @FAC,@RNDX2           FAC = 0
MOV  @FAC,@RNDX1           FAC = 0

********************************************************************
* RNZOLD will calculate a new seed from the old 16-bit seed at >83C0
* via 4 calls to RAND99. >83C0 is actually different for every pro-
* gram start. RNZOLD calls RAND99 twice each for X1 and X2. RAND99
* is modified from GPL RAND to obviate needing to pass the maximum
* centimal digit as well as to include XB's "SRL R0,2" shift.
*     NOPE-->(16K possible starting positions)<--NOPE
* ACTUALLY-->(25^4 = 390,625 possible positions)<--ACTUALLY
*
*++ If TI had not added the "SRL R0,2" for each call to
*++ "RAND 99", it would have been 100^4 = 100,000,000
*
RANDOM EQU  >8378

RNZOLD LI   R0,>4201
MOV  R0,@FAC     FAC = >4201 (sign+exponent+1st digit fixed at 1)
CLR  @FAC+4      FAC+4..
CLR  @FAC+6      ..FAC+7 = >00000000
BL   @RNDMZ      Set up new seed..
DATA RNDX1       ..for X1
BL   @RNDMZ      Set up new seed..
DATA RNDX2       ..for X2
*                       ..Xnew = 100000010000..124240012424

* Get next 2 pseudorandom centimal digits
*
RNDMZ  DECT RSTK        reserve return stack space
MOV  *R11+,R1    Get address of seed-half destination for FPMOVE
MOV  R11,*RSTK   save new return
BL   @RAND99     GPL Randomize..1 centimal digit (2 decimal)
MOVB R0,@FAC+2   0<=FAC+2<=24[18h]!!
BL   @RAND99     GPL Randomize..1 centimal digit (2 decimal)
MOVB R0,@FAC+3   0<=FAC+3<=24[18h]!!
LI   R0,FAC      source
BL   @FPMOVE     Put in seed half..max: >4201181800 = 12424
MOV  *RSTK+,R11  pop return

*--RAND99 is based on Console ROM 0's ALC for GPL RAND, with the
*--addition of the 2-bit right shift of XB's RNDMZ---------------
*----------------------------------------------------------------
*------ Generate A Pseudorandom, Centimal Number in R0 MSB ------
*----------------------------------------------------------------
WKSC   EQU  >83C0             ISR workspace (R0 = PRN SEED)
WKSE   EQU  >83E0             GPL workspace
R5LSB  EQU  WKSE+R5+R5+1      low byte of R5 of GPL WS

RND99  BL   @RAND99     perform GPL RAND 99 equivalent

RAND99 LI   R4,>6FE5          28645 to R4
MPY  @WKSC,R4          SEED * 28645
AI   R5,>7AB9          PRN = SEED * 28645 + 31417
MOV  R5,@WKSC          PRN is stored as new SEED in >83C0
CLR  R4                clear upper half of dividend
SWPB R5                swap bytes of PRN to get PRN'
DIV  R6,R4             perform division by modulus (lim+1)
MOVB @R5LSB,R0         LSB of remainder to MSB of R0
* XB comments say >00<=R0<=FF <--WRONG!! ACTUALLY--> 0<=R0<=99[63h]
SRL  R0,2        [LES Note: To be removed soon]
* XB comments say >00<=R0<=3F <--WRONG!! ACTUALLY--> 0<=R0<=24[18h]
*++
*++ The above SRL will eventually be removed to get back to maximum
*++ possible centimal digits of 0..99. The SWPB may also be removed
*++ in favor of the "SRC R0,5" of TI Forth.

RT      Return random centimal digit to caller,
*++               which is, unfortunately, limited to 24 by someone
*++               who did not understand what "RAND 99" was actually
*++               doing! The XB comments indicate the programmers
*++               thought 99 was a seed for RAND, rather than the
*++               limit for the returned byte that it is!

END```

The ALC is extensively commented and hopefully fairly easy to understand, though that is not for me to say. At least, it should be easier to follow for those unfamiliar with GPL.

...lee

##### Share on other sites

That's a monster. You spent some time on that I reckon.

Do we have any idea if it would be better than say, a 32 bit integer PRNG scaled and converted to a float?

I know it would be a different sequence, but does the float version have any better characteristics?

##### Share on other sites

1 hour ago, TheBF said:

That's a monster. You spent some time on that I reckon.

Yeah, a bit more than I intended.

1 hour ago, TheBF said:

Do we have any idea if it would be better than say, a 32 bit integer PRNG scaled and converted to a float?

I know it would be a different sequence, but does the float version have any better characteristics?

Not sure, except for size. The largest 32-bit integer is 4,294,967,295. The largest TI FP integer is 99,999,999,999,999, which would require 47 bits to capture, so I guess it is potentially better.

...lee

##### Share on other sites

On 5/3/2024 at 9:59 PM, Lee Stewart said:

The ALC is extensively commented and hopefully fairly easy to understand, though that is not for me to say. At least, it should be easier to follow for those unfamiliar with GPL.

I modified my translation of XB’s GPL code for RND and RANDOMIZE to make comparisons with the GPL more straightforward and, hopefully, easier. I also made the comments clearer, I think:

Spoiler
```*******************************************************************
* PRNG_FBF exposes 5 functions as ALC equivalents for XB's        *
*     GPL code:                                                   *
* Opcode Fxn     XB fxn  Description of XB function               *
* ------ ------  ------- ---------------------------------------- *
*    0   NRND....NRND....GPL code called by RND                   *
*    1   RNZNEW..NRNDMZ..GPL code called by RANDOMIZE <user seed> *
*    2   RNZOLD..NRNDMZ..GPL code called by RANDOMIZE             *
*    3   SEEDIN..Initialize FP seed (done at XB startup           *
*    4   RAND99..RAND...Console ROM ALC for GPL RAND <max>...     *
*                    ...returns pseudorandom, 1-byte value in...  *
*                    ...MSB of R0                                 *
*******************************************************************

*******************************************************************
* Necessary EQUates
*
FPSTK  EQU  R13      FP stack pointer
RSTK   EQU  R15      return stack pointer
FPSTK0 EQU  >8378    bottom of FP stack in scratchpad RAM (grows up)
RSTK0  EQU  >83BE    bottom of return stack in scratchpad RAM (grows down)

FSUB   EQU  >0D7C    console ROM FP SUBtraction subroutine
FMUL   EQU  >0E88    console ROM FP MULtiplication subroutine
FAC    EQU  >834A
ARG    EQU  >835C
GPLWS  EQU  >83E0

* RNDX1, RNDX2, SAVWP are at top of low-level support RAM for fbForth,
* just above system PPBs. If using for some other system, these can
* be anywhere in modifiable RAM.
RNDX1  EQU  >3C1A    PRNG seed (lower half)
RNDX2  EQU  >3C22    PRNG seed (upper half)
PRNGWS EQU  >3C2A    PRNG workspace (only using R13,R14,R15)

AORG >A032    HERE at fbForth 3.0 startup..
*++                  ..put wherever is convenient

******************************************************************
*++ PRNG: Vector for BLWP to the FP PRNG functions.
*++   Input...R0: opcode
*++
*++   Called with
*++      BLWP @PRNG
*
PRNG   DATA PRNGWS,PRNGEN
*
*++ Entry point (PRNGEN) and exit (PRNGEX) are farther below to
*++ facilitate JuMPs instead of Branches.
*++
******************************************************************
* Subroutine call table:
*
PRNGTB DATA NRND,RNZNEW,RNZOLD,SEEDIN,RAND99
*++ opcode:  0     1      2      3      4
*++
*******************************************************************
* SPUSH pushes 8 bytes, usually a Floating Point Number (FPN),    *
* from FAC to Scratchpad stack, pointed to by FPSTK. There is only*
* room for 4 FPNs. The user must check for overflow/underflow.    *
*******************************************************************
*     FPSTK: FP stack pointer
*
SPUSH  DECT RSTK           reserve return stack space
MOV  R11,*RSTK      save return
AI   FPSTK,8        reserve FP stack space
LI   R0,FAC         source (FAC)
MOV  FPSTK,R1       destination
BL   @FPMOVE        copy FAC to top of stack
MOV  *RSTK+,R11     pop return

*******************************************************************
* SPOP pops 8 bytes, usually an FPN, from Scratchpad stack,       *
* pointed to by FPSTK, to FAC. There is only room for 4 FPNs. The *
* user must check for overflow/underflow.                         *
*******************************************************************
*     FPSTK: FP stack pointer
*
SPOP   DECT RSTK           reserve return stack space
MOV  R11,*RSTK      save return
MOV  FPSTK,R0       source
LI   R1,FAC         destination
BL   @FPMOVE        copy top of stack to FAC
AI   FPSTK,-8       pop FP stack
MOV  *RSTK+,R11     pop return

*******************************************************************
* SSUB first negates FPN in FAC, then proceeds with SADD. SADD    *
* pops FP value from FP stack to ARG and runs FADD to add FAC to  *
* ARG. Result is in FAC: ARG - FAC if SSUB or ARG + FAC if SADD.  *
*******************************************************************
*
SSUB   NEG  @FAC           negate FP number in FAC
SADD   DECT RSTK           reserve return stack space
MOV  R11,*RSTK      save return
MOV  FPSTK,R0       source
LI   R1,ARG         destination
BL   @FPMOVE        copy top of stack to ARG
AI   FPSTK,-8       pop FP stack
MOV  *RSTK+,R11     pop return

*******************************************************************
* INTPOS truncates a positive FPN in FAC. Zero or negative FPNs   *
* remain unchanged.                                               *
*     Exponent Fraction Bytes                                     *
*     -------- --------------                                     *
*     > 46h       0                                               *
*     = 46h       0                                               *
*     < 46h       1..7                                            *
*******************************************************************
*     uses R0,R1
*
INTPOS MOVB @FAC,R0        get sign+exp byte..negative?
JLT  INTPSX         yes..we're outta here
SRL  R0,8           to LSB, clearing MSB
LI   R1,>0046       load exp comparison value
S    R0,R1          fraction part?                R1=46h-exp
JGT  INTPS1         yes..zero it
INTPS1 CI   R1,6           integer part?
JGT  INTPS3         no..go zero number
LI   R0,FAC+7       start at last byte
INTPS2 MOVB R1,*R0         zero next byte (R1 MSB = 0)
DEC  R0             next byte left
DEC  R1             done?
JNE  INTPS2         no..zero another byte
INTPS3 CLR  @FAC           return 0..no integer part

*******************************************************************
* FPMOVE copies 8 bytes from source in R0 to destination in R1.   *
* FRMFAC copies 8 bytes from FAC to destination passed via R11,   *
*        bumping R11 once.                                        *
* TOARG  copies 8 bytes from source passed via R11 to ARG,        *
*        bumping R11 once.                                        *
* TOFAC  copies 8 bytes from source passed via R11 to FAC,        *
*        bumping R11 once.                                        *
*******************************************************************
*
FRMFAC LI   R0,FAC      FAC is source
JMP  FPMV1       do it
TOARG  LI   R1,ARG      ARG is destination
JMP  FPMOVE      do it
TOFAC  LI   R1,FAC      FAC is destination
TOFAC1 MOV  *R11+,R0    get source address
JMP  FPMOVE      do it
FPMV1  MOV  *R11+,R1    get destination address
FPMOVE MOV  *R0+,*R1+   copy bytes 0,1
MOV  *R0+,*R1+   copy bytes 2,3
MOV  *R0+,*R1+   copy bytes 4,5
MOV  *R0,*R1     copy bytes 6,7

*************************************************************************
* Pseudorandom Number Generator: NRAND                                  *
*************************************************************************
*                                                                       *
*     X[n+1] = (A*X[n]+C) mod M;  RND = X/M                             *
*     X[n+1] = (14389820420821 * X[n] + 21132486540519) mod 1E14        *
*                                                                       *
*     where:  X = X2 * 1E7 + X1                                         *
*             A = A2 * 1E7 + A1                                         *
*             C = C2 * 1E7 + C1                                         *
*             M = 1E14                                                  *
* Initially:                                                            *
*     X[n+1] = (14389820420821(335212624223) + 21132486540519) mod 1E14 *
*                                                                       *
* Assumptions:                                                          *
*  (1) All numbers are integers; fractional parts are                   *
*      truncated                                                        *
*  (2) If the variables listed below start in the ranges                *
*      specified. They will also end in the ranges specified            *
*                                                                       *
* Constants: 0 <= A2 <  5E6  ; 0 <= A1 < 5E6                            *
*            0 <= C2 <  1E7  ; 0 <= C1 < 1E7                            *
* Variables: 0 <= X2 <  1E7  ; 0 <= X1 < 1E7                            *
*            0 <= T1 <= 1E14 ; 0 <= T2 < 1E7                            *
*            0 <= T3 <= 1E14 ; 0 <= T4 < 1E7                            *
*                                                                       *
* Stack usage:                                                          *
*            Constant Refs      Constant Refs   Constant Refs           *
* +---------+      In/Out            In/Out          In/Out             *
* | STACK+4 | X2*A1(F)(H)       --    ----      --    ----              *
* +---------+                                                           *
* | STACK+3 |   T2 (C)(J)       --    ----      --    ----              *
* +---------+                                                           *
* | STACK+2 |   T1 (B)(D)      X1new (E)(N)     --    ----              *
* +---------+                                                           *
* | STACK+1 |old X1(A)(G)       T3   (K)(L)    X2new (M)(P)             *
* +---------+                                                           *
*************************************************************************

*=======================================================================*
*==~~~~~~~~~~~~~~~~~~~~~~~~~ NRND Opcode = 0 ~~~~~~~~~~~~~~~~~~~~~~~~~==*
*=======================================================================*
*== NRND is functionally equivalent to XB's GPL NRND. It is called    ==*
*== from GPL workspace(>83E0) by PRNG. It leaves the FP result in FAC.==*
*=======================================================================*
*
* Compute new value for X1..save it in @RNDX1
*
*                          Stack usage....................................
*                                 FAC/ARG
*                          Srefs  Contents               Stack
*                          -----  -----------------      -----------------
NRND   BL   @TOFAC                FAC = X1
DATA RNDX1
BL   @SPUSH         (A)    FAC = X1                 S:x1
BL   @TOARG                ARG = A1
DATA RNDA1
BL   @FMUL                 FAC = X1*A1
BL   @TOARG                ARG = C1
DATA RNDC1
BL   @SPUSH         (B)    FAC = T1                 S:x1 t1
BL   @TOARG                ARG = 1/1E7
DATA RNDEM
BL   @FMUL                 FAC = T1/1E7
BL   @INTPOS            T2=FAC = INT(T1/1E7)
BL   @SPUSH         (C)    FAC = T2                 S:x1 t1 t2
BL   @TOARG                ARG = 1E7
DATA RNDEP
BL   @FMUL                 FAC = T2*1E7             S:x1 t1 t2
AI   FPSTK,-8    hide T2 so T1 TOS for SSUB         S:x1 t1 {t2}
BL   @SSUB       (D) X1new=FAC = T1-T2*1E7          S:x1 {t1 t2}
BL   @FRMFAC               FAC = X1new
DATA RNDX1
BL   @SPUSH          (E)   FAC = X1new              S:x1 x1new {t2}
*
* Compute new value for X2, save it in @RNDX2
*
BL   @TOFAC                FAC = X2
DATA RNDX2
CLR  @FAC+6         FAC6..FAC7 = >0000
MOVB @FAC+6,@FAC+5        FAC5 = >00
BL   @TOARG                ARG = A1
DATA RNDA1
BL   @FMUL                 FAC = X2*A1
AI   FPSTK,8     expose T2 for later              S:x1 x1new t2
BL   @SPUSH         (F)    FAC = X2*A1            S:x1 x1new t2 x2*a1
AI   FPSTK,-24   hide top 3 FPNs on stack         S:x1 {x1new t2 x2*a1}
BL   @SPOP          (G)    FAC = X1               S:{x1 x1new t2 x2*a1}
AI   FPSTK,32    expose all four FPNs on stack    S:x1 x1new t2 x2*a1
BL   @TOARG                ARG = A2
DATA RNDA2
BL   @FMUL                 FAC = X1*A2
BL   @SADD           (H)   FAC = X2*A1+X1*A2        S:x1 x1new t2
BL   @TOARG                ARG = C2
DATA RNDC2
BL   @SADD          (J) T3=FAC = X2*A1+X1*A2+C2+T2  S:x1 x1new
AI   FPSTK,-16   hide FPNs on stack                 S:{x1 x1new}
BL   @SPUSH         (K)    FAC = T3                 S:t3 {x1new}
BL   @TOARG                ARG = 1/1E7
DATA RNDEM
BL   @FMUL                 FAC = T3/1E7
BL   @INTPOS            T4=FAC = INT(T3/1E7)
BL   @TOARG                ARG = 1E7
DATA RNDEP
BL   @FMUL                 FAC = T4*1E7
BL   @SSUB          (L) X2new=FAC = T3-T4*1E7       S:{x1new}
BL   @FRMFAC               FAC = X2new
DATA RNDX2
*
* Compute new value for RND, leave it in FAC for return to caller
*     0 <= RND < 1
*
BL   @TOARG                ARG = 1/1E7
DATA RNDEM
BL   @FMUL                 FAC = X2new/1E7
BL   @SPUSH         (M)    FAC = X2new/1E7          S:x2new/1e7 {x1new}
AI   FPSTK,8     expose X1new on stack              S:x2new/1e7 x1new
BL   @SPOP          (N)    FAC = X1new              S:x2new/1e7
BL   @FMUL                 FAC = X1new/1E7
BL   @FMUL                 FAC = X1new/1E14

***********************************************************
* Constants for the Random Number Routine
RNDA2  DATA >4301,>2B59,>5200,>0000    ; = 1438982
RNDA1  DATA >422A,>0815,>0000,>0000    ; = 0420821
RNDC2  DATA >4302,>0B20,>3000,>0000    ; = 2113248
RNDC1  DATA >4306,>3605,>1300,>0000    ; = 6540519
RNDEP  DATA >430A,>0000,>0000,>0000    ; = 1E7
RNDEM  DATA >3C0A,>0000,>0000,>0000    ; = 1/1E7
X2SEED DATA >4203,>2315,>0000,>0000    ; =   33521, X2 initial value
X1SEED DATA >4302,>3E2A,>1700,>0000    ; = 2624223, X1 initial value
***********************************************************

******************************************************************
* PRNGEN is the entry point for the 5 PRNG routines,
*     NRND: opcode = 0
*   RNZNEW: opcode = 1 and user's seed in FAC
*   RNXOLD: opcode = 2
*   SEEDIN: opcode = 3 initializes FP seed to XB startup value
*   RAND99: opcode = 4 calls RAND with "DATA 99" internally
*
PRNGEN LIMI 0                 disable interrupts
MOV  *R13,@GPLWS+2     save opcode from caller's R0 to GPLWS R1
LWPI GPLWS             switch to GPL workspace
MOV  R1,R1             negative?
CI   R1,4              no..too high?
LI   FPSTK,FPSTK0      initialize FP stack (grows up)
LI   RSTK,RSTK0        initialize return stack (grows down)
SLA  R1,1              make opcode an index
MOV  @PRNGTB(R1),R1    get branch address of opcode
B    *R1               do it

*=======================================================================*
*==~~~~~~~~~~~~~~~~~~~~~~~~ SEEDIN Opcode = 3 ~~~~~~~~~~~~~~~~~~~~~~~~==*
*=======================================================================*
*== SEEDIN is functionally equivalent to XB's GPL INTRND. It is       ==*
*== called from GPL workspace(>83E0) by PRNG. It initializes the FP   ==*
*== PRNG seed by copying X1SEED and X2SEED to RNDX1 and RNDX2. SEEDIN ==*
*== should be run at system startup.                                  ==*
*=======================================================================*
*
BL   @FPMOVE        copy startup seed X2
BL   @FPMOVE        copy startup seed X1
* Fall through to PRNGEX to clean up and return to caller...

*******************************************************************
*
PRNGEX LI   FPSTK,>9800       restore R13
LI   RSTK,>8C02        restore R15
LWPI PRNGWS
RTWP

*************************************************************************
*                   RANDOMIZE Statement (2 subroutines)
*************************************************************************
*
*=======================================================================*
*==~~~~~~~~~~~~~~~~~~~~~~~~ RNZNEW Opcode = 1 ~~~~~~~~~~~~~~~~~~~~~~~~==*
*=======================================================================*
*== RNZNEW is functionally equivalent to XB's GPL NRNDMZ when called  ==*
*== from XB with "RANDOMIZE <seed>". RNZNEW is called from GPL work-  ==*
*== space (>83E0) by PRNG and expects the new seed in FAC. XB did not ==*
*== check for negative numbers, but RNZNEW forces them positive.      ==*
*== Place value is ignored in the input number by overstoring the     ==*
*== sign+exponent byte with >46.                                      ==*
*== TI comment: 99,000,000,000,001 possible starting positions.       ==*
*== [LES note: Actually, 100^7 = 100,000,000,000,000 are possible,    ==*
*== but who's counting?!]                                             ==*
*=======================================================================*
*
RNZNEW MOV  @FAC,R0           FAC = 0?
JEQ  GA3B6             yes..we're done
JGT  RNZNW1            > 0..get on with it
NEG  @FAC              first make it positive
RNZNW1 LI   R0,>4600          exponent for integer only
MOVB R0,@FAC           0 < FAC < 1E14
BL   @SPUSH            Let FAC = X2*1E7+X1           S:x2*1e7+x1
BL   @TOARG                ARG = 1/1E7
DATA RNDEM
BL   @FMUL                 FAC = X2+X1/1E7
BL   @INTPOS               FAC = X2
BL   @FRMFAC               FAC = X2
DATA RNDX2
BL   @TOARG                ARG = 1E7
DATA RNDEP
BL   @FMUL                 FAC = X2*1E7              S:x2*1e7+x1
BL   @SSUB              X1=FAC = X2*1E7+X1-X2*1E7    S:
BL   @FRMFAC               FAC = X1
DATA RNDX1

GA3B6  MOV  @FAC,@RNDX2           FAC = 0
MOV  @FAC,@RNDX1           FAC = 0

*=======================================================================*
*==~~~~~~~~~~~~~~~~~~~~~~~~ RNZOLD Opcode = 2 ~~~~~~~~~~~~~~~~~~~~~~~~==*
*=======================================================================*
*== RNZOLD is functionally equivalent to XB's GPL NRNDMZ when called  ==*
*== from XB with RANDOMIZE. RNZOLD is called from GPL workspace       ==*
*== (>83E0) by PRNG. RNZOLD will calculate a new seed from the old    ==*
*== 16-bit seed at >83C0 via 4 calls to RAND with "DATA 99". >83C0 is ==*
*== actually different for every program start. RNZOLD calls RAND     ==*
*== with "DATA 99" twice each for RNDX1 and RNDX2.                    ==*
*== TI comment-->(16K possible starting positions)<--NOPE             ==*
*== TI probably meant "16M" because 64^4 = 16MiB, however, that is    ==*
*== also wrong:                                                       ==*
*==   ACTUALLY-->(25^4 = 390,625 possible positions)<--ACTUALLY       ==*
*== If TI had not added the "SRL R0,2" for each call to "RAND 99", it ==*
*== would have been 100^4 = 100,000,000.                              ==*
*=======================================================================*
*
RANDOM EQU  >8378

RNZOLD LI   R0,>4201
MOV  R0,@FAC     FAC = >4201 (sign+exponent+1st digit fixed at 1)
CLR  @FAC+4      FAC+4..
CLR  @FAC+6      ..FAC+7 = >00000000
BL   @RNDMZ      Set up new seed..
DATA RNDX1       ..for X1
BL   @RNDMZ      Set up new seed..
DATA RNDX2       ..for X2
*                       ..Xnew = 100000010000..124240012424

* Get next 2 pseudorandom centimal digits
*
RNDMZ  DECT RSTK        reserve return stack space
MOV  *R11+,R1    Get address of seed-half destination for FPMOVE
MOV  R11,*RSTK   save new return
BL   @RAND       perform GPL RAND equivalent..
DATA 99          ..to return 1 centimal digit (2 decimal)
* XB comments say >00<=R0<=FF <--WRONG!! ACTUALLY--> 0<=R0<=99[63h]
SRL  R0,2        [LES Note: To be removed in future use]
* XB comments say >00<=R0<=3F <--WRONG!! ACTUALLY--> 0<=R0<=24[18h]
* [LES Note: The SRLs above and below, limiting the returned centimal
*   digit to 24, were added by someone who apparently did not under-
*   stand what "RAND 99" was actually doing! The XB comments imply
*   the TI programmers thought 99 was a seed for RAND, rather than
*   the limit for the returned byte that it is!]
MOVB R0,@FAC+2   0<=FAC+2<=24[18h]!!
BL   @RAND       perform GPL RAND equivalent..
DATA 99          ..to return 1 centimal digit (2 decimal)
* XB comments say >00<=R0<=FF <--WRONG!! ACTUALLY--> 0<=R0<=99[63h]
SRL  R0,2        [LES Note: To be removed in future use]
* XB comments say >00<=R0<=3F <--WRONG!! ACTUALLY--> 0<=R0<=24[18h]
MOVB R0,@FAC+3   0<=FAC+3<=24[18h]!!
LI   R0,FAC      source
BL   @FPMOVE     Put in seed half..max: >4201181800 = 12424
MOV  *RSTK+,R11  pop return

*-----------------------------------------------------------------
*-- RAND is functionally equivalent to Console ROM 0's ALC for GPL
*-- RAND, except that input is via *R11+ instead of *R13 and output
*-- is MSB of R0, rather than the byte at RANDOM (>8378).
*-----------------------------------------------------------------
*------ Generate A Pseudorandom, Centimal Number in R0 MSB -------
*-----------------------------------------------------------------
WKSC   EQU  >83C0             ISR workspace (R0 = PRN SEED)
WKSE   EQU  >83E0             GPL workspace
R5LSB  EQU  WKSE+R5+R5+1      low byte of R5 of GPL WS

RAND   LI   R4,>6FE5          28645 to R4
MPY  @WKSC,R4          SEED * 28645
AI   R5,>7AB9          PRN = SEED * 28645 + 31417
MOV  R5,@WKSC          PRN is stored as new SEED in >83C0
MOV  *R11+,R6          get maximum byte-value to be returned
SB   R6,R6             clear MSB, insuring R6 < 256
INC  R6                make it a modulus (max+1)
CLR  R4                clear upper half of dividend
SWPB R5                swap bytes of PRN to get PRN'
DIV  R6,R4             perform division by modulus (lim+1)
MOVB @R5LSB,R0         LSB of remainder to MSB of R0
*++
*++ In the future, the SWPB may be removed in favor of adding
*++ "SRC R5,5" just before storing in WKSC per TI Forth.

RT      Return random centimal digit to caller

*=======================================================================*
*==~~~~~~~~~~~~~~~~~~~~~~~~ RAND99 Opcode = 4 ~~~~~~~~~~~~~~~~~~~~~~~~==*
*=======================================================================*
*== RAND99 is called from GPL workspace (>83E0) by PRNG to run RAND   ==*
*== with "DATA 99" to return a random centimal digit in FAC.          ==*
*=======================================================================*
*
RAND99 BL   @RAND       perform GPL RAND equivalent..
DATA 99          ..to return 1 centimal digit (2 decimal)
SRL  R0,8        MSB to LSB, zeroing MSB
MOV  R0,@FAC     return value in FAC

END```

##### Share on other sites

Is this any faster? Or slower? Or just what it does?

##### Share on other sites

52 minutes ago, RXB said:

Is this any faster? Or slower? Or just what it does?

..than what? previous code? XB?

If you mean the code in my earlier post (my 3rd post back), it is virtually identical in speed. I mostly just moved some code around to make it easier to compare to XB’s GPL.

If you mean XB’s RND, it is 3.3 times faster.

...lee

##### Share on other sites

Ignoring the speed, is the XB random number generator "better" than the standard BASIC generator?

Are the numbers more random?

Since both give numbers between 0 and .99999..., could the XB random numbers have more numbers to the right of the decimal point?

##### Share on other sites

I have added to the first post my explanation of how XB’s RANDOMIZE and RANDOMIMZE <seed> work.

...lee

##### Share on other sites

6 hours ago, senior_falcon said:

Ignoring the speed, is the XB random number generator "better" than the standard BASIC generator?

That is difficult to say without testing for randomness, which I have not done. XB does the LCG thing with full 14-decimal digit integers, whereas TIB does that with only 2 decimal digits at a time for at least 7 times—once for each pair of the 14 decimal digits. They both produce up to 14 decimal digits of precision in the returned number. XB uses the lower 14 digits of the LCG, whereas TIB swaps the bytes of the new seed before it returns each 2 “random” decimal digits. TIB’s LCG is 16 bits wide (run 7 times), whereas XB’s LCG is 47 bits wide (run once).

6 hours ago, senior_falcon said:

Are the numbers more random?

Potentially, but needs testing.

6 hours ago, senior_falcon said:

Since both give numbers between 0 and .99999..., could the XB random numbers have more numbers to the right of the decimal point?

No. Each produce up to 14 decimal digits of precision—see above comments for details.

...lee

## Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

×   Pasted as rich text.   Paste as plain text instead

Only 75 emoji are allowed.