home bbs files messages ]

Forums before death by AOL, social media and spammers... "We can't have nice things"

   comp.lang.forth      Forth programmers eat a lot of Bratwurst      117,927 messages   

[   << oldest   |   < older   |   list   |   newer >   |   newest >>   ]

   Message 117,592 of 117,927   
   Anton Ertl to All   
   Generating a random sequence of Forth wo   
   30 Sep 25 16:33:50   
   
   From: anton@mips.complang.tuwien.ac.at   
      
   I wanted to generate a random sequence consisting of a number, NOOP,   
   and DROP.  When EVALUATEing the sequence, starting with an empty   
   stack, it should end with an empty stack, and should not underflow the   
   stack.   
      
   I implemented this twice.  The first attempt just generated a random   
   word/number in the normal case, and a specific kind of word/number in   
   the boundary cases:   
      
   : genseq {: u -- :}   
       0 u 0 do   
           case   
               dup 0= ?of 1+ ." 9 " endof   
               dup i + u u>= ?of 1- ." drop " endof   
               dup i + 1+ u u>= ?of ." noop " endof   
               rnd 3 um* nip 1- dup case   
                   -1 of ." drop " endof   
                   0  of ." noop " endof   
                   1  of ." 8 " endof   
               endcase   
               +   
               0 endcase   
       loop   
       drop ;   
      
   When I do 16 GENSEQ with seed 1234, I get the following sequence:   
      
   9 drop 9 8 8 noop drop drop 8 noop drop noop noop 8 drop drop   
      
   The requirements are distributed across several places in this word,   
   which I did not like.  I also did not like the non-randomness of the   
   sequence of DROPs at the end, and the non-randomness of the 9 whenever   
   the stack is empty.  I also found that I needed to add a clause for   
   when the stack depth is one less than the number of remaining words.   
      
   So I thought about other ways to do it.   
      
   Eventually I came up with the following approach: Starts with counters   
   for the different kinds of words/numbers.  Whenever one word/number is   
   generated, its counter is reduced.  At each step the probability of   
   generating a kind of word/number is proportional to the remaining   
   counter for that word/number.  The same-depth requirement is   
   implemented by having the same initial counter for the number and   
   DROP.  The no-underflow requirement is implemented by skipping the   
   output of a DROP if the count of remaining DROPs = the count of the   
   remaining numbers.   
      
   Here's the code:   
      
   create counts 3 cells allot   
      
   : initcounts ( u -- )   
       dup 3 / dup counts ! dup counts 2 th ! 2* - counts cell+ ! ;   
      
   : .counts ( -- )   
       counts 3 cell array>mem mem+do   
           i @ 4 .r   
       loop ;   
      
   : countsth-- ( u -- )   
       -1 counts rot th +! ;   
      
   : select1 ( u -- )   
       \ u is the index of the word   
       dup case   
           0 of ." 9 "    countsth-- endof   
           1 of ." noop " countsth-- endof   
           2 of counts @ counts 2 th @ u< if   
                   ." drop " countsth--   
               else   
                   drop endif   
           endof   
       endcase ;   
      
   : sum ( addr u -- u2 )   
       \ addr u is an array with u elements, u2 is the sum of the elements   
       0 -rot cell array>mem mem+do   
           i @ +   
       loop ;   
      
   : genseq ( u -- )   
       initcounts   
       begin   
           counts 3 sum dup while   
               rnd um* nip ( sum )   
               3 0 u+do ( sum1 )   
                   counts i th @ 2dup u< if   
                       i select1 then   
                   - loop   
               drop   
       repeat   
       drop ;   
      
   The code is much longer, but I like it better.  The requirements are   
   concentrated in two places.   
      
   16 GENSEQ generates   
      
   9 9 noop noop drop noop 9 drop noop drop noop 9 drop 9 noop drop   
      
   for seed 123.  A disadvantage of this approach is that it does not   
   generate a word/number on each iteration through the main loop.  I   
   have thought about how to achieve this, but the result would be quite   
   a bit more complex; it's also only a minor issue.  E.g., for   
   generating 1000 words/numbers with seed 123, the main loop has 1036   
   iterations.   
      
   The second approach takes quite a bit more time; for generating a   
   million words/numbers into a string on a Zen 4, 192_876_892 cycles   
   compared to 146_644_156 cycles.   
      
   The original goal was to produce a sequence where various parts of the   
   text interpreter have branch mispredictions; the less predictable the   
   sequence, the better.  In this respect the second sequence is   
   significantly better.  For a sequence of 1000 words/numbers,   
   text-interpreted 1000 times, the number of mispredictions is:   
      
   728_322 first approach   
   821_813 second approach   
      
   (with 247_125 mispredictions coming from (mainly) Gforth startup).   
      
   - anton   
   --   
   M. Anton Ertl  http://www.complang.tuwien.ac.at/anton/home.html   
   comp.lang.forth FAQs: http://www.complang.tuwien.ac.at/forth/faq/toc.html   
        New standard: https://forth-standard.org/   
   EuroForth 2025 CFP: http://www.euroforth.org/ef25/cfp.html   
   EuroForth 2025 registration: https://euro.theforth.net/   
      
   --- SoupGate-Win32 v1.05   
    * Origin: you cannot sedate... all the things you hate (1:229/2)   

[   << oldest   |   < older   |   list   |   newer >   |   newest >>   ]


(c) 1994,  bbs@darkrealms.ca