For-like Syntax for Rnd Function and Generalized Floor and The Friends.

;===============================================================
; I like for loop. It is only one loop I can write automatically,
; for all other loops I always have to think what happens
; inside, and what is really exit condition. It seems that for
; loop somehow fit well into my intuition, and I observed the same
; on my students.
;
; On the other side, I was never quite comfortable with functions
; for random numbers, I always had to think whether upper limit
; is included or not, and what happens if I extend the segment
; or shift it. Not that it is some higher level mathematics,
; but it was never quite smooth for me.
;
; After quite a lot of programming, only yesterday I came to idea
; to use syntax for rnd resembling lovely "for" loop so I do not
; need to care about borderline cases any more. For example:
;
;   if (for i 0 10 2)    generates      0 2 4 6 8 and 10
;      (rnd   0 10 2)    should return  0, 2, 4, 6, 8 or 10
;  
;   if (for   3  4 0.25) generates      3, 3.25, 3.5, 3.75 and 4
;      (rnd   3  4 0.25) should return  3, 3.25, 3.5, 3.75 or  4
;
; You got the idea. If "step" is 1, then it can be omitted, like in
; many for loops. But what about real numbers - is special syntax
; still required? No, for these - step is simply 0. It
; has some mathematical sense. "For" loop doesn't move if your
; step is 0, but rnd

; First, one cute set of replacements for sub, add etc.
; Usual sings for mathematically operations, followed by "."
; which should remind me on "point" from "floating point."
; It is also easier to visually parse and switch from floating point
; to integer versions and vice versa.

(set '-. sub '+. add '*. mul '/. div)

; Append for symbols.

(set 'symappend
     (lambda()(sym (apply 'append
                          (map string $args)))))
                          
; As said, generalized floor (and when I'm here, ceil and round
; as well). There is no reason number should be ceiled
; to some multiple of 1 and not to multiple of any other number,
; not necessarily power of 10 either. It comes handy in this
; particular case.
                          
(dolist(j (list 'floor 'ceil 'round))
   (set (symappend 'g j)
        (expand (lambda(x step)
                   (if (= step 0)
                      x
                      (*. (j (/. x step)) step)))
                   'j)))
                   
; I'm very happy with the loop above, it demostrate few nice
; features.

(println (gfloor 3.1415926 0.001))      ;3.141
(println (gceil 324.12567 (/. 100 3)))  ;333.3333333
(println (ground 4.1888876 0.0125))     ;4.1875

(set 'rnd
     (lambda(a b step)
        (if (> a b)
            (rnd b a step) ; because of specificity of Newlisp for
            (begin (when (not step) (set 'step 1))
            
                   (if (and (= step 0)
                            (= (random) (random)))  ;[?]
                       b
                       
                       (let ((scale (+. (gfloor (- b a) step) ; [??]
                                        step)))       
                            (+. a (gfloor (*. scale (random))
                                          step))))))))
                                
; Maybe only two details deserve explanation: ? and ??.
;
; [?] Why that strange (= (random) (random))?

; It is the result of distribution of the random real numbers,  
; provided by majority of programming tools, so I guess that in
; Newlisp random number is also chosen from [0,1>, if adjusted for
; offset and scale, from [a,b>.
;
; That means, in ideal case, probability that a is chosen is 0,
; but it is still possible. On the other hand, not only that
; probability that (random)=b is zero, but it is completely
; impossible.
;
; Since I want that right edge of the segment has equal chances
; as a left border, I gave it one extra chance. If function generates
; two equal random numbers - I'll count it as 1. If it doesn't,
; radnom number is determined in [0, 1>. That is the idea.
;
; [??] Why formula for scale is that complicated?
;
; Because "for i:=0 to 5 step 2" is legal, and managing that case
; complicates things a bit compared to only "for i:=0 to 4 step 2."

 
; TEST

(seed (date-value))

(dolist(x (list '(rnd 0 5 2)
                '(rnd 3 4 0.25)
                '(rnd 1 4)
                '(rnd -6.666 -5.555 0)
                '(rnd +2 -2 0.01)))
    (println)
    (dotimes(i 6)
       (println x " = " (eval x))))
       
(exit)

After exit, I do not need ";" for comments any more.

RESULTS:

(rnd 0 5 2) = 0
(rnd 0 5 2) = 2
(rnd 0 5 2) = 2
(rnd 0 5 2) = 4
(rnd 0 5 2) = 0
(rnd 0 5 2) = 2

(rnd 3 4 0.25) = 3.25
(rnd 3 4 0.25) = 3.75
(rnd 3 4 0.25) = 3.5
(rnd 3 4 0.25) = 3.5
(rnd 3 4 0.25) = 3.25
(rnd 3 4 0.25) = 4

(rnd 1 4) = 1
(rnd 1 4) = 1
(rnd 1 4) = 4
(rnd 1 4) = 1
(rnd 1 4) = 3
(rnd 1 4) = 1

(rnd -6.666 -5.555 0) = -5.844655354
(rnd -6.666 -5.555 0) = -5.862020386
(rnd -6.666 -5.555 0) = -6.024592486
(rnd -6.666 -5.555 0) = -6.231477462
(rnd -6.666 -5.555 0) = -6.409919187
(rnd -6.666 -5.555 0) = -6.586712912

(rnd 2 -2 0.01) = 1
(rnd 2 -2 0.01) = 1.47
(rnd 2 -2 0.01) = -1.53
(rnd 2 -2 0.01) = -1.63
(rnd 2 -2 0.01) = 1.48
(rnd 2 -2 0.01) = -0.98




2 comments:

  1. Using the function 'sequence' and 'amb' you could define a shorter 'rnd'

    (apply amb (sequence 3 4 0.25))

    'sequence' works similar to 'for' but gives you a list to work on. 'amb' picks randomly a number from the list.

    ReplyDelete