[gimp] Bug 690544 - Script-fu (rand 4294967295) hangs on machines with 64-bit longs



commit 139801f222a8af0fdcdfbd2e90ed8a046d617259
Author: Pedro Gimeno <bugzilla gnome personal formauri es>
Date:   Sun May 29 15:03:13 2016 +0200

    Bug 690544 - Script-fu (rand 4294967295) hangs on machines with 64-bit longs

 plug-ins/script-fu/scripts/script-fu-compat.init |  101 ++++++++++++++++++++--
 1 files changed, 93 insertions(+), 8 deletions(-)
---
diff --git a/plug-ins/script-fu/scripts/script-fu-compat.init 
b/plug-ins/script-fu/scripts/script-fu-compat.init
index a80a698..88f5c02 100644
--- a/plug-ins/script-fu/scripts/script-fu-compat.init
+++ b/plug-ins/script-fu/scripts/script-fu-compat.init
@@ -13,6 +13,8 @@
 
 ;The random number generator routines below have been slightly reformatted.
 ;A couple of define blocks which are not needed have been commented out.
+;It has also been extended to enable it to generate numbers with exactly 31
+;bits or more.
 ;The original file was called rand2.scm and can be found in:
 ;http://www-2.cs.cmu.edu/afs/cs/project/ai-repository/ai/lang/scheme/code/math/random/
 
@@ -65,15 +67,98 @@
 ; Bust           (abcde)      0.3024      0.3058
 
 (define (random n)
-  (let* (
-        (n (inexact->exact (truncate n)))
-        (M 2147483647)
-        (slop (modulo M n))
+  (define (internal-random n)
+    (let* (
+          (n (inexact->exact (truncate n)))
+          (M 2147483647)
+          (slop (modulo M (abs n)))
+          )
+      (let loop ((r (msrg-rand)))
+        (if (>= r slop)
+          (modulo r n)
+          (loop (msrg-rand))
+        )
+      )
+    )
+  )
+
+  ; Negative numbers have a bigger range in twos complement platforms
+  ; (nearly all platforms out there) than positive ones, so we deal with
+  ; the numbers in negative form.
+  (if (> n 0)
+    (+ n (random (- n)))
+
+    (if (>= n -2147483647)
+      (internal-random n)
+
+      ; 31-or-more-bits number requested - needs multiple extractions
+      ; because we don't generate enough random bits.
+      (if (>= n -1152921504606846975)
+        ; Up to 2^60-1, two extractions are enough
+        (let ((q (- (quotient (+ n 1) 1073741824) 1))) ; q=floor(n/2^30)
+          (let loop ()
+            (let ((big (+ (* (internal-random q) 1073741824)
+                          (internal-random -1073741824)
+                       )
+                 ))
+              (if (> big n)
+                big
+                (loop)
+              )
+            )
+          )
+        )
+
+        ; From 2^60 up, we do three extractions.
+        ; The code is better understood if seen as generating three
+        ; digits in base 2^30. q is the maximum value the first digit
+        ; can take. The other digits can take the full range.
+        ;
+        ; The strategy is to generate a random number digit by digit.
+        ; Here's an example in base 10. Say the input n is 348
+        ; (thus requesting a number between 0 and 347). Then the algorithm
+        ; first calls (internal-random 4) to get a digit betweeen 0 and 3,
+        ; then (internal-random 10) twice to get two more digits between
+        ; 0 and 9. Say the result is 366: since it is greater than 347,
+        ; it's discarded and the process restarted. When the result is
+        ; <= 347, that's the returned value. The probability of it being
+        ; greater than the max is always strictly less than 1/2.
+        ;
+        ; This is the same idea but in base 2^30 (1073741824). The
+        ; first digit's weight is (2^30)^2 = 1152921504606846976,
+        ; similarly to how in our base 10 example, the first digit's
+        ; weight is 10^2 = 100. In the base 10 example we first divide
+        ; the target number 348 by 100, taking the ceiling, to get 4.
+        ; Here we divide by (2^30)^2 instead, taking the ceiling too.
+        ;
+        ; The math is a bit obscured by the fact that we generate
+        ; the digits as negative, so that the result is negative as
+        ; well, but it's really the same thing. Changing the sign of
+        ; every digit just changes the sign of the result.
+        ;
+        ; This method works for n up to (2^30)^2*(2^31-1) which is
+        ; 2475880077417839045191401472 (slightly under 91 bits). That
+        ; covers the 64-bit range comfortably, and some more. If larger
+        ; numbers are needed, they'll have to be composed with a
+        ; user-defined procedure.
+
+        (if (>= n -2475880077417839045191401472)
+          (let ((q (- (quotient (+ n 1) 1152921504606846976) 1))) ; q=floor(n/2^60)
+            (let loop ()
+              (let ((big (+ (* (internal-random q) 1152921504606846976)
+                            (* (internal-random -1073741824) 1073741824)
+                            (internal-random -1073741824)
+                         )
+                   ))
+                (if (> big n)
+                  big
+                  (loop)
+                )
+              )
+            )
+          )
+          (error "requested (random n) range too large")
         )
-    (let loop ((r (msrg-rand)))
-      (if (> r slop)
-        (modulo r n)
-        (loop (msrg-rand))
       )
     )
   )


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]