Announcement

Collapse

Forum Guidelines

This forum is for finished source code that is working properly. If you have questions about this or any other source code, please post it in one of the Discussion Forums, not here.
See more
See less

A Swiss Army random number generator

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • A Swiss Army random number generator

    'RandINT.BAS -- A Swiss Army linear congruential pseudo-random number generator

    ' by E.W. Menzel Jr. March 2005.
    ' THIS IS FREEWARE
    ' Language: PowerBASIC PBCC
    ' Purpose: Provide a choice of random number generators -- all with known, open-source,
    ' well-tested algorithms, & some with much longer periods than PB's built-in RND. You can
    ' even switch your algorithms on the fly, should you care to do so.

    ' NOTES:
    ' 1. To "RANDOMIZE" simply set Rand.Seed = any positive integer between 1 and Rand.M -2.
    ' PB's RANDOMIZE and RND are not, however, used here.

    ' 2. For the theory behind these and other generators see:
    ' S.K. Park & K.W. Miller, "Random number generators: Good ones are hard to find",
    ' Communications of the ACM, Oct 1988, vol 31, 1192-1201.
    ' Donald Knuth, "The art of computer programming", 1998, vol 2, Chapter 3, 3rd edition.

    ' 3. Knuth lists many good (and a few "infamous") pairs of constants, together with their
    ' performances on tests of randomness. Here I pick four good pairs. If you need more than
    ' that, it will be easy to add them.

    Code:
    #COMPILE EXE
    #DIM ALL
    
    DECLARE FUNCTION RandInt() AS QUAD
    DECLARE FUNCTION RNG() AS EXT
    DECLARE FUNCTION RandPeriod() AS QUAD
    DECLARE FUNCTION RandPick(whichOne AS LONG, seed AS QUAD) AS QUAD
    
    'seed & constants for linear congruential random number generators
    TYPE RandType
        seed AS QUAD
        A    AS QUAD     'constant a
        M    AS QUAD     'constant m
    END TYPE
    
    GLOBAL Rand AS RandType
    
    'Function RandInt (plus the Type definition above) might be all you
    'really need; the rest is window-dressing. It returns a 10-digit integer.
    'Note that if the generator has not been defined, default values are used.
    FUNCTION RandINT AS QUAD
        DIM Rand AS GLOBAL RandType
        IF Rand.M < 1&& THEN Rand.A=16807&&: Rand.M=(2^31)-1&&
        IF Rand.seed < 1&& THEN Rand.seed=TIMER
        Rand.seed = (Rand.seed * Rand.A) MOD Rand.M
        FUNCTION = Rand.seed
    END FUNCTION
    
    'RNG is an equivalent of PB's RND,
    'in yielding uniformly distributed numbers ranging from 0 to 1,
    'with theoretical exact mean=0.5 and var=(1/12) or 0.08333...
    FUNCTION RNG AS EXT
        FUNCTION = RandINT / Rand.M
    END FUNCTION
    
    'which generator & starting seed number do you want?
    FUNCTION RandPick(whichOne AS LONG, seed AS QUAD) AS QUAD
        DIM A AS STRING, N AS LONG
        N =4 'however many generators are listed below
    
        IF seed <1 OR whichOne<1 OR whichOne>N THEN
            CLS
            LOCATE 18,1
        END IF
        IF WhichOne >N OR WhichOne <1 THEN
            'Rand. is a GLOBAL TYPE variable. Function RandInt DIM's it and will screen it.
            PRINT "Which random number generator do you want?"
            PRINT "(1)=Lavaux&Jannsens (2)=Cray X-MP (3) Marsaglia (4)Park&Miller "
            PRINT "[default = ";N;
            LINE INPUT "] ";A$
            WhichOne=VAL(A$)
        END IF
        SELECT CASE WhichOne
           CASE 1: Rand.A=31167285&&: Rand.M=2^48     'see Knuth, 1998, p.106, line 23
           CASE 2: Rand.A=44485709377909: Rand.M=2^46 'see Knuth, 1998, p.106, line 22
           CASE 3: Rand.A=69069: Rand.M=2^32          'see Knuth, 1998, p.106, line 22; G.Marsaglia
           CASE ELSE: Rand.A=16807&&: Rand.M=(2^31)-1 'see Knuth, 1998, p. 106, line 19; Park-Miller
        END SELECT
    
        IF seed THEN
           Rand.seed=seed
        ELSE
           LINE INPUT "Enter a seed number (0 or neg. = TIMER) ";A$
           Rand.seed = VAL(a$)
        END IF
        IF Rand.seed < 1 THEN Rand.seed=INT(TIMER)
        FUNCTION=Rand.seed
    
    END FUNCTION
    
    'Note: the period for PB's RND is 2^32 or over 4 billion, and
    'the period for the generator using Rand.A=31167285&& & Rand.M=2^48
    'is 2^48. Don't expect Function RandPeriod to reach the latter number
    'any time soon. (I quit after a day or so, and count of a mere 350 billion.)
    FUNCTION RandPeriod AS QUAD
        DIM X(1 TO 5) AS QUAD, N AS QUAD, Hit AS LONG, UB AS LONG
        DIM Y AS LONG, I AS LONG, J AS LONG
        UB=UBOUND(X)
        Y=24
        LOCATE Y-1,10: PRINT "Testing for RandINT's period or length of cycle"
        FOR N=1 TO UB
            X(N)=RandInt
        NEXT N
        DO
            FOR I=1 TO 1000000
                INCR N
                IF RandInt=X(1) THEN
                    Hit=1
                    FOR J=2 TO UB
                        IF RandInt=X(J) THEN INCR Hit
                    NEXT J
                    IF Hit >= UB  THEN EXIT DO
                END IF
            NEXT I
            LOCATE Y,10: PRINT N;
         LOOP UNTIL INSTAT
         IF INKEY$<>"" THEN N = -N
         LOCATE Y,10: PRINT N
         PRINT "press any key to continue"
         WAITKEY$
         FUNCTION = N
    END FUNCTION
    
    'demo
    'Note, from RandINT above, that Rand. is a GLOBAL TYPE variable & that Function RandInt DIM's it.
    'You can change Rand.seed, Rand.a, & Rand.m values as you wish -- but don't say I didn't warn you.
    FUNCTION PBMAIN () AS LONG
       DIM I AS LONG, A AS STRING, Period AS QUAD, RandConstM AS QUAD
       DIM freq(9) AS LONG, Y AS LONG
       DIM X AS EXT, sumX AS EXT, sumX2 AS EXT, N AS EXT, mean AS EXT, var AS EXT
    
    
       Y=RandPick(4,666)  'pick generator# & seed.. If you don't, RandINT will use its defaults
       Period=RandPeriod  'Start FUNCTION to determine how many randoms we can draw before the same
                          'sequence repeats. (Note: This could take many days for some generators)
    
       Rand.seed = 1      're-seed & start over
       DO
           PRINT
           FOR I=1 TO 10000
               X= RNG     'same as RandINT / Rand.M
               sumX = sumX+X
               sumX2=sumX2+X*X:
               N=N+1
               A$=FORMAT$(X,".000000000000000000 ")
               IF I>9900 THEN PRINT A$;
               A$=MID$(A$,2,1)    ' #2 is first digit within the fractional number
               Y=VAL(A$)
               INCR freq(Y)
           NEXT
    
           PRINT
           PRINT "frequencies of the digits 0-9"
           FOR I=0 TO UBOUND (freq)
               PRINT freq(I);
           NEXT
           PRINT
           PRINT "current seed num ";Rand.seed
           'If initial seed was 1 and seed #10,001 is not 1043618065, exactly, then
           ' the Park & Miller minimal standard RNG was not implemented
           IF N=10000 AND Rand.seed=1043618065 THEN
               PRINT "Alright already... You used seed=1, a=16807, m=(2^31)-1. "
           END IF
    
           mean=sumX/N: var=(sumX2-(sumX^2/N)) /(N-1)
           A$="mean "+ STR$(mean##,18)+" variance "+STR$(var##,18)+ "   N "+STR$(N)
           PRINT A$
           PRINT "Enter q to quit, any number 1-"; Rand.M-1;
           LINE INPUT " for new seed ";A$
           IF VAL(A$) THEN Rand.seed = VAL(a$)
       LOOP UNTIL UCASE$(A$)="Q"
    END FUNCTION
    ------------------




    [This message has been edited by Emil Menzel (edited March 23, 2005).]

  • #2

    Just ran into: http://www.gnu.org/software/gsl/manual/gsl-ref_17.html

    It makes my program look very elementary. The basic idea is
    more or less the same, but the GNU Scientific Library
    incorporates dozens of different generators -- many of them
    more sophisticated than linear congruential ones.

    If you know C and want to do other PB programmers a real
    service, translate this whole library, or whichever sections
    interest you the most, into PB! The source code is
    available at no cost.


    ------------------

    Comment

    Working...
    X