home *** CD-ROM | disk | FTP | other *** search
/ Simtel MSDOS 1992 September / Simtel20_Sept92.cdr / msdos / turbo_c / tcrnd11.arc / RND.ASM < prev    next >
Assembly Source File  |  1989-03-10  |  15KB  |  535 lines

  1. page 60,132
  2.  
  3. ; Copyright (c) 1987, 1988 by Mark Adler  Pasadena, CA
  4. ; This program may be used and freely copied by anyone except that it
  5. ; may not be sold by itself or as part of any package without the
  6. ; permission of the author.
  7.  
  8. ; RND.ASM by Mark Adler  7 Dec 1987
  9. ; updated by Mark Adler  6 Feb 1988  to change randomize() to rndmize()
  10. ;                                    to avoid conflict with Turbo C's
  11. ;                                    randomize() macro.
  12.  
  13. ; Assemble using Microsoft Macro Assembler 5.0 or later, or Borland
  14. ; TASM 1.0 or later.  For TASM, use the /w-pdc option to turn off
  15. ; the pass dependent construction warning.  Specify the memory model as
  16. ; follows:
  17. ;
  18. ; C>masm/mx/d__compact__ rnd;
  19. ;
  20. ; or
  21. ;
  22. ; C>tasm/mx/w-pdc/d__compact__ rnd
  23. ;
  24. ; where "compact" can be replaced by tiny, small, medium, large, or
  25. ; huge.  Note that two underscores precede "compact", and two
  26. ; underscores follow it.  The file 'tc.asi' must be accessible.  It may
  27. ; be convenient to add the resulting RND.OBJ file to the C library.
  28. ; For example:
  29. ;
  30. ; C>tasm/mx/w-pdc/d__compact__ rnd;
  31. ; C>tlib/e cc +rnd
  32.  
  33.  title Random Number Generators for Turbo C
  34.  
  35.  include tc.asi
  36.  
  37. comment #
  38.  
  39. by Mark Adler --- algorithms shamelessly copied from Knuth's "Art
  40.                   of Computer Programming", vol 2, 2nd ed.
  41.  
  42. This module provides two different random number generators, both
  43. augmented by shuffling.  The two methods are linear conguential and
  44. additive generation.  Both run at approximately the same speed.  The
  45. reason for having two is to allow switching between them to check the
  46. sensitivity of the simulation to the random number generation method.
  47. The routines are:
  48.  
  49. void setseed(s)  - Set the seed to the 32 bit value 's' and initialize
  50. long s;            the tables for the additive generator and shuffling.
  51.                    Also sets the method to linear congruential.
  52.  
  53. long seed()      - Return the current linear congruential seed.
  54.  
  55. long ticks()     - Return the current tick count (IBM PC compatibles).
  56.  
  57. long rndmize()   - Same as setseed(ticks()) and returns the seed used.
  58.  
  59. void rndpick(f)  - Select the random number generator method to be used.
  60. int f;             f=0 picks linear congruential, f=1 picks additive.
  61.  
  62. long lrnd()      - Return a 32 bit random number.
  63.  
  64. int rnd(n)       - Return a random number between 0 and n, inclusive.
  65. int n;             (n is a 16 bit integer greater than 0.)
  66.  
  67. double drnd()    - Return a random floating point number in [0..1].
  68.  
  69. void shuffle(a, n) - Shuffle the (16 bit) integer array 'a' with 'n'
  70. int a[], n;          entries.  (Unrelated to the shuffling used in the
  71.                      random number generation.)
  72.  
  73. The expected use is to call either rndmize() or setseed() to set the
  74. seed and initialize the tables, then call rndpick() to select the
  75. generation method, and then any combination of lrnd()'s, rnd()'s,
  76. drnd()'s, and shuffle()'s.  rndpick() should not be used in the middle
  77. of the simulation to try to get a "more random" sequence.  The same
  78. method should be used throughout, but the method can be switched between
  79. runs and the results compared to see if they have any dependence on the
  80. random number generators themselves.
  81.  
  82. The drnd() routine assumes the presence of an 80X87, but does not check
  83. for it.
  84.  
  85. These routines should be easily customizable for use with other
  86. compilers.
  87.  
  88. #
  89.  
  90.  .8087
  91.  
  92.  
  93. @header
  94.  
  95.  
  96. @dseg
  97.  even
  98. rmul dd 1664525         ;Good multiplier (Knuth Vol. 2, 2nd Ed,
  99.                         ;                 pg. 102, line 26).
  100. scale dw -31            ;Scale to make fraction out of long.
  101. @endd
  102.  
  103.  
  104. @bseg
  105.  even
  106.         ; data for linear conguential method with shuffling.
  107. rseed dw ?,?            ;32 bit random number seed.
  108. rvals dw 128 dup(?)     ;Room for 64 old seeds.
  109.         ; (note: the rvals and radd tables must be adjacent for setup.)
  110.         ; data for additive generator method.
  111. radd dw 110 dup(?)      ;Room for 55 integers for additive sequence.
  112. raj dw ?                ;j and k for additive generator.
  113. rak dw ?
  114.         ; flag for method choice.
  115. rtyp db ?
  116.  
  117. @endb
  118.  
  119.  
  120. @cseg
  121.  
  122. shfval macro
  123.  ;;
  124.  ;; shfval takes the 32 bit random number in DX:AX and uses part of it
  125.  ;; to pick a previous random value from a set of 64, swaps the new one
  126.  ;; with the old one and returns the old one in DX:AX.  This should make
  127.  ;; a random sequence "randomer" (Knuth Vol.2, 2nd Ed., pg 32, algorithm
  128.  ;; B.)  This macro uses the registers AX, BX, and DX.
  129.  ;;
  130.   mov BL,DH
  131.   and BX,0FCh                   ;;Pick off high 6 bits of DX:AX.
  132.   xchg AX,dgroup:rvals[BX]      ;;Exchange new seed with old one.
  133.   xchg DX,dgroup:rvals[BX+2]
  134.  ;;
  135. endm
  136.  
  137.  
  138. lincon macro
  139.  ;;
  140.  ;; lincon uses the linear congruential method to generate a 32 bit
  141.  ;; random number.  The number is left in DX:AX and updates rseed.
  142.  ;; This macro assumes the direction flag is cleared and that ES and DS
  143.  ;; are equal.  It uses the registers AX, BX, CX, DX, SI, and DI.
  144.  ;;
  145.         ;; multiply seed by multiplier modulo 2^32.
  146.   mov SI,offset dgroup:rmul     ;;Point SI to multiplier.
  147.   mov DI,offset dgroup:rseed    ;;Point DI to seed.
  148.   lodsw                 ;;Get low word of multiplier.
  149.   mov BX,AX             ;;Save that.
  150.   mul word ptr [DI+2]   ;;Multiply by high word of seed.
  151.   mov CX,AX             ;;Save low word of that for high word of result.
  152.   lodsw                 ;;Get high word of multiplier.
  153.   mul word ptr [DI]     ;;Multiply by low word of seed.
  154.   add CX,AX             ;;Add low word into high word of result.
  155.   xchg AX,BX            ;;Get low word of multiplier.
  156.   mul word ptr [DI]     ;;Multiply by low word of seed.
  157.   add DX,CX             ;;Add other high word components.
  158.         ;; add constant modulo 2^32.
  159.   add AX,1              ;;Constant = 1 (relatively prime to
  160.   adc DX,0              ;; everything).
  161.         ;; store seed back.
  162.   stosw                 ;;Store low word.
  163.   mov [DI],DX           ;;Store high word.
  164.  ;;
  165. endm
  166.  
  167.  
  168. addgen macro
  169.  local jcyc, kcyc, done
  170.  ;;
  171.  ;; addgen uses the additive generator of Mitchell and Moore (Knuth
  172.  ;; Vol.2, 2nd Ed., pg. 27.) to generate a 32 but random number.  The
  173.  ;; number is left in DX:AX.  This macro uses the registers AX, BX, DX,
  174.  ;; SI, and DI.
  175.  ;;
  176.         ;; get random 32 bit number in DX:AX.
  177.   mov SI,dgroup:raj     ;;Get j.
  178.   mov DI,dgroup:rak     ;;Get k.
  179.   mov BX,offset dgroup:radd     ;;Point to circular list of 55 integers.
  180.   mov AX,[BX+SI]        ;; r = (radd[k] += radd[j]);
  181.   add AX,[BX+DI]
  182.   mov [BX+DI],AX
  183.   inc BX
  184.   inc BX
  185.   mov DX,[BX+SI]
  186.   adc DX,[BX+DI]
  187.   mov [BX+DI],DX
  188.   sub SI,4
  189.   jb jcyc               ;; j = j ? j - 1 : 54;
  190.   sub DI,4
  191.   jb kcyc               ;; k = k ? k - 1 : 54;
  192.   mov dgroup:raj,SI     ;;Update j and k.
  193.   mov dgroup:rak,DI
  194.   jmp short done
  195.         ;; j cycles up to top.
  196.  jcyc:
  197.    sub DI,4             ;;If j bad, then k is OK.
  198.    mov dgroup:raj,54*4  ;;Cycle j.
  199.    mov dgroup:rak,DI    ;;Set k.
  200.    jmp short done
  201.         ;; k cycles up to top.
  202.  kcyc:
  203.    mov dgroup:raj,SI    ;;If k bad, then j is OK.
  204.    mov dgroup:rak,54*4  ;;Cycle k.
  205.         ;; now everything is updated properly.
  206.  done:
  207.  ;;
  208. endm
  209.  
  210.  
  211. choice macro
  212.  local lin,shf
  213.  ;;
  214.  ;; Use either the linear congruential or the additive generator
  215.  ;; depending on the value of rtyp.  Follow with a shuffle.
  216.  ;;
  217.   cmp dgroup:rtyp,0     ;;See if zero.
  218.   je lin                ;;If so, use linear congruential.
  219.    addgen               ;;Else, use additive generator.
  220.    jmp short shf
  221.  lin:
  222.    lincon               ;;Use linear congruential.
  223.  shf:
  224.   shfval                ;;Shuffle with old values.
  225.  ;;
  226. endm
  227.  
  228.  
  229. zerton macro n
  230.  local ismax
  231.  ;;
  232.  ;; zerton takes a random 32 bit number in DX:AX and makes a random
  233.  ;; number in the range 0..n (inclusive).  n may be anything allowed as
  234.  ;; the source of 'mov CX,'.  The result is left in AX and n+1 is left
  235.  ;; in CX.  This macro uses the registers AX, BX, CX, and DX.
  236.  ;;
  237.         ;; get random number in [0..n] (as per Knuth's recommendation).
  238.   ifdifi <n>,<cx>
  239.    mov CX,n             ;;Get n.
  240.   endif
  241.   inc CX                ;;Use n+1 to multiply "fraction" in [0..1).
  242.   jz ismax              ;;If n is 65535, then return high part of seed.
  243.    mov BX,DX            ;;Save high part of seed.
  244.    mul CX               ;;Multiply low part of fraction by n+1.
  245.    xchg AX,BX           ;;Get high part of fraction.
  246.    mov BX,DX            ;;Save high part of product.
  247.    mul CX               ;;Multiply high part of fraction by n+1.
  248.    add AX,BX            ;;Add component from low part of fraction.
  249.    adc DX,0
  250.  ismax:
  251.   xchg AX,DX            ;;Truncate to integer.
  252.  ;;
  253. endm
  254.  
  255.  
  256. @proc shuffle
  257.  a equ [BP+@a]
  258.  n equ [BP+@a+@d]
  259.  ;
  260.  ; void shuffle(a, n)
  261.  ; int a[], n;
  262.  ;
  263.  ; Shuffle n items (algorithm from Knuth).  n must be greater than zero
  264.  ; and 'int a[n]' must fit in the segment.
  265.  ;
  266.   @enter
  267.   push SI
  268.   push DI
  269.         ; set up to use string instructions (for lincon).
  270.   mov AX,DS             ;Get DS.
  271.   mov ES,AX             ;Set ES to same as DS.
  272.   cld                   ;Autoincrement.
  273.         ; do shuffle.
  274.   mov CX,n              ; get n, j = n - 1.
  275.  shflp:
  276.    loop swp             ;Do n-1 swaps.
  277.    jmp shfend
  278.  swp:
  279.    push CX              ;Save j.
  280.    choice               ;Make 32 bit random number.
  281.    pop CX               ;Restore j.
  282.    zerton CX            ;k = random number in 0..j.
  283.    dec CX               ;Restore j.
  284.    cmp AX,CX            ;See if swap needed (k != j).
  285.    je shno              ;If not, skip swap.
  286.     mov SI,AX           ;Put offsets in SI, DI.
  287.     shl SI,1
  288.     mov DI,CX
  289.     shl DI,1
  290.     if __LDATA__
  291.      lds BX,a           ;Point to a[].
  292.     else
  293.      mov BX,a           ;Point to a[].
  294.     endif
  295.     mov AX,[BX+SI]      ;Get a[k].
  296.     xchg AX,[BX+DI]     ;Exchange with a[j].
  297.     mov [BX+SI],AX      ;Set new a[k].
  298.     if __LDATA__
  299.      mov AX,ES          ;Restore DS.
  300.      mov DS,AX
  301.     endif
  302.   shno:
  303.    jmp shflp
  304.         ; done.
  305.  shfend:
  306.   pop DI
  307.   pop SI
  308.   @leave
  309.  ;
  310. @endp shuffle
  311.  
  312.  
  313. @proc lrnd
  314.  ;
  315.  ; long lrnd()
  316.  ;
  317.  ; Return random 32 bit number using linear congruential method.
  318.  ;
  319.   @enter
  320.   push SI
  321.   push DI
  322.         ; set up to use string instructions (for lincon).
  323.   mov AX,DS             ;Set ES to same as DS.
  324.   mov ES,AX
  325.   cld                   ;Autoincrement.
  326.         ; get a new random number in DX:AX.
  327.   choice                ;Make 32 bit random number.
  328.         ; return seed.
  329.   pop DI
  330.   pop SI
  331.   @leave
  332.  ;
  333. @endp lrnd
  334.  
  335.  
  336. @proc rnd
  337.  n equ [BP+@a]
  338.  ;
  339.  ; int rnd(n)
  340.  ; unsigned n;
  341.  ;
  342.  ; Return random number in [0..n] using linear congruential method.
  343.  ;
  344.   @enter
  345.   push SI
  346.   push DI
  347.         ; set up to use string instructions (for lincon).
  348.   mov AX,DS             ;Set ES to same as DS.
  349.   mov ES,AX
  350.   cld                   ;Autoincrement.
  351.         ; get a new random number in DX:AX.
  352.   choice                ;Make 32 bit random number.
  353.         ; convert to random number in 0..n.
  354.   zerton n
  355.         ; return it.
  356.   pop DI
  357.   pop SI
  358.   @leave
  359.  ;
  360. @endp rnd
  361.  
  362.  
  363. @proc drnd
  364.  ;
  365.  ; double drnd()
  366.  ;
  367.  ; Return random number in [0..1].  This routine assumes a numeric
  368.  ; coprocessor is present, but does not check for it.  The fraction
  369.  ; generated has 31 random bits plus the possibility of 1.  The values
  370.  ; of 0 and 1 have half the probability of any other fraction, as it
  371.  ; should be.  The floating point value is left on the numeric processor
  372.  ; stack.  Two levels of stack are used.
  373.  ;
  374.   @enter
  375.   push SI
  376.   push DI
  377.         ; set up to use string instructions (for lincon).
  378.   mov AX,DS             ;Set ES to same as DS.
  379.   mov ES,AX
  380.   cld                   ;Autoincrement.
  381.         ; do NP operation concurrently with random number generation.
  382.   fild dgroup:scale     ;Get scale to make fraction.
  383.         ; get a new random number in DX:AX.
  384.   choice                ;Make 32 bit random number.
  385.         ; make seed into floating point, leave on NP stack.
  386.   push DX               ;Put in memory.
  387.   push AX
  388.   fild dword ptr [BP-8] ;Load seed into stack.
  389.   fabs                  ;Take absolute value.
  390.   fscale                ;Scale to fraction.
  391.   fstp st(1)            ;Pull scale out of stack.
  392.   add SP,4              ;Trash 32 bit value.
  393.         ; done.
  394.   pop DI
  395.   pop SI
  396.   @leave
  397.  ;
  398. @endp drnd
  399.  
  400.  
  401. @proc seed
  402.  ;
  403.  ; long seed()
  404.  ;
  405.  ; Return current seed.
  406.  ;
  407.   @enter
  408.   mov AX,dgroup:rseed   ;Get low word.
  409.   mov DX,dgroup:rseed+2 ;Get high word.
  410.   @leave
  411.  ;
  412. @endp seed
  413.  
  414.  
  415. @proc ticks
  416.  ;
  417.  ; long ticks()
  418.  ;
  419.  ; Get tick count (assumes IBM PC like).
  420.  ;
  421.   @enter
  422.   mov AH,0              ;Read ticks.
  423.   int 1Ah               ;Time of day interrupt.
  424.   mov AX,DX             ;Get low word in AX.
  425.   mov DX,CX             ;Get high word in CX.
  426.   @leave
  427.  ;
  428. @endp ticks
  429.  
  430.  
  431. setup proc near
  432.  ;
  433.  ; Setup radd, raj, and rak for addgen and rvals for shfval.  See Knuth
  434.  ; for why 23 and 54 are the desired values for j and k.
  435.  ; Note: one of the requirements is that not all of the integers in
  436.  ; radd[] can be even.  This is satisfied since lincon returns
  437.  ; alternating even and odd numbers.
  438.  ;
  439.   push SI
  440.   push DI
  441.         ; set up to use string instructions (for lincon).
  442.   mov AX,DS             ;Set ES to same as DS.
  443.   mov ES,AX
  444.   cld                   ;Autoincrement.
  445.         ; fill rvals[] and radd[] tables.
  446.   mov DI,offset dgroup:rvals
  447.   mov CX,64+55          ;Set rvals[0..63] and radd[0..54] (longs).
  448.  fill:
  449.    push DI              ;Save registers.
  450.    push CX
  451.    lincon               ;Get 32 bit random number in DX:AX.
  452.    pop CX               ;Restore registers.
  453.    pop DI
  454.    stosw                ;Store number.
  455.    xchg AX,DX
  456.    stosw
  457.    loop fill
  458.         ; set j and k.
  459.   mov dgroup:raj,23*4   ; j = 23;
  460.   mov dgroup:rak,54*4   ; k = 54;
  461.         ; set rtyp to use linear congruential.
  462.   mov dgroup:rtyp,0
  463.         ; done.
  464.   pop DI
  465.   pop SI
  466.   ret
  467.  ;
  468. setup endp
  469.  
  470.  
  471. @proc rndmize
  472.  ;
  473.  ; long rndmize()
  474.  ;
  475.  ; Randomize seed and set up for addgen.  This sets the seed to the
  476.  ; current tick count, generates random numbers for addgen and shfval,
  477.  ; sets j and k for addgen, and returns the tick count used.  The
  478.  ; returned tick count could be saved and used as the argument for
  479.  ; setseed() to exactly repeat the pseudo-random sequence.
  480.  ;
  481.   @enter
  482.   call _ticks
  483.   mov dgroup:rseed,AX   ;Set low word.
  484.   mov dgroup:rseed+2,DX ;Set high word.
  485.   push DX               ;Save seed.
  486.   push AX
  487.   call setup            ;Setup tables.
  488.   pop AX                ;Return seed.
  489.   pop DX
  490.   @leave
  491.  ;
  492. @endp rndmize
  493.  
  494.  
  495. @proc setseed
  496.  s equ [BP+@a]
  497.  ;
  498.  ; void setseed(s)
  499.  ; long s;
  500.  ;
  501.  ; Set seed to argument.
  502.  ;
  503.   @enter
  504.   mov AX,s              ;Get low word.
  505.   mov dgroup:rseed,AX   ;Set low word.
  506.   mov AX,s+2            ;Get high word.
  507.   mov dgroup:rseed+2,AX ;Set high word.
  508.   call setup            ;Set up tables.
  509.   @leave
  510.  ;
  511. @endp setseed
  512.  
  513.  
  514. @proc rndpick
  515.  f equ [BP+@a]
  516.  ;
  517.  ; void rndpick(f)
  518.  ; int f;
  519.  ;
  520.  ; Set the method to be used for random number generation.  f=0 selects
  521.  ; linear conguential (the default after rndmize() or setseed() called)
  522.  ; and f=1 selects additive generator.
  523.  ;
  524.   @enter
  525.   mov AL,f
  526.   mov dgroup:rtyp,AL
  527.   @leave
  528.  ;
  529. @endp rndpick
  530.  
  531.  
  532. @endc
  533.  
  534.  end
  535.