zsh-workers
 help / color / mirror / code / Atom feed
* [PATCH 1/1] zsh/random
@ 2024-04-27 19:31 Clinton Bunch
  2024-06-17 14:43 ` Jun. T
  0 siblings, 1 reply; 7+ messages in thread
From: Clinton Bunch @ 2024-04-27 19:31 UTC (permalink / raw)
  To: zsh-workers

[-- Attachment #1: Type: text/plain, Size: 25942 bytes --]

diff --git a/Completion/Zsh/Type/_module_math_func 
b/Completion/Zsh/Type/_module_math_func
index 5044bdf4c..c997bde8b 100644
--- a/Completion/Zsh/Type/_module_math_func
+++ b/Completion/Zsh/Type/_module_math_func
@@ -2,7 +2,7 @@

  local mod
  local -a funcs alts
-local -a modules=( example mathfunc system )
+local -a modules=( example mathfunc system random)

  for mod in $modules; do
    funcs=( ${${${(f)"$(zmodload -Fl zsh/$mod 2>/dev/null)"}:#^+f:*}##+f:} )
diff --git a/Doc/Makefile.in b/Doc/Makefile.in
index d9be182e9..1197e74aa 100644
--- a/Doc/Makefile.in
+++ b/Doc/Makefile.in
@@ -68,7 +68,7 @@ Zsh/mod_hlgroup.yo Zsh/mod_langinfo.yo \
  Zsh/mod_ksh93.yo Zsh/mod_mapfile.yo Zsh/mod_mathfunc.yo \
  Zsh/mod_nearcolor.yo Zsh/mod_newuser.yo \
  Zsh/mod_parameter.yo Zsh/mod_pcre.yo Zsh/mod_private.yo \
-Zsh/mod_regex.yo Zsh/mod_sched.yo Zsh/mod_socket.yo \
+Zsh/mod_regex.yo Zsh/mod_random.yo Zsh/mod_sched.yo Zsh/mod_socket.yo \
  Zsh/mod_stat.yo  Zsh/mod_system.yo Zsh/mod_tcp.yo \
  Zsh/mod_termcap.yo Zsh/mod_terminfo.yo \
  Zsh/mod_watch.yo \
diff --git a/Doc/Zsh/mod_random.yo b/Doc/Zsh/mod_random.yo
new file mode 100644
index 000000000..17f6e5ff6
--- /dev/null
+++ b/Doc/Zsh/mod_random.yo
@@ -0,0 +1,57 @@
+COMMENT(!MOD!zsh/random
+Some High-quality randomness parameters and functions.
+!MOD!)
+The tt(zsh/random) module gets random data from the kernel random pool. 
If no
+kernel random pool can be found, the module will not load.
+
+subsect(Parameters)
+
+startitem()
+vindex(SRANDOM)
+item(tt(SRANDOM)) (
+A random positive 32-bit integer between 0 and 4,294,967,295. This 
parameter
+is read-only. The name was chosen for compatibility with Bash and to
+distinguish it from tt(RANDOM) which has a documented repeatable behavior.
+)
+enditem()
+
+subsect(Math Functions)
+
+startitem()
+item(tt(zrand_float+LPAR()RPAR())) (
+Returns a random floating point number between 0 and 1.
+)
+enditem()
+
+startitem()
+item(tt(zrand_int)+LPAR()tt(upper), tt(lower), tt(inclusive)RPAR()) (
+Returns a random integer between tt(lower) and tt(upper). All 
parameters are
+optional.  If none are specified it is equivalent to
+tt(SRANDOM).
+
+tt(upper) is the upper bound on the resultant number and defaults to
+4,294,967,295.
+
+tt(lower) is the lower bound and defaults to 0.
+
+The defaults of these two arguments are also the maximum and minimum to 
which
+either can be set.
+
+tt(inclusive) is a flag that controls whether the result is ever equal to
+tt(upper).  By default it is not. If this argument is set to a non-zero 
value
+then it may be.
+
+This is to facilitate a construct like tt($a[zrand_int($#a)+1]) rather
+than tt($a[zrand_int+LPAR()$#a-1+RPAR()+1]).
+For example, if $#a is 16, you would use tt(zrand_int+LPAR()16RPAR()) 
which has
+16 possible return values 0-15.  Because the function can return zero, 
in order
+to use it as an array index from 1-16 you need to add one.  It would
+be an array index range error for it to also potentially return 16 
($#a). You
+could, however, use the construct tt(zrand_int+LPAR()16,1,1+RPAR()) 
instead of
+adding 1 to achieve the same result, but it is more verbose.
+
+Most statistics algorithms seem to also expect 0 to tt(upper)-1, so 
this was
+deemed the most commonly desired case and chosen as the default.
+)
+enditem()
+
diff --git a/Src/Modules/random.c b/Src/Modules/random.c
new file mode 100644
index 000000000..b45483afe
--- /dev/null
+++ b/Src/Modules/random.c
@@ -0,0 +1,324 @@
+/*
+ * random.c - module to access kernel random sources.
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 2022 Clinton Bunch
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Clinton Bunch or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Clinton Bunch and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Clinton Bunch and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Clinton Bunch and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "random.mdh"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#ifdef HAVE_SYS_RANDOM_H
+#include <sys/random.h>
+#endif
+
+/* Simplify select URANDOM specific code */
+#if !defined(HAVE_ARC4RANDOM_BUF) && !defined(HAVE_GETRANDOM)
+#define USE_URANDOM
+#endif
+
+/* buffer to pre-load integers for SRANDOM to lessen the context 
switches */
+uint32_t rand_buff[8];
+static int buf_cnt = -1;
+
+#ifdef USE_URANDOM
+/* File descriptor for /dev/urandom */
+int randfd = -1;
+#endif /* USE_URANDOM */
+
+static zlong get_srandom(UNUSED(Param p));
+
+/**/
+ssize_t
+getrandom_buffer(void *buf, size_t len)
+{
+    ssize_t ret;
+    size_t  val     = 0;
+    uint8_t *bufptr = buf;
+
+    do {
+       errno = 0;
+#ifdef HAVE_ARC4RANDOM_BUF
+       arc4random_buf(buf,len);
+       ret = len;
+#elif defined(HAVE_GETRANDOM)
+       ret=getrandom(bufptr,(len - val),0);
+#else
+       ret=read(randfd,bufptr,(len - val));
+#endif
+       if (ret < 0) {
+           if (errno != EINTR || errflag || retflag || breaks || 
contflag) {
+               zwarn("Unable to get random data: %e.", errno);
+               return -1;
+           }
+       }
+       bufptr += ret;
+       val    += ret;
+    } while (ret < len);
+    return ret;
+}
+
+/*
+ * Generate count number of random 32-bit integers between 0 and max-1
+ * Got this algorithm from
+ * https://lemire.me/blog/2016/06/30/fast-random-shuffling/
+ * adapting the public domain code.
+ *
+ */
+
+/**/
+void
+get_bound_random_buffer(uint32_t *buffer, size_t count, uint32_t max)
+{
+    uint64_t multi_result;
+    uint32_t threshold;
+    uint32_t leftover;
+
+    size_t i; /* loop counter */
+
+    getrandom_buffer((void*) buffer, count*sizeof(uint32_t));
+    if (max == UINT32_MAX)
+       return;
+
+    for(i=0;i<count;i++) {
+       multi_result = ((uint64_t) buffer[i]) * (uint64_t) max;
+       leftover = (uint32_t) multi_result;
+
+       /*
+        * The following if statement should (according to Google's Gemini)
+        * only be executed with a probability of 1/2**32 or 2.33e-10
+        */
+       if(leftover < max) {
+           threshold= -max % max;
+           while (leftover < threshold) {
+               uint32_t j=get_srandom(NULL);
+               multi_result=(uint64_t) j * (uint64_t) max;
+               leftover= (uint32_t) multi_result;
+           }
+       }
+       buffer[i]=multi_result >> 32;
+    }
+}
+
+/*
+ * Provides for the SRANDOM parameter and returns an unsigned 32-bit random
+ * integer.
+ */
+
+/**/
+static zlong
+get_srandom(UNUSED(Param pm)) {
+
+    if(buf_cnt <= 0) {
+       getrandom_buffer((void*) rand_buff,sizeof(rand_buff));
+       buf_cnt=sizeof(rand_buff)/sizeof(rand_buff[0]);
+    }
+    return rand_buff[--buf_cnt];
+}
+
+/*
+ * Implements math function zrand_int takes 0 to 3 arguments an upper 
bound,
+ * a lower bound and a flag as to whether the range is inclusive or 
not.  The
+ * default is exclusive.  If neither upper or lower is specified this is no
+ * different than SRANDOM.
+ */
+
+/**/
+static mnumber
+math_zrand_int(UNUSED(char *name), int argc, mnumber *argv, UNUSED(int id))
+{
+    mnumber ret;
+    uint32_t i;
+    zlong lower=0, upper=UINT32_MAX,incl=0, diff;
+
+    ret.type = MN_INTEGER;
+
+    switch (argc) {
+       case 0: ret.u.l=get_srandom(NULL);
+               return ret;
+               break;
+       case 3: incl = (argv[2].u.l != 0)?1:0;
+       case 2: lower = argv[1].u.l;
+       case 1: upper = argv[0].u.l;
+       default: diff = upper-lower+incl;
+    }
+
+    if (lower < 0 || lower >= UINT32_MAX) {
+       zwarn("Lower bound (%z) out of range: 0-4294967295",lower);
+    } else if (upper < lower) {
+       zwarn("Upper bound (%z) must be greater than Lower Bound 
(%z)",upper,lower);
+    } else if (upper < 0 || upper >= UINT32_MAX) {
+       zwarn("Upper bound (%z) out of range: 0-4294967295",upper);
+    }
+
+    if ( diff == 0 ) {
+       ret.u.l=upper; /* still not convinced this shouldn't be an error. */
+    } else {
+       get_bound_random_buffer(&i,1,(uint32_t) diff);
+       ret.u.l=i+lower;
+    }
+    return ret;
+}
+
+/*
+ * Implements the mathfunc zrand_float and returns a random floating-point
+ * number between 0 and 1.
+ *
+ */
+
+/**/
+static mnumber
+math_zrand_float(UNUSED(char *name), UNUSED(int argc), UNUSED(mnumber 
*argv),
+            UNUSED(int id))
+{
+    mnumber ret;
+    double r;
+
+    r = random_real();
+    if (r < 0) {
+       zwarnnam(name, "Failed to get sufficient random data.");
+    }
+    ret.type = MN_FLOAT;
+    ret.u.d = r;
+
+    return ret;
+}
+
+static const struct gsu_integer srandom_gsu =
+{ get_srandom, nullintsetfn, stdunsetfn };
+
+static struct paramdef patab[] = {
+    {"SRANDOM", PM_INTEGER | PM_READONLY_SPECIAL | PM_HIDEVAL, NULL,
+           &srandom_gsu, NULL, NULL, NULL},
+};
+
+static struct mathfunc mftab[] = {
+    NUMMATHFUNC("zrand_float", math_zrand_float, 0, 0, 0),
+    NUMMATHFUNC("zrand_int", math_zrand_int, 0, 3, 0),
+};
+
+static struct features module_features = {
+    NULL, 0,
+    NULL, 0,
+    mftab, sizeof(mftab)/sizeof(*mftab),
+    patab, sizeof(patab)/sizeof(*patab),
+    0
+};
+
+/**/
+int
+setup_(UNUSED(Module m))
+{
+#ifdef USE_URANDOM
+    /* Check for the existence of /dev/urandom */
+
+    struct stat st;
+
+    errno=0;
+    if (stat("/dev/urandom",&st) < 0) {
+       zwarn("Error getting kernel random pool: %e.", errno);
+       return 1;
+    }
+
+    errno=0;
+    if (!(S_ISCHR(st.st_mode)) ) {
+       zwarn("Error getting kernel random pool: %e.", errno);
+       return 1;
+    }
+#endif /* USE_URANDOM */
+    return 0;
+}
+
+/**/
+int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m, &module_features, enables);
+}
+
+/**/
+int
+boot_(Module m)
+{
+#ifdef USE_URANDOM
+    /*
+     * Open /dev/urandom.  Here because of a weird issue on HP-UX 11.31
+     * When opening in setup_ open returned 0.  In boot_, it returns
+     * an unused file descriptor. Decided against ifdef HPUX as it works
+     * here just as well for other platforms.
+     *
+     */
+
+    int tmpfd=-1;
+
+    errno=0;
+    if ((tmpfd = open("/dev/urandom", O_RDONLY)) < 0) {
+       zwarn("Could not access kernel random pool: %e.",errno);
+       return 1;
+    }
+    randfd = movefd(tmpfd);
+    addmodulefd(randfd,FDT_MODULE);
+    if (randfd < 0) {
+       zwarn("Could not access kernel random pool.");
+       return 1;
+    }
+#endif /* USE_URANDOM */
+    return 0;
+}
+
+/**/
+int
+cleanup_(Module m)
+{
+    return setfeatureenables(m, &module_features, NULL);
+}
+
+/**/
+int
+finish_(UNUSED(Module m))
+{
+#ifdef USE_URANDOM
+    if (randfd >= 0)
+       zclose(randfd);
+#endif /* USE_URANDOM */
+    return 0;
+}
+
diff --git a/Src/Modules/random.mdd b/Src/Modules/random.mdd
new file mode 100644
index 000000000..7a75f29ff
--- /dev/null
+++ b/Src/Modules/random.mdd
@@ -0,0 +1,7 @@
+name=zsh/random
+link=either
+load=yes
+
+autofeatures="p:SRANDOM f:zrand_float f:zrand_int"
+
+objects="random.o random_real.o"
diff --git a/Src/Modules/random_real.c b/Src/Modules/random_real.c
new file mode 100644
index 000000000..900360414
--- /dev/null
+++ b/Src/Modules/random_real.c
@@ -0,0 +1,213 @@
+/*  This file contains code under different copyrights separated by */
+/* ====@@@@@=== */
+
+/*
+ * random_real.c - module to access kernel random sources.
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 2022 Clinton Bunch
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Clinton Bunch or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Clinton Bunch and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Clinton Bunch and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Clinton Bunch and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "random.mdh"
+
+#include <math.h>
+#include <stdint.h>
+#include <errno.h>
+
+
+/* Count the number of leading zeros, hopefully in gcc/clang by HW
+ * instruction */
+#if defined(__GNUC__) || defined(__clang__)
+#define clz64(x) __builtin_clzll(x)
+#else
+#define clz64(x) _zclz64(x)
+
+/**/
+int
+_zclz64(uint64_t x) {
+    int n = 0;
+
+    if (x == 0)
+       return 64;
+
+    if (!(x & 0xFFFFFFFF00000000ull)) {
+       n+=32;
+       x<<=32;
+    }
+    if (!(x & 0xFFFF000000000000ull)) {
+       n+=16;
+       x<<=16;
+    }
+    if (!(x & 0xFF00000000000000ull)) {
+       n+=8;
+       x<<=8;
+    }
+    if (!(x & 0xF000000000000000ull)) {
+       n+=4;
+       x<<=4;
+    }
+    if (!(x & 0xC000000000000000ull)) {
+       n+=2;
+       x<<=1;
+    }
+    if (!(x & 0x8000000000000000ull)) {
+       n+=1;
+    }
+    return n;
+}
+#endif /* __GNU_C__ or __clang__ */
+
+/**/
+uint64_t
+random_64bit(void) {
+    uint64_t r;
+
+    if(getrandom_buffer(&r,sizeof(r)) < 0) {
+       zwarn("zsh/random: Can't get sufficient random data.");
+       return 1; /* 0 will cause loop */
+    }
+
+    return r;
+}
+
+/* ====@@@@@=== */
+/* Following code is under the below copyright, despite changes for error
+ * handling and removing GCCisms */
+
+/*-
+ * Copyright (c) 2014 Taylor R. Campbell
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Uniform random floats: How to generate a double-precision
+ * floating-point numbers in [0, 1] uniformly at random given a uniform
+ * random source of bits.
+ *
+ * See <http://mumble.net/~campbell/2014/04/28/uniform-random-float>
+ * for explanation.
+ *
+ * Updated 2015-02-22 to replace ldexp(x, <constant>) by x * ldexp(1,
+ * <constant>), since glibc and NetBSD libm both have slow software
+ * bit-twiddling implementations of ldexp, but GCC can constant-fold
+ * the latter.
+ */
+
+/*
+ * random_real: Generate a stream of bits uniformly at random and
+ * interpret it as the fractional part of the binary expansion of a
+ * number in [0, 1], 0.00001010011111010100...; then round it.
+ */
+
+/**/
+double
+random_real(void)
+{
+       int exponent = 0;
+       uint64_t significand = 0;
+       uint64_t r = 0;
+       unsigned shift;
+
+       /*
+        * Read zeros into the exponent until we hit a one; the rest
+        * will go into the significand.
+        */
+       while (significand == 0) {
+               exponent -= 64;
+
+               /* Get random_64bit and check for error */
+               errno = 0;
+               significand = random_64bit();
+               if (errno)
+                   return -1;
+               /*
+                * If the exponent falls below -1074 = emin + 1 - p,
+                * the exponent of the smallest subnormal, we are
+                * guaranteed the result will be rounded to zero.  This
+                * case is so unlikely it will happen in realistic
+                * terms only if random_64bit is broken.
+                */
+               if (exponent < -1074)
+                       return 0;
+       }
+
+       /*
+        * There is a 1 somewhere in significand, not necessarily in
+        * the most significant position.  If there are leading zeros,
+        * shift them into the exponent and refill the less-significant
+        * bits of the significand.  Can't predict one way or another
+        * whether there are leading zeros: there's a fifty-fifty
+        * chance, if random_64bit is uniformly distributed.
+        */
+       shift = clz64(significand);
+       if (shift != 0) {
+
+               errno = 0;
+               r = random_64bit();
+               if (errno)
+                   return -1;
+
+               exponent -= shift;
+               significand <<= shift;
+               significand |= (r >> (64 - shift));
+       }
+
+       /*
+        * Set the sticky bit, since there is almost surely another 1
+        * in the bit stream.  Otherwise, we might round what looks
+        * like a tie to even when, almost surely, were we to look
+        * further in the bit stream, there would be a 1 breaking the
+        * tie.
+        */
+       significand |= 1;
+
+       /*
+        * Finally, convert to double (rounding) and scale by
+        * 2^exponent.
+        */
+       return ldexp((double)significand, exponent);
+}
+/* ====@@@@@=== */
diff --git a/Test/V15random.ztst b/Test/V15random.ztst
new file mode 100644
index 000000000..889324380
--- /dev/null
+++ b/Test/V15random.ztst
@@ -0,0 +1,120 @@
+%prep
+
+  if ! zmodload zsh/mathfunc 2>/dev/null; then
+    ZTST_unimplemented="can't load the zsh/mathfunc module for testing"
+  fi
+
+  print -ru $ZTST_fd 'This test may take two seconds...'
+
+  function calc_chi2() {
+# Calculate Chi Squared
+       integer n=$1
+       for ((k=1;k<=n;k++))
+       do
+               ((delta=${observed[$k]}-expected))
+               #echo "$k=$delta \c"
+               ((chi2+=delta**2/expected))
+       done
+       #echo
+  }
+  function ligf() {
+       float -E S=$1 Z=$2
+       float -gE RET
+       if (( Z < 0.0 )); then
+               RET=0.0
+       fi
+       float -E Sc=$((1.0/S))
+       ((Sc *= Z**S ))
+       ((Sc *= exp(-1*Z) ))
+       float -E Sum=1.0 Nom=1.0 Denom=1.0
+       integer I
+       # 200 iterations seems like enough for an approximation of an 
infinate
+       # series
+       for (( I=0;I<200;I++))
+       do
+               ((Nom *= Z))
+               ((S++))
+               ((Denom *= S))
+               ((Sum += (Nom/Denom) ))
+       done
+       RET=$((Sum * Sc))
+  }
+#echo $observed
+  function calc_p() {
+       #echo "chi2=$chi2"
+       float chi2=0 cdf=0
+        float RET
+       calc_chi2 "$n"
+       ligf $(((n-1)*0.5)) $((chi2*0.5))
+       ((cdf=$RET/gamma((n-1)*0.5) ))
+       ((p=1-cdf))
+  }
+%test
+  fail=0
+  for ((o=0;o<100;o++))
+  do
+      n=20
+      samples=1000
+      integer j=0
+      float -F expected=$((samples*1.0/n))
+      float -F p
+      typeset -a observed=([$n]=0)
+      for ((i=1;i<=samples;i++))
+      do
+        j=$(( zrand_int(n,1,1)  ))
+        ((observed[$j]++))
+      done
+      calc_p
+      if ((p<0.05)); then
+         ((fail++))
+      fi
+  done
+  ((fail <= 10))
+0q:random integer $samples samples between 1-$n
+
+  fail=0
+  unset observed
+  for ((o=0;o<100;o++))
+  do
+      n=20
+      samples=1000
+      integer j=0
+      float -F expected=$((samples*1.0/n))
+      float -F p
+      typeset -a observed=([$n]=0)
+      for ((i=1;i<=samples;i++))
+      do
+        j=$(( ceil(zrand_float()*n)  ))
+        ((observed[$j]++))
+      done
+      calc_p
+      if ((p<0.05)); then
+         ((fail++))
+      fi
+  done
+  ((fail <= 10))
+0q:random float $samples samples
+
+  fail=0
+  unset observed
+  for ((o=0;o<100;o++))
+  do
+      n=16
+      samples=1000
+      integer j=0
+      float -F expected=$((samples*1.0/n))
+      float -F p
+      typeset -a observed=([$n]=0)
+      for ((i=1;i<=samples;i++))
+      do
+        j=$(( (SRANDOM & 0x0F)+1  ))
+        ((observed[$j]++))
+      done
+      calc_p
+      if ((p<0.05)); then
+         ((fail++))
+      fi
+  done
+  ((fail <= 10))
+0q:SRANDOM mod 16 $samples samples
+
diff --git a/configure.ac b/configure.ac
index 78621042d..35f641cde 100644
--- a/configure.ac
+++ b/configure.ac
@@ -636,6 +636,7 @@ fi
  AC_CHECK_HEADERS(sys/time.h sys/times.h sys/select.h termcap.h termio.h \
                  termios.h sys/param.h sys/filio.h string.h memory.h \
                  limits.h fcntl.h libc.h sys/utsname.h sys/resource.h \
+                 sys/random.h \
                  locale.h errno.h stdio.h stdarg.h varargs.h stdlib.h \
                  unistd.h sys/capability.h \
                  utmp.h utmpx.h sys/types.h pwd.h grp.h poll.h sys/mman.h \
@@ -1292,6 +1293,7 @@ AC_CHECK_FUNCS(strftime strptime mktime timelocal \
                cygwin_conv_path \
                nanosleep \
                srand_deterministic \
+               getrandom arc4random_buf \
                setutxent getutxent endutxent getutent)
  AC_FUNC_STRCOLL


[-- Attachment #2: zsh-random.patch.txt --]
[-- Type: text/plain, Size: 21649 bytes --]

diff --git a/Completion/Zsh/Type/_module_math_func b/Completion/Zsh/Type/_module_math_func
index 5044bdf4c..c997bde8b 100644
--- a/Completion/Zsh/Type/_module_math_func
+++ b/Completion/Zsh/Type/_module_math_func
@@ -2,7 +2,7 @@
 
 local mod
 local -a funcs alts
-local -a modules=( example mathfunc system )
+local -a modules=( example mathfunc system random)
 
 for mod in $modules; do
   funcs=( ${${${(f)"$(zmodload -Fl zsh/$mod 2>/dev/null)"}:#^+f:*}##+f:} )
diff --git a/Doc/Makefile.in b/Doc/Makefile.in
index d9be182e9..1197e74aa 100644
--- a/Doc/Makefile.in
+++ b/Doc/Makefile.in
@@ -68,7 +68,7 @@ Zsh/mod_hlgroup.yo Zsh/mod_langinfo.yo \
 Zsh/mod_ksh93.yo Zsh/mod_mapfile.yo Zsh/mod_mathfunc.yo \
 Zsh/mod_nearcolor.yo Zsh/mod_newuser.yo \
 Zsh/mod_parameter.yo Zsh/mod_pcre.yo Zsh/mod_private.yo \
-Zsh/mod_regex.yo Zsh/mod_sched.yo Zsh/mod_socket.yo \
+Zsh/mod_regex.yo Zsh/mod_random.yo Zsh/mod_sched.yo Zsh/mod_socket.yo \
 Zsh/mod_stat.yo  Zsh/mod_system.yo Zsh/mod_tcp.yo \
 Zsh/mod_termcap.yo Zsh/mod_terminfo.yo \
 Zsh/mod_watch.yo \
diff --git a/Doc/Zsh/mod_random.yo b/Doc/Zsh/mod_random.yo
new file mode 100644
index 000000000..17f6e5ff6
--- /dev/null
+++ b/Doc/Zsh/mod_random.yo
@@ -0,0 +1,57 @@
+COMMENT(!MOD!zsh/random
+Some High-quality randomness parameters and functions.
+!MOD!)
+The tt(zsh/random) module gets random data from the kernel random pool. If no
+kernel random pool can be found, the module will not load.
+
+subsect(Parameters)
+
+startitem()
+vindex(SRANDOM)
+item(tt(SRANDOM)) (
+A random positive 32-bit integer between 0 and 4,294,967,295.  This parameter
+is read-only. The name was chosen for compatibility with Bash and to
+distinguish it from tt(RANDOM) which has a documented repeatable behavior.
+)
+enditem()
+
+subsect(Math Functions)
+
+startitem()
+item(tt(zrand_float+LPAR()RPAR())) (
+Returns a random floating point number between 0 and 1.
+)
+enditem()
+
+startitem()
+item(tt(zrand_int)+LPAR()tt(upper), tt(lower), tt(inclusive)RPAR()) (
+Returns a random integer between tt(lower) and tt(upper). All parameters are
+optional.  If none are specified it is equivalent to
+tt(SRANDOM).
+
+tt(upper) is the upper bound on the resultant number and defaults to
+4,294,967,295.
+
+tt(lower) is the lower bound and defaults to 0.
+
+The defaults of these two arguments are also the maximum and minimum to which
+either can be set.
+
+tt(inclusive) is a flag that controls whether the result is ever equal to
+tt(upper).  By default it is not. If this argument is set to a non-zero value
+then it may be.
+
+This is to facilitate a construct like tt($a[zrand_int($#a)+1]) rather
+than tt($a[zrand_int+LPAR()$#a-1+RPAR()+1]).
+For example, if $#a is 16, you would use tt(zrand_int+LPAR()16RPAR()) which has
+16 possible return values 0-15.  Because the function can return zero, in order
+to use it as an array index from 1-16 you need to add one.  It would
+be an array index range error for it to also potentially return 16 ($#a). You
+could, however, use the construct tt(zrand_int+LPAR()16,1,1+RPAR()) instead of
+adding 1 to achieve the same result, but it is more verbose.
+
+Most statistics algorithms seem to also expect 0 to tt(upper)-1, so this was
+deemed the most commonly desired case and chosen as the default.
+)
+enditem()
+
diff --git a/Src/Modules/random.c b/Src/Modules/random.c
new file mode 100644
index 000000000..b45483afe
--- /dev/null
+++ b/Src/Modules/random.c
@@ -0,0 +1,324 @@
+/*
+ * random.c - module to access kernel random sources.
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 2022 Clinton Bunch
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Clinton Bunch or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Clinton Bunch and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Clinton Bunch and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Clinton Bunch and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "random.mdh"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#ifdef HAVE_SYS_RANDOM_H
+#include <sys/random.h>
+#endif
+
+/* Simplify select URANDOM specific code */
+#if !defined(HAVE_ARC4RANDOM_BUF) && !defined(HAVE_GETRANDOM)
+#define USE_URANDOM
+#endif
+
+/* buffer to pre-load integers for SRANDOM to lessen the context switches */
+uint32_t rand_buff[8];
+static int buf_cnt = -1;
+
+#ifdef USE_URANDOM
+/* File descriptor for /dev/urandom */
+int randfd = -1;
+#endif /* USE_URANDOM */
+
+static zlong get_srandom(UNUSED(Param p));
+
+/**/
+ssize_t
+getrandom_buffer(void *buf, size_t len)
+{
+    ssize_t ret;
+    size_t  val     = 0;
+    uint8_t *bufptr = buf;
+
+    do {
+	errno = 0;
+#ifdef HAVE_ARC4RANDOM_BUF
+	arc4random_buf(buf,len);
+	ret = len;
+#elif defined(HAVE_GETRANDOM)
+	ret=getrandom(bufptr,(len - val),0);
+#else
+	ret=read(randfd,bufptr,(len - val));
+#endif
+	if (ret < 0) {
+	    if (errno != EINTR || errflag || retflag || breaks || contflag) {
+		zwarn("Unable to get random data: %e.", errno);
+		return -1;
+	    }
+	}
+	bufptr += ret;
+	val    += ret;
+    } while (ret < len);
+    return ret;
+}
+
+/*
+ * Generate count number of random 32-bit integers between 0 and max-1 
+ * Got this algorithm from
+ * https://lemire.me/blog/2016/06/30/fast-random-shuffling/
+ * adapting the public domain code.
+ *
+ */
+
+/**/
+void
+get_bound_random_buffer(uint32_t *buffer, size_t count, uint32_t max)
+{
+    uint64_t multi_result;
+    uint32_t threshold;
+    uint32_t leftover;
+
+    size_t i; /* loop counter */
+
+    getrandom_buffer((void*) buffer, count*sizeof(uint32_t));
+    if (max == UINT32_MAX) 
+	return;
+
+    for(i=0;i<count;i++) {
+	multi_result = ((uint64_t) buffer[i]) * (uint64_t) max;
+	leftover = (uint32_t) multi_result;
+
+	/*
+	 * The following if statement should (according to Google's Gemini)
+	 * only be executed with a probability of 1/2**32 or 2.33e-10
+	 */
+	if(leftover < max) {
+	    threshold= -max % max;
+	    while (leftover < threshold) {
+		uint32_t j=get_srandom(NULL);
+		multi_result=(uint64_t) j * (uint64_t) max;
+		leftover= (uint32_t) multi_result;
+	    }
+	}
+	buffer[i]=multi_result >> 32;
+    }
+}
+
+/*
+ * Provides for the SRANDOM parameter and returns an unsigned 32-bit random
+ * integer.
+ */
+
+/**/
+static zlong
+get_srandom(UNUSED(Param pm)) {
+
+    if(buf_cnt <= 0) {
+	getrandom_buffer((void*) rand_buff,sizeof(rand_buff));
+	buf_cnt=sizeof(rand_buff)/sizeof(rand_buff[0]);
+    }
+    return rand_buff[--buf_cnt];
+}
+
+/*
+ * Implements math function zrand_int takes 0 to 3 arguments an upper bound,
+ * a lower bound and a flag as to whether the range is inclusive or not.  The
+ * default is exclusive.  If neither upper or lower is specified this is no
+ * different than SRANDOM.
+ */
+
+/**/
+static mnumber
+math_zrand_int(UNUSED(char *name), int argc, mnumber *argv, UNUSED(int id))
+{
+    mnumber ret;
+    uint32_t i;
+    zlong lower=0, upper=UINT32_MAX,incl=0, diff;
+
+    ret.type = MN_INTEGER;
+
+    switch (argc) {
+	case 0: ret.u.l=get_srandom(NULL);
+		return ret;
+		break;
+	case 3: incl = (argv[2].u.l != 0)?1:0;
+	case 2: lower = argv[1].u.l;
+	case 1: upper = argv[0].u.l;
+	default: diff = upper-lower+incl;
+    }
+
+    if (lower < 0 || lower >= UINT32_MAX) {
+	zwarn("Lower bound (%z) out of range: 0-4294967295",lower);
+    } else if (upper < lower) {
+	zwarn("Upper bound (%z) must be greater than Lower Bound (%z)",upper,lower);
+    } else if (upper < 0 || upper >= UINT32_MAX) {
+	zwarn("Upper bound (%z) out of range: 0-4294967295",upper);
+    }
+
+    if ( diff == 0 ) {
+	ret.u.l=upper; /* still not convinced this shouldn't be an error. */
+    } else {
+	get_bound_random_buffer(&i,1,(uint32_t) diff);
+	ret.u.l=i+lower;
+    }
+    return ret;
+}
+
+/*
+ * Implements the mathfunc zrand_float and returns a random floating-point
+ * number between 0 and 1.  
+ *
+ */
+
+/**/
+static mnumber
+math_zrand_float(UNUSED(char *name), UNUSED(int argc), UNUSED(mnumber *argv),
+	     UNUSED(int id))
+{
+    mnumber ret;
+    double r;
+
+    r = random_real();
+    if (r < 0) {
+	zwarnnam(name, "Failed to get sufficient random data.");
+    }
+    ret.type = MN_FLOAT;
+    ret.u.d = r;
+
+    return ret;
+}
+
+static const struct gsu_integer srandom_gsu =
+{ get_srandom, nullintsetfn, stdunsetfn };
+
+static struct paramdef patab[] = {
+    {"SRANDOM", PM_INTEGER | PM_READONLY_SPECIAL | PM_HIDEVAL, NULL,
+	    &srandom_gsu, NULL, NULL, NULL},
+};
+
+static struct mathfunc mftab[] = {
+    NUMMATHFUNC("zrand_float", math_zrand_float, 0, 0, 0),
+    NUMMATHFUNC("zrand_int", math_zrand_int, 0, 3, 0),
+};
+
+static struct features module_features = {
+    NULL, 0,
+    NULL, 0,
+    mftab, sizeof(mftab)/sizeof(*mftab),
+    patab, sizeof(patab)/sizeof(*patab),
+    0
+};
+
+/**/
+int
+setup_(UNUSED(Module m))
+{
+#ifdef USE_URANDOM
+    /* Check for the existence of /dev/urandom */
+
+    struct stat st;
+
+    errno=0;
+    if (stat("/dev/urandom",&st) < 0) {
+	zwarn("Error getting kernel random pool: %e.", errno);
+	return 1;
+    }
+
+    errno=0;
+    if (!(S_ISCHR(st.st_mode)) ) {
+	zwarn("Error getting kernel random pool: %e.", errno);
+	return 1;
+    }
+#endif /* USE_URANDOM */
+    return 0;
+}
+
+/**/
+int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m, &module_features, enables);
+}
+
+/**/
+int
+boot_(Module m)
+{
+#ifdef USE_URANDOM
+    /*
+     * Open /dev/urandom.  Here because of a weird issue on HP-UX 11.31
+     * When opening in setup_ open returned 0.  In boot_, it returns
+     * an unused file descriptor. Decided against ifdef HPUX as it works
+     * here just as well for other platforms.
+     *
+     */
+
+    int tmpfd=-1;
+
+    errno=0;
+    if ((tmpfd = open("/dev/urandom", O_RDONLY)) < 0) {
+	zwarn("Could not access kernel random pool: %e.",errno);
+	return 1;
+    }
+    randfd = movefd(tmpfd);
+    addmodulefd(randfd,FDT_MODULE);
+    if (randfd < 0) {
+	zwarn("Could not access kernel random pool.");
+	return 1;
+    }
+#endif /* USE_URANDOM */
+    return 0;
+}
+
+/**/
+int
+cleanup_(Module m)
+{
+    return setfeatureenables(m, &module_features, NULL);
+}
+
+/**/
+int
+finish_(UNUSED(Module m))
+{
+#ifdef USE_URANDOM
+    if (randfd >= 0)
+	zclose(randfd);
+#endif /* USE_URANDOM */
+    return 0;
+}
+
diff --git a/Src/Modules/random.mdd b/Src/Modules/random.mdd
new file mode 100644
index 000000000..7a75f29ff
--- /dev/null
+++ b/Src/Modules/random.mdd
@@ -0,0 +1,7 @@
+name=zsh/random
+link=either
+load=yes
+
+autofeatures="p:SRANDOM f:zrand_float f:zrand_int"
+
+objects="random.o random_real.o"
diff --git a/Src/Modules/random_real.c b/Src/Modules/random_real.c
new file mode 100644
index 000000000..900360414
--- /dev/null
+++ b/Src/Modules/random_real.c
@@ -0,0 +1,213 @@
+/*  This file contains code under different copyrights separated by */
+/* ====@@@@@=== */
+
+/*
+ * random_real.c - module to access kernel random sources.
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 2022 Clinton Bunch
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Clinton Bunch or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Clinton Bunch and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Clinton Bunch and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Clinton Bunch and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "random.mdh"
+
+#include <math.h>
+#include <stdint.h>
+#include <errno.h>
+
+
+/* Count the number of leading zeros, hopefully in gcc/clang by HW
+ * instruction */
+#if defined(__GNUC__) || defined(__clang__)
+#define clz64(x) __builtin_clzll(x)
+#else
+#define clz64(x) _zclz64(x)
+
+/**/
+int
+_zclz64(uint64_t x) {
+    int n = 0;
+
+    if (x == 0)
+	return 64;
+
+    if (!(x & 0xFFFFFFFF00000000ull)) {
+	n+=32;
+	x<<=32;
+    }
+    if (!(x & 0xFFFF000000000000ull)) {
+	n+=16;
+	x<<=16;
+    }
+    if (!(x & 0xFF00000000000000ull)) {
+	n+=8;
+	x<<=8;
+    }
+    if (!(x & 0xF000000000000000ull)) {
+	n+=4;
+	x<<=4;
+    }
+    if (!(x & 0xC000000000000000ull)) {
+	n+=2;
+	x<<=1;
+    }
+    if (!(x & 0x8000000000000000ull)) {
+	n+=1;
+    }
+    return n;
+}
+#endif /* __GNU_C__ or __clang__ */
+
+/**/
+uint64_t
+random_64bit(void) {
+    uint64_t r;
+
+    if(getrandom_buffer(&r,sizeof(r)) < 0) {
+	zwarn("zsh/random: Can't get sufficient random data.");
+	return 1; /* 0 will cause loop */
+    }
+
+    return r;
+}
+
+/* ====@@@@@=== */
+/* Following code is under the below copyright, despite changes for error
+ * handling and removing GCCisms */
+
+/*-
+ * Copyright (c) 2014 Taylor R. Campbell
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Uniform random floats: How to generate a double-precision
+ * floating-point numbers in [0, 1] uniformly at random given a uniform
+ * random source of bits.
+ *
+ * See <http://mumble.net/~campbell/2014/04/28/uniform-random-float>
+ * for explanation.
+ *
+ * Updated 2015-02-22 to replace ldexp(x, <constant>) by x * ldexp(1,
+ * <constant>), since glibc and NetBSD libm both have slow software
+ * bit-twiddling implementations of ldexp, but GCC can constant-fold
+ * the latter.
+ */
+
+/*
+ * random_real: Generate a stream of bits uniformly at random and
+ * interpret it as the fractional part of the binary expansion of a
+ * number in [0, 1], 0.00001010011111010100...; then round it.
+ */
+
+/**/
+double
+random_real(void)
+{
+	int exponent = 0;
+	uint64_t significand = 0;
+	uint64_t r = 0;
+	unsigned shift;
+
+	/*
+	 * Read zeros into the exponent until we hit a one; the rest
+	 * will go into the significand.
+	 */
+	while (significand == 0) {
+		exponent -= 64;
+
+		/* Get random_64bit and check for error */
+		errno = 0;
+		significand = random_64bit();
+		if (errno)
+		    return -1;
+		/*
+		 * If the exponent falls below -1074 = emin + 1 - p,
+		 * the exponent of the smallest subnormal, we are
+		 * guaranteed the result will be rounded to zero.  This
+		 * case is so unlikely it will happen in realistic
+		 * terms only if random_64bit is broken.
+		 */
+		if (exponent < -1074)
+			return 0;
+	}
+
+	/*
+	 * There is a 1 somewhere in significand, not necessarily in
+	 * the most significant position.  If there are leading zeros,
+	 * shift them into the exponent and refill the less-significant
+	 * bits of the significand.  Can't predict one way or another
+	 * whether there are leading zeros: there's a fifty-fifty
+	 * chance, if random_64bit is uniformly distributed.
+	 */
+	shift = clz64(significand);
+	if (shift != 0) {
+
+		errno = 0;
+		r = random_64bit();
+		if (errno)
+		    return -1;
+
+		exponent -= shift;
+		significand <<= shift;
+		significand |= (r >> (64 - shift));
+	}
+
+	/*
+	 * Set the sticky bit, since there is almost surely another 1
+	 * in the bit stream.  Otherwise, we might round what looks
+	 * like a tie to even when, almost surely, were we to look
+	 * further in the bit stream, there would be a 1 breaking the
+	 * tie.
+	 */
+	significand |= 1;
+
+	/*
+	 * Finally, convert to double (rounding) and scale by
+	 * 2^exponent.
+	 */
+	return ldexp((double)significand, exponent);
+}
+/* ====@@@@@=== */
diff --git a/Test/V15random.ztst b/Test/V15random.ztst
new file mode 100644
index 000000000..889324380
--- /dev/null
+++ b/Test/V15random.ztst
@@ -0,0 +1,120 @@
+%prep
+
+  if ! zmodload zsh/mathfunc 2>/dev/null; then
+    ZTST_unimplemented="can't load the zsh/mathfunc module for testing"
+  fi
+
+  print -ru $ZTST_fd 'This test may take two seconds...'
+
+  function calc_chi2() {
+# Calculate Chi Squared
+	integer n=$1
+	for ((k=1;k<=n;k++))
+	do
+		((delta=${observed[$k]}-expected))
+		#echo "$k=$delta \c"
+		((chi2+=delta**2/expected))
+	done
+	#echo
+  }
+  function ligf() {
+	float -E S=$1 Z=$2
+	float -gE RET
+	if (( Z < 0.0 )); then
+		RET=0.0
+	fi
+	float -E Sc=$((1.0/S))
+	((Sc *= Z**S ))
+	((Sc *= exp(-1*Z) ))
+	float -E Sum=1.0 Nom=1.0 Denom=1.0
+	integer I
+	# 200 iterations seems like enough for an approximation of an infinate
+	# series
+	for (( I=0;I<200;I++))
+	do
+		((Nom *= Z))
+		((S++))
+		((Denom *= S))
+		((Sum += (Nom/Denom) ))
+	done
+	RET=$((Sum * Sc))
+  }
+#echo $observed
+  function calc_p() {
+	#echo "chi2=$chi2"
+	float chi2=0 cdf=0
+        float RET
+	calc_chi2 "$n"
+	ligf $(((n-1)*0.5)) $((chi2*0.5))
+	((cdf=$RET/gamma((n-1)*0.5) ))
+	((p=1-cdf))
+  }
+%test
+  fail=0
+  for ((o=0;o<100;o++))
+  do
+      n=20
+      samples=1000
+      integer j=0
+      float -F expected=$((samples*1.0/n))
+      float -F p
+      typeset -a observed=([$n]=0)
+      for ((i=1;i<=samples;i++))
+      do
+        j=$(( zrand_int(n,1,1)  ))
+        ((observed[$j]++))
+      done
+      calc_p
+      if ((p<0.05)); then
+         ((fail++))
+      fi
+  done
+  ((fail <= 10))
+0q:random integer $samples samples between 1-$n
+
+  fail=0
+  unset observed
+  for ((o=0;o<100;o++))
+  do
+      n=20
+      samples=1000
+      integer j=0
+      float -F expected=$((samples*1.0/n))
+      float -F p
+      typeset -a observed=([$n]=0)
+      for ((i=1;i<=samples;i++))
+      do
+        j=$(( ceil(zrand_float()*n)  ))
+        ((observed[$j]++))
+      done
+      calc_p
+      if ((p<0.05)); then
+         ((fail++))
+      fi
+  done
+  ((fail <= 10))
+0q:random float $samples samples
+
+  fail=0
+  unset observed
+  for ((o=0;o<100;o++))
+  do
+      n=16
+      samples=1000
+      integer j=0
+      float -F expected=$((samples*1.0/n))
+      float -F p
+      typeset -a observed=([$n]=0)
+      for ((i=1;i<=samples;i++))
+      do
+        j=$(( (SRANDOM & 0x0F)+1  ))
+        ((observed[$j]++))
+      done
+      calc_p
+      if ((p<0.05)); then
+         ((fail++))
+      fi
+  done
+  ((fail <= 10))
+0q:SRANDOM mod 16 $samples samples
+
diff --git a/configure.ac b/configure.ac
index 78621042d..35f641cde 100644
--- a/configure.ac
+++ b/configure.ac
@@ -636,6 +636,7 @@ fi
 AC_CHECK_HEADERS(sys/time.h sys/times.h sys/select.h termcap.h termio.h \
 		 termios.h sys/param.h sys/filio.h string.h memory.h \
 		 limits.h fcntl.h libc.h sys/utsname.h sys/resource.h \
+                 sys/random.h \
 		 locale.h errno.h stdio.h stdarg.h varargs.h stdlib.h \
 		 unistd.h sys/capability.h \
 		 utmp.h utmpx.h sys/types.h pwd.h grp.h poll.h sys/mman.h \
@@ -1292,6 +1293,7 @@ AC_CHECK_FUNCS(strftime strptime mktime timelocal \
 	       cygwin_conv_path \
 	       nanosleep \
 	       srand_deterministic \
+               getrandom arc4random_buf \
 	       setutxent getutxent endutxent getutent)
 AC_FUNC_STRCOLL
 

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH 1/1] zsh/random
  2024-04-27 19:31 [PATCH 1/1] zsh/random Clinton Bunch
@ 2024-06-17 14:43 ` Jun. T
  2024-06-17 23:52   ` Clinton Bunch
  0 siblings, 1 reply; 7+ messages in thread
From: Jun. T @ 2024-06-17 14:43 UTC (permalink / raw)
  To: zsh-workers

# Please do not separate a patch into [0/1] and [1/1].
# Some people reply to [0/1], others to [1/1].

What is the current status of this patch?

Here are my comments:


> 2024/04/28 4:31, Clinton Bunch <cdb_zsh@zentaur.org> wrote:

> Doc/Zsh/mod_random.yo

> +item(tt(SRANDOM)) (
> +A random positive 32-bit integer between 0 and 4,294,967,295.

'between 0 and 4,294,967,295'
I think $SRAND can be 0; can it be 4,294,967,295, or only to
4,294,967,295-1?

> +item(tt(zrand_float+LPAR()RPAR())) (
> +Returns a random floating point number between 0 and 1.

'between 0 and 1' Is this inclusive?
Can zrand_float() return 1?
  
> +item(tt(zrand_int)+LPAR()tt(upper), tt(lower), tt(inclusive)RPAR()) (
> +Returns a random integer between tt(lower) and tt(upper). All parameters are
> +optional.  If none are specified it is equivalent to
> +tt(SRANDOM).

> +tt(inclusive) is a flag that controls whether the result is ever equal to
> +tt(upper).  By default it is not.

If 'inclusive' is 0 (off) by default, then
0 <= zrand_int() < 4,294,967,295 by default.
Is this equivalent to $SRANDOM?


> Src/Modules/random.c

> +#include <stdbool.h>
> +#include <stdint.h>

I guess stdbool.h is not used?

I think including stdint.h (new in C99 but may exist in earlier
compilers) without #ifdef HAVE_STDINT_H is OK now, since we
are now using C99 as the C language standard, right?

> +/* buffer to pre-load integers for SRANDOM to lessen the context switches */
> +uint32_t rand_buff[8];

static uint32_t rand_buff[8];

> +int
> +boot_(Module m)

UNUSED(m)


> Src/Modules/random.mdd

> +load=yes

Does this module really need be autoloadable? I don't know what are
the criteria that a module should be autoloadable.


> Test/V15random.ztst

I don't know whether zsh need to check the "quality" of the random
number. I guess the simple $RANDOM would also pass these tests.

Maybe better to check that $SRANDOM is 'non-repeatable'?

If we will do the quality check, then:

> +  print -ru $ZTST_fd 'This test may take two seconds...'

Why two seconds? A few seconds, or something like that?

> +  function calc_chi2() {
> +# Calculate Chi Squared
(snip)
> +       #echo
> +  }

Please remove several '#echo' (for debugging?).

You can convert the functions calc_chi2(), ligf() etc. into
math-functions by using 'functions -M'. Then you can call them as,
for example (( p = 1 - ligf(s, z)/gamma(s) ))

> +0q:random integer $samples samples between 1-$n

The flag 'q' works only for the lines starting with < > or ?.
$samples and $n are not expanded.

--
Jun

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH 1/1] zsh/random
  2024-06-17 14:43 ` Jun. T
@ 2024-06-17 23:52   ` Clinton Bunch
  2024-06-18  0:16     ` Clinton Bunch
  0 siblings, 1 reply; 7+ messages in thread
From: Clinton Bunch @ 2024-06-17 23:52 UTC (permalink / raw)
  To: zsh-workers

On 6/17/2024 09:43, Jun. T wrote:
> # Please do not separate a patch into [0/1] and [1/1].
> # Some people reply to [0/1], others to [1/1].
>
> What is the current status of this patch?
It's been waiting on feedback
>
> Here are my comments:
>
>
>> 2024/04/28 4:31, Clinton Bunch <cdb_zsh@zentaur.org> wrote:
>> Doc/Zsh/mod_random.yo
>> +item(tt(SRANDOM)) (
>> +A random positive 32-bit integer between 0 and 4,294,967,295.
> 'between 0 and 4,294,967,295'
> I think $SRAND can be 0; can it be 4,294,967,295, or only to
> 4,294,967,295-1?
4,294,967,295 is 2**32-1 (odd number)
>
>> +item(tt(zrand_float+LPAR()RPAR())) (
>> +Returns a random floating point number between 0 and 1.
> 'between 0 and 1' Is this inclusive?
> Can zrand_float() return 1?
Yes. it can return both 0 and 1.  I've specified inclusive in the 
documentation.
>    
>> +item(tt(zrand_int)+LPAR()tt(upper), tt(lower), tt(inclusive)RPAR()) (
>> +Returns a random integer between tt(lower) and tt(upper). All parameters are
>> +optional.  If none are specified it is equivalent to
>> +tt(SRANDOM).
>> +tt(inclusive) is a flag that controls whether the result is ever equal to
>> +tt(upper).  By default it is not.
> If 'inclusive' is 0 (off) by default, then
> 0 <= zrand_int() < 4,294,967,295 by default.
> Is this equivalent to $SRANDOM?
In the code it calls the same function as SRANDOM when no arguments are 
given.
>
>> Src/Modules/random.c
>> +#include <stdbool.h>
>> +#include <stdint.h>
> I guess stdbool.h is not used?
Apparently I removed all places where I used a bool type
>
> I think including stdint.h (new in C99 but may exist in earlier
> compilers) without #ifdef HAVE_STDINT_H is OK now, since we
> are now using C99 as the C language standard, right?
>
>> +/* buffer to pre-load integers for SRANDOM to lessen the context switches */
>> +uint32_t rand_buff[8];
> static uint32_t rand_buff[8];
>
>> +int
>> +boot_(Module m)
> UNUSED(m)
>
>
>> Src/Modules/random.mdd
>> +load=yes
> Does this module really need be autoloadable? I don't know what are
> the criteria that a module should be autoloadable.
I would think we'd want at least the SRANDOM parameter autoloaded
>
>
>> Test/V15random.ztst
> I don't know whether zsh need to check the "quality" of the random
> number. I guess the simple $RANDOM would also pass these tests.
RANDOM would not pass these tests.  They test the difference between the 
distribution of the generated values against the ideal equal distribution.
>
> Maybe better to check that $SRANDOM is 'non-repeatable'?
Since SRANDOM can't be set how would you prove non-repeatable?
>
> If we will do the quality check, then:
>
>> +  print -ru $ZTST_fd 'This test may take two seconds...'
> Why two seconds? A few seconds, or something like that?
I believe that was the result of timings during testing.  As I recall 
the other tests were more specific than a few.
>
>> +  function calc_chi2() {
>> +# Calculate Chi Squared
> (snip)
>> +       #echo
>> +  }
> Please remove several '#echo' (for debugging?).
Yes, they were for debugging.
>
> You can convert the functions calc_chi2(), ligf() etc. into
> math-functions by using 'functions -M'. Then you can call them as,
> for example (( p = 1 - ligf(s, z)/gamma(s) ))
>
>> +0q:random integer $samples samples between 1-$n
> The flag 'q' works only for the lines starting with < > or ?.
> $samples and $n are not expanded.
Okay explicit values used in messages.
>
> --
> Jun




^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH 1/1] zsh/random
  2024-06-17 23:52   ` Clinton Bunch
@ 2024-06-18  0:16     ` Clinton Bunch
  2024-06-18  2:10       ` Clinton Bunch
  2024-06-19 20:21       ` Bart Schaefer
  0 siblings, 2 replies; 7+ messages in thread
From: Clinton Bunch @ 2024-06-18  0:16 UTC (permalink / raw)
  To: zsh-workers

Also is anyone able to test on AIX or HP-UX?  Since neither have 
getrandom or arc4random and would fall back to /dev/urandom, I'd like to 
make sure they work.  I'm hoping someone else can test so I don't have 
to spend money to rent an AIX or HPE VM.

On 6/17/2024 18:52, Clinton Bunch wrote:
> On 6/17/2024 09:43, Jun. T wrote:
>> # Please do not separate a patch into [0/1] and [1/1].
>> # Some people reply to [0/1], others to [1/1].
>>
>> What is the current status of this patch?
> It's been waiting on feedback
>>
>> Here are my comments:
>>
>>
>>> 2024/04/28 4:31, Clinton Bunch <cdb_zsh@zentaur.org> wrote:
>>> Doc/Zsh/mod_random.yo
>>> +item(tt(SRANDOM)) (
>>> +A random positive 32-bit integer between 0 and 4,294,967,295.
>> 'between 0 and 4,294,967,295'
>> I think $SRAND can be 0; can it be 4,294,967,295, or only to
>> 4,294,967,295-1?
> 4,294,967,295 is 2**32-1 (odd number)
>>
>>> +item(tt(zrand_float+LPAR()RPAR())) (
>>> +Returns a random floating point number between 0 and 1.
>> 'between 0 and 1' Is this inclusive?
>> Can zrand_float() return 1?
> Yes. it can return both 0 and 1.  I've specified inclusive in the 
> documentation.
>>> +item(tt(zrand_int)+LPAR()tt(upper), tt(lower), tt(inclusive)RPAR()) (
>>> +Returns a random integer between tt(lower) and tt(upper). All 
>>> parameters are
>>> +optional.  If none are specified it is equivalent to
>>> +tt(SRANDOM).
>>> +tt(inclusive) is a flag that controls whether the result is ever 
>>> equal to
>>> +tt(upper).  By default it is not.
>> If 'inclusive' is 0 (off) by default, then
>> 0 <= zrand_int() < 4,294,967,295 by default.
>> Is this equivalent to $SRANDOM?
> In the code it calls the same function as SRANDOM when no arguments 
> are given.
>>
>>> Src/Modules/random.c
>>> +#include <stdbool.h>
>>> +#include <stdint.h>
>> I guess stdbool.h is not used?
> Apparently I removed all places where I used a bool type
>>
>> I think including stdint.h (new in C99 but may exist in earlier
>> compilers) without #ifdef HAVE_STDINT_H is OK now, since we
>> are now using C99 as the C language standard, right?
>>
>>> +/* buffer to pre-load integers for SRANDOM to lessen the context 
>>> switches */
>>> +uint32_t rand_buff[8];
>> static uint32_t rand_buff[8];
>>
>>> +int
>>> +boot_(Module m)
>> UNUSED(m)
>>
>>
>>> Src/Modules/random.mdd
>>> +load=yes
>> Does this module really need be autoloadable? I don't know what are
>> the criteria that a module should be autoloadable.
> I would think we'd want at least the SRANDOM parameter autoloaded
>>
>>
>>> Test/V15random.ztst
>> I don't know whether zsh need to check the "quality" of the random
>> number. I guess the simple $RANDOM would also pass these tests.
> RANDOM would not pass these tests.  They test the difference between 
> the distribution of the generated values against the ideal equal 
> distribution.
>>
>> Maybe better to check that $SRANDOM is 'non-repeatable'?
> Since SRANDOM can't be set how would you prove non-repeatable?
>>
>> If we will do the quality check, then:
>>
>>> +  print -ru $ZTST_fd 'This test may take two seconds...'
>> Why two seconds? A few seconds, or something like that?
> I believe that was the result of timings during testing.  As I recall 
> the other tests were more specific than a few.
>>
>>> +  function calc_chi2() {
>>> +# Calculate Chi Squared
>> (snip)
>>> +       #echo
>>> +  }
>> Please remove several '#echo' (for debugging?).
> Yes, they were for debugging.
>>
>> You can convert the functions calc_chi2(), ligf() etc. into
>> math-functions by using 'functions -M'. Then you can call them as,
>> for example (( p = 1 - ligf(s, z)/gamma(s) ))
>>
>>> +0q:random integer $samples samples between 1-$n
>> The flag 'q' works only for the lines starting with < > or ?.
>> $samples and $n are not expanded.
> Okay explicit values used in messages.
>>
>> -- 
>> Jun
>
>
>



^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH 1/1] zsh/random
  2024-06-18  0:16     ` Clinton Bunch
@ 2024-06-18  2:10       ` Clinton Bunch
  2024-06-19 20:21       ` Bart Schaefer
  1 sibling, 0 replies; 7+ messages in thread
From: Clinton Bunch @ 2024-06-18  2:10 UTC (permalink / raw)
  To: zsh-workers

On 6/17/2024 19:16, Clinton Bunch wrote:
>
> On 6/17/2024 18:52, Clinton Bunch wrote:
>> On 6/17/2024 09:43, Jun. T wrote:
>>>> Test/V15random.ztst
>>> I don't know whether zsh need to check the "quality" of the random
>>> number. I guess the simple $RANDOM would also pass these tests.
>> RANDOM would not pass these tests.  They test the difference between 
>> the distribution of the generated values against the ideal equal 
>> distribution.
>>
I was wrong.  Apparently, RANDOM isn't sufficiently biased to fail these 
tests.  I'm beginning to think there is no useful test that can be run 
on this module, but I'm open to suggestions.
>>
>
>



^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH 1/1] zsh/random
  2024-06-18  0:16     ` Clinton Bunch
  2024-06-18  2:10       ` Clinton Bunch
@ 2024-06-19 20:21       ` Bart Schaefer
  2024-06-19 20:37         ` Clinton Bunch
  1 sibling, 1 reply; 7+ messages in thread
From: Bart Schaefer @ 2024-06-19 20:21 UTC (permalink / raw)
  To: Clinton Bunch; +Cc: zsh-workers

On Mon, Jun 17, 2024 at 5:16 PM Clinton Bunch <cdb_zsh@zentaur.org> wrote:
>
> Also is anyone able to test on AIX or HP-UX?  Since neither have
> getrandom or arc4random and would fall back to /dev/urandom, I'd like to
> make sure they work.

Would it not suffice to change the #defines in config.h so as to force
use of /dev/urandom on some other architecture?


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH 1/1] zsh/random
  2024-06-19 20:21       ` Bart Schaefer
@ 2024-06-19 20:37         ` Clinton Bunch
  0 siblings, 0 replies; 7+ messages in thread
From: Clinton Bunch @ 2024-06-19 20:37 UTC (permalink / raw)
  To: zsh-workers

[-- Attachment #1: Type: text/plain, Size: 599 bytes --]


On 6/19/2024 3:21 PM, Bart Schaefer wrote:
> On Mon, Jun 17, 2024 at 5:16 PM Clinton Bunch<cdb_zsh@zentaur.org>  wrote:
>> Also is anyone able to test on AIX or HP-UX?  Since neither have
>> getrandom or arc4random and would fall back to /dev/urandom, I'd like to
>> make sure they work.
> Would it not suffice to change the #defines in config.h so as to force
> use of /dev/urandom on some other architecture?
Kind of an ugly post-configure hack, but it could be used for 
preliminary testing of the code.  Still before shipping it should be 
tested on the actual OS/architecture, IMO

[-- Attachment #2: Type: text/html, Size: 1283 bytes --]

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2024-06-19 20:38 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-04-27 19:31 [PATCH 1/1] zsh/random Clinton Bunch
2024-06-17 14:43 ` Jun. T
2024-06-17 23:52   ` Clinton Bunch
2024-06-18  0:16     ` Clinton Bunch
2024-06-18  2:10       ` Clinton Bunch
2024-06-19 20:21       ` Bart Schaefer
2024-06-19 20:37         ` Clinton Bunch

Code repositories for project(s) associated with this public inbox

	https://git.vuxu.org/mirror/zsh/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).