[latex3-commits] [git/LaTeX3-latex3-latex3] master: Change fp random implementation (be4ade7)

Bruno Le Floch bruno at le-floch.fr
Sun May 6 16:13:51 CEST 2018


Repository : https://github.com/latex3/latex3
On branch  : master
Link       : https://github.com/latex3/latex3/commit/be4ade7818d593a77cf9c9523e9c356a88af9ada

>---------------------------------------------------------------

commit be4ade7818d593a77cf9c9523e9c356a88af9ada
Author: Bruno Le Floch <bruno at le-floch.fr>
Date:   Sat May 5 18:54:34 2018 -0400

    Change fp random implementation
    
    Seeds with the same parity were (often) producing the same sequence
    of parities for the 16th digit of successive rand(), for instance.


>---------------------------------------------------------------

be4ade7818d593a77cf9c9523e9c356a88af9ada
 l3kernel/l3fp-random.dtx         |  396 +++++++++++++++++---------------------
 l3kernel/testfiles/m3rand001.tlg |   78 ++++----
 2 files changed, 211 insertions(+), 263 deletions(-)

diff --git a/l3kernel/l3fp-random.dtx b/l3kernel/l3fp-random.dtx
index 4deabe4..d259099 100644
--- a/l3kernel/l3fp-random.dtx
+++ b/l3kernel/l3fp-random.dtx
@@ -199,7 +199,12 @@
 %   ensures that the number obtained before the shift and division is at
 %   most $2^{17}(2^{14}-1)+(2^{17}-1)=\cs{c_max_int}$.  The
 %   non-uniformity is at most of order $2^{17}/2^{42} = 2^{-25}$.
-% \item Otherwise, split $R=R_2\times 2^{28}+R_1\times 2^{14}+R_0$,
+% \item Since it is not clear that the plan outlined in the following
+%   paragraph has good uniformity, we implement large ranges directly in
+%   terms of the floating point |randint|.
+%
+%   Earlier plan: \enquote{Otherwise,
+%   split $R=R_2\times 2^{28}+R_1\times 2^{14}+R_0$,
 %   precompute $R_{21}=R_2\times 2^{14}+R_1$ and
 %   $R_{10}=R_1\times 2^{14}+R_0$ and $Z = X_1\times 2^{14}+Y_1$.  Then
 %   compute on the one hand
@@ -210,133 +215,94 @@
 %   \operatorname{ediv}(R_{10}\times Y,2^{28}),2^{14})$ then sum them
 %   (this may give $2^{31}$, an overflow).  If the result is greater
 %   than $\meta{max}$ (in particular if it overflows) then replace it by
-%   $\meta{min}$.
+%   $\meta{min}$.}
 % \item To get a floating point number in $[0,1)$ just call the
 %   $R=10000\leq 2^{17}$ procedure above to produce four blocks of four
 %   digits.
-% \item To get an integer floating point number in a small range
-%   $R\leq 2^{17}$ use the above simplified algorithm.  If the range is
-%   bigger (it can be up to $2\times 10^{16}-1$), work with fixed-point
-%   numbers: get six times four digits to build a fixed point number,
-%   multiply by $R$ and add $\meta{min}$.
+% \item To get an integer floating point number in a range (whose size
+%   can be up to $2\times 10^{16}-1$), work with fixed-point numbers:
+%   get six times four digits to build a fixed point number, multiply by
+%   $R$ and add $\meta{min}$.  This requires some care because
+%   \pkg{l3fp-extended} only supports non-negative numbers.
 % \end{itemize}
-% This is strategy is not yet implemented at all.
 %
-% \begin{macro}[EXP]{\@@_rand_uniform:}
-% \begin{variable}
-%   {
-%     \c_@@_rand_size_int,
-%     \c_@@_rand_four_int,
-%     \c_@@_rand_eight_int,
-%   }
-%   The \cs{tex_uniformdeviate:D} primitive gives a pseudo-random
-%   integer in a range $[0,n-1]$ of the user's choice.  This number is
-%   meant to be uniformly distributed, but is produced by rescaling a
-%   uniform pseudo-random integer in $[0,2^{28}-1]$.  For instance,
-%   setting~$n$ to (any multiple of) $2^{29}$ gives only even values.
-%   Thus it is only safe to call \cs{tex_uniformdeviate:D} with
-%   argument $2^{28}$.  This integer is also used in the implementation
-%   of \cs{int_rand:nn}.  We also use variants of this number
-%   rounded down to multiples of $10^4$ and $10^8$.
+% \begin{variable}{\c_@@_rand_critical_int}
+%   Constant equal to $2^{17}$, the maximal size of a range that
+%   \cs{int_range:nn} can do with its \enquote{simple} algorithm.
 %    \begin{macrocode}
-    \cs_new:Npn \@@_rand_uniform:
-      { \tex_uniformdeviate:D \c_@@_rand_size_int }
-    \int_const:Nn \c_@@_rand_size_int   { 268 435 456 }
-    \int_const:Nn \c_@@_rand_four_int   { 268 430 000 }
-    \int_const:Nn \c_@@_rand_eight_int  { 200 000 000 }
+    \int_const:Nn \c_@@_rand_critical_int {   13 1072 }
 %    \end{macrocode}
 % \end{variable}
+%
+% \begin{macro}[EXP]{\@@_randint:nn}
+%   Used in an integer expression, \cs{@@_randint:nn} \Arg{min}
+%   |{|$R$|}| gives a random number
+%   $\lfloor(R\operatorname{random}(2^{14}) +
+%   \operatorname{random}(R))/2^{14}\rfloor$.  that is between
+%   \meta{min} and $\meta{min}+R-1$ inclusive.
+%    \begin{macrocode}
+    \cs_new:Npn \@@_randint:nn #1#2
+      {
+        #1 + (#2 * \tex_uniformdeviate:D 1 6384
+              + \tex_uniformdeviate:D #2 - 8192 ) / 16384
+      }
+%    \end{macrocode}
 % \end{macro}
 %
-% \begin{macro}[EXP]{\@@_rand_myriads:n}
 % \begin{macro}[EXP]
-%   {
-%     \@@_rand_myriads_loop:nn,
-%     \@@_rand_myriads_get:w,
-%     \@@_rand_myriads_last:,
-%     \@@_rand_myriads_last:w,
-%   }
-%   Used as \cs{@@_rand_myriads:n} |{XXX}| with one input character per
-%   block of four digit we want.  Given a pseudo-random integer from the
-%   primitive, we extract $2$ blocks of digits if possible, namely if
-%   the integer is less than $2\times 10^8$.  If that's not possible,
-%   we try to extract $1$~block, which succeeds in the range $[2\times
-%   10^8, 26843\times 10^4)$.  For the $5456$ remaining possible values
-%   we just throw away the random integer and get a new one.  Depending
-%   on whether we got $2$, $1$, or~$0$ blocks, remove the same number of
-%   characters from the input stream with \cs{use_i:nnn}, \cs{use_i:nn}
-%   or nothing.
+%   {\@@_rand_myriads:n, \@@_rand_myriads_loop:w, \@@_rand_myriads_get:w}
+%   Used as \cs{@@_rand_myriads:n} |{XXX}| with one letter |X|
+%   (specifically) per block of four digit we want; it expands to |;|
+%   followed by the requested number of brace groups, each containing
+%   four (pseudo-random) digits.  Digits are produced as a random number
+%   in $[10000,19999]$ for the usual reason of preserving leading zeros.
 %    \begin{macrocode}
     \cs_new:Npn \@@_rand_myriads:n #1
+      { \@@_rand_myriads_loop:w #1 \prg_break: X \prg_break_point: ; }
+    \cs_new:Npn \@@_rand_myriads_loop:w #1 X
       {
-        \@@_rand_myriads_loop:nn #1
-          { ? \use_i_delimit_by_q_stop:nw \@@_rand_myriads_last: }
-          { ? \use_none_delimit_by_q_stop:w } \q_stop
-      }
-    \cs_new:Npn \@@_rand_myriads_loop:nn #1#2
-      {
-        \use_none:n #2
+        #1
         \exp_after:wN \@@_rand_myriads_get:w
-        \int_value:w \@@_rand_uniform: ; {#1}{#2}
-      }
-    \cs_new:Npn \@@_rand_myriads_get:w #1 ;
-      {
-        \if_int_compare:w #1 < \c_@@_rand_eight_int
-          \exp_after:wN \use_none:n
-          \int_value:w \@@_int_eval:w
-            \c_@@_rand_eight_int + #1 \@@_int_eval_end:
-          \exp_after:wN \use_i:nnn
-        \else:
-          \if_int_compare:w #1 < \c_@@_rand_four_int
-            \exp_after:wN \use_none:nnnnn
-            \int_value:w \@@_int_eval:w
-              \c_@@_rand_four_int + #1 \@@_int_eval_end:
-            \exp_after:wN \exp_after:wN \exp_after:wN \use_i:nn
-          \fi:
-        \fi:
-        \@@_rand_myriads_loop:nn
-      }
-    \cs_new:Npn \@@_rand_myriads_last:
-      {
-        \exp_after:wN \@@_rand_myriads_last:w
-        \int_value:w \@@_rand_uniform: ;
-      }
-    \cs_new:Npn \@@_rand_myriads_last:w #1 ;
-      {
-        \if_int_compare:w #1 < \c_@@_rand_four_int
-          \exp_after:wN \use_none:nnnnn
-          \int_value:w \@@_int_eval:w
-            \c_@@_rand_four_int + #1 \@@_int_eval_end:
-        \else:
-          \exp_after:wN \@@_rand_myriads_last:
-        \fi:
+        \int_value:w \@@_int_eval:w
+          \@@_randint:nn { 10000 } { 10000 }
+        \@@_rand_myriads_loop:w
       }
+    \cs_new:Npn \@@_rand_myriads_get:w 1 #1 ; { ; {#1} }
 %    \end{macrocode}
 % \end{macro}
-% \end{macro}
 %
 % \subsection{Random floating point}
 %
-% \begin{macro}[EXP]{\@@_rand_o:Nw}
-% \begin{macro}[EXP]{\@@_rand_o:, \@@_rand_o:w}
+% \begin{macro}[EXP]{\@@_rand_o:Nw, \@@_rand_o:w}
 %   First we check that |random| was called without argument.  Then get
-%   four blocks of four digits.
+%   four blocks of four digits and convert that fixed point number to a
+%   floating point number (this correctly sets the exponent).  This has
+%   a minor bug: if all of the random numbers are zero then the result
+%   is correctly~$0$ but it raises the \texttt{underflow} flag; it
+%   should not do that.
 %    \begin{macrocode}
     \cs_new:Npn \@@_rand_o:Nw ? #1 @
       {
         \tl_if_empty:nTF {#1}
-          { \@@_rand_o: }
+          {
+            \exp_after:wN \@@_rand_o:w
+            \exp:w \exp_end_continue_f:w
+            \@@_rand_myriads:n { XXXX } { 0000 } { 0000 } ; 0
+          }
           {
             \__kernel_msg_expandable_error:nnnnn
               { kernel } { fp-num-args } { rand() } { 0 } { 0 }
             \exp_after:wN \c_nan_fp
           }
       }
-    \cs_new:Npn \@@_rand_o:
-      { \@@_parse_o:n { . \@@_rand_myriads:n { xxxx } } }
+    \cs_new:Npn \@@_rand_o:w ;
+      {
+        \exp_after:wN \@@_sanitize:Nw
+        \exp_after:wN 0
+        \int_value:w \@@_int_eval:w \c_zero \@@_fixed_to_float_o:wN
+      }
 %    \end{macrocode}
 % \end{macro}
-% \end{macro}
 %
 % \subsection{Random integer}
 %
@@ -346,33 +312,31 @@
 %     \@@_randint_default:w,
 %     \@@_randint_badarg:w,
 %     \@@_randint_o:w,
-%     \@@_randint_e:wnn,
-%     \@@_randint_e:wwNnn,
-%     \@@_randint_e:wwwNnn,
-%     \@@_randint_narrow_e:nnnn,
-%     \@@_randint_wide_e:nnnn,
-%     \@@_randint_wide_e:wnnn,
+%     \@@_randint_auxi_o:ww,
+%     \@@_randint_auxii:wn,
+%     \@@_randint_auxiii_o:ww,
+%     \@@_randint_auxiv_o:ww,
+%     \@@_randint_auxv_o:w,
 %   }
 %     Enforce that there is one argument (then add first argument~$1$)
-%     or two arguments.  Enforce that they are integers in
-%     $(-10^{16},10^{16})$ and ordered.  We distinguish narrow ranges
-%     (less than $2^{28}$) from wider ones.
-%
-%     For narrow ranges, compute the number~$n$ of possible outputs as
-%     an integer using \cs{fp_to_int:n}, and reduce a pseudo-random
-%     $28$-bit integer~$r$ modulo~$n$.  On its own, this is not uniform
-%     when $[0,2^{28}-1]$ does not divide evenly into intervals of
-%     size~$n$.  The auxiliary \cs{@@_randint_e:wwwNnn} discards the
-%     pseudo-random integer if it lies in an incomplete interval, and
-%     repeats.
-%
-%     For wide ranges we use the same code except for the last eight
-%     digits which use \cs{@@_rand_myriads:n}.  It is not safe to
-%     combine the first digits with the last eight as a single string of
-%     digits, as this may exceed $16$~digits and be rounded.  Instead,
-%     we first add the first few digits (times $10^8$) to the lower
-%     bound.  The result is compared to the upper bound and the process
-%     repeats if needed.
+%     or two arguments.  Call \cs{@@_randint_badarg:w} on each; this
+%     function inserts |1| \cs{exp_stop_f:} to end the \cs{if_case:w}
+%     statement if either the argument is not an integer or if its
+%     absolute value is $\geq 10^{16}$.  Also bail out if
+%     \cs{@@_compare_back:ww} yields~|1|, meaning that the bounds are
+%     not in the right order.  Otherwise an auxiliary converts each
+%     argument times $10^{-16}$ (hence the shift in exponent) to a
+%     $24$-digit fixed point number (see \pkg{l3fp-extended}).
+%     Then compute the number of choices, $\meta{max}+1-\meta{min}$.
+%     Create a random $24$-digit fixed-point number with
+%     \cs{@@_rand_myriads:n}, then use a fused multiply-add instruction
+%     to multiply the number of choices to that random number and add it
+%     to \meta{min}.  Then truncate to $16$ digits (namely select the
+%     integer part of $10^{16}$ times the result) before converting back
+%     to a floating point number  (\cs{@@_sanitize:Nw} takes care of zero).
+%     To avoid issues with negative numbers, add $1$ to all fixed point
+%     numbers (namely $10^{16}$ to the integers they represent), except
+%     of course when it is time to convert back to a float.
 %    \begin{macrocode}
     \cs_new:Npn \@@_randint_o:Nw ?
       {
@@ -387,7 +351,7 @@
           {
             \if_meaning:w 1 #1
               \if_int_compare:w
-                \use_i_delimit_by_q_stop:nw #3 \q_stop > \c_@@_prec_int
+                  \@@_use_i_until_s:nw #3 ; > \c_@@_prec_int
                 1 \exp_stop_f:
               \fi:
             \fi:
@@ -399,9 +363,9 @@
         \if_case:w
             \@@_randint_badarg:w #1;
             \@@_randint_badarg:w #2;
-            \fp_compare:nNnTF { #1; } > { #2; } { 1 } { 0 } \exp_stop_f:
-          \exp_after:wN \exp_after:wN \exp_after:wN \@@_randint_e:wnn
-            \@@_parse:n { #2; - #1; } { #1; } { #2; }
+            \if:w 1 \@@_compare_back:ww #2; #1; 1 \exp_stop_f: \fi:
+            0 \exp_stop_f:
+          \@@_randint_auxi_o:ww #1; #2;
         \or:
           \@@_invalid_operation_tl_o:ff
             { randint } { \@@_array_to_clist:n { #1; #2; } }
@@ -409,134 +373,118 @@
         \fi:
         \exp_after:wN \exp_end:
       }
-    \cs_new:Npn \@@_randint_e:wnn #1;
+    \cs_new:Npn \@@_randint_auxi_o:ww #1 ; #2 ; #3 \exp_end:
       {
-        \exp_after:wN \@@_randint_e:wwNnn
-        \int_value:w \@@_rand_uniform: \exp_after:wN ;
-        \exp:w \exp_end_continue_f:w
-          \fp_compare:nNnTF { #1 ; } < \c_@@_rand_size_int
-            { \fp_to_int:n { #1 ; + 1 } ; \@@_randint_narrow_e:nnnn }
-            {
-              \fp_to_int:n { floor(#1 ; * 1e-8 + 1) } ;
-              \@@_randint_wide_e:nnnn
-            }
+        \fi:
+        \@@_randint_auxii:wn #2 ;
+        { \@@_randint_auxii:wn #1 ; \@@_randint_auxiii_o:ww }
       }
-    \cs_new:Npn \@@_randint_e:wwNnn #1 ; #2 ;
+    \cs_new:Npn \@@_randint_auxii:wn \s_@@ \@@_chk:w 1#1#2#3 ;
       {
-        \exp_after:wN \@@_randint_e:wwwNnn
-        \int_value:w \int_mod:nn {#1} {#2} ; #1 ; #2 ;
+        \exp_after:wN \@@_ep_to_fixed:wwn
+        \int_value:w \@@_int_eval:w
+          #2 - \c_@@_prec_int , #3 {0000} {0000} ;
+        {
+          \if_meaning:w 0 #1
+            \exp_after:wN \use_i:nnnn
+            \exp_after:wN \@@_fixed_add_one:wN
+          \fi:
+          \exp_after:wN \@@_fixed_sub:wwn \c_@@_one_fixed_tl
+        }
+        \@@_fixed_continue:wn
       }
-    \cs_new:Npn \@@_randint_e:wwwNnn #1 ; #2 ; #3 ; #4
+    \cs_new:Npn \@@_randint_auxiii_o:ww #1 ; #2 ;
       {
-        \int_compare:nNnTF { #2 - #1 + #3 } > \c_@@_rand_size_int
-          {
-            \exp_after:wN \@@_randint_e:wwNnn
-              \int_value:w \@@_rand_uniform: ; #3 ; #4
-          }
-          { #4 {#1} {#3} }
+        \@@_fixed_add:wwn #2 ;
+          {0000} {0000} {0000} {0001} {0000} {0000} ;
+        \@@_fixed_sub:wwn #1 ;
+        {
+          \exp_after:wN \use_i:nn
+          \exp_after:wN \@@_fixed_mul_add:wwwn
+          \exp:w \exp_end_continue_f:w \@@_rand_myriads:n { XXXXXX } ;
+        }
+        #1 ;
+        \@@_randint_auxiv_o:ww
+        #2 ;
+        \@@_randint_auxv_o:w #1 ; @
       }
-    \cs_new:Npn \@@_randint_narrow_e:nnnn #1#2#3#4
-      { \@@_parse_o:n { #3 + #1 } \exp:w }
-    \cs_new:Npn \@@_randint_wide_e:nnnn #1#2#3#4
+    \cs_new:Npn \@@_randint_auxiv_o:ww #1#2#3#4#5 ; #6#7#8#9
       {
-        \exp_after:wN \exp_after:wN
-        \exp_after:wN \@@_randint_wide_e:wnnn
-          \@@_parse:n { #3 + #1e8 + \@@_rand_myriads:n { xx } }
-          {#2} {#3} {#4}
+        \if_int_compare:w
+            \if_int_compare:w #1#2 > #6#7 \exp_stop_f: 1 \else:
+            \if_int_compare:w #1#2 < #6#7 \exp_stop_f: - \fi: \fi:
+            #3#4 > #8#9 \exp_stop_f:
+          \@@_use_i_until_s:nw
+        \fi:
+        \@@_randint_auxv_o:w {#1}{#2}{#3}{#4}#5
       }
-    \cs_new:Npn \@@_randint_wide_e:wnnn #1 ; #2#3#4
+    \cs_new:Npn \@@_randint_auxv_o:w #1#2#3#4#5 ; #6 @
       {
-        \fp_compare:nNnTF { #1 ; } > {#4}
-          {
-            \exp_after:wN \@@_randint_e:wwNnn
-              \int_value:w \@@_rand_uniform: ; #2 ;
-              \@@_randint_wide_e:nnnn {#3} {#4}
-          }
-          { \@@_exp_after_o:w #1 ; \exp:w }
+        \exp_after:wN \@@_sanitize:Nw
+        \int_value:w
+        \if_int_compare:w #1 < 10000 \exp_stop_f:
+          2
+        \else:
+          0
+          \exp_after:wN \exp_after:wN
+          \exp_after:wN \@@_reverse_args:Nww
+        \fi:
+        \exp_after:wN \@@_fixed_sub:wwn \c_@@_one_fixed_tl
+        {#1} {#2} {#3} {#4} {0000} {0000} ;
+        {
+          \exp_after:wN \exp_stop_f:
+          \int_value:w \@@_int_eval:w \c_@@_prec_int
+            \@@_fixed_to_float_o:wN
+        }
+        0
+        \exp:w \exp_after:wN \exp_end:
       }
 %    \end{macrocode}
 % \end{macro}
 % \end{macro}
 %
-% \begin{macro}{\int_rand:nn}
-% \begin{macro}{\@@_randint:ww}
-% \begin{macro}{\@@_randint_narrow:n}
-% \begin{macro}{\@@_randint_narrow:nn}
-% \begin{macro}{\@@_randint_narrow:nnn}
+% \begin{macro}{\int_rand:nn, \@@_randint:ww}
 %   Evaluate the argument and filter out the case where the lower
 %   bound~|#1| is more than the upper bound~|#2|.  Then determine
-%   whether the range is narrower than \cs{c_@@_rand_size_int}; |#2-#1|
-%   may overflow for very large positive~|#2| and negative~|#1|.  If the
-%   range is wide, use slower code from \pkg{l3fp}.  If the range is
-%   narrow, call \cs{@@_randint_narrow:n} \Arg{choices} where
+%   whether the range is narrower than \cs{c_@@_rand_critical_int};
+%   |#2-#1| may overflow for very large positive~|#2| and negative~|#1|.
+%   If the range is wide, use slower code from \pkg{l3fp}.  If the range
+%   is narrow, call \cs{@@_randint:nn} |{#1}| \Arg{choices} where
 %   \meta{choices} is the number of possible outcomes.
-%
-%   One option then would be to reduce a random number modulo
-%   \meta{choices}, but the low-order bits of random numbers provided by
-%   the primitive have poor randomness: empirical tests indicate that
-%   taking random numbers modulo~$2$ gives only two possible sequences
-%   of bits depending on the seed's parity.  Instead, fit in the output
-%   range $[0,\cs{c_@@_rand_size_int}-1]$ of the random number generator
-%   \meta{choices} equal-size intervals of maximum possible size
-%   (obtained using \cs{int_div_truncate:nn}).  Given a random number,
-%   divide it by the size to determine which interval it falls into; if
-%   it is beyond \meta{choices}, try another random number.
 %    \begin{macrocode}
     \cs_new:Npn \int_rand:nn #1#2
       {
-        \exp_after:wN \@@_randint:ww
-        \int_value:w \int_eval:n {#1} \exp_after:wN ;
-        \int_value:w \int_eval:n {#2} ;
-      }
-    \cs_new:Npn \@@_randint:ww #1; #2;
-      {
-        \int_compare:nNnTF {#1} > {#2}
+        \int_eval:n
           {
-            \__kernel_msg_expandable_error:nnnn
-              { kernel } { randint-backward-range } {#1} {#2}
-            \@@_randint:ww #2; #1;
-          }
-          {
-            \int_compare:nNnTF {#1} > 0
-              { \int_compare:nNnTF { #2 - #1 } < \c_@@_rand_size_int }
-              { \int_compare:nNnTF {#2} < { #1 + \c_@@_rand_size_int } }
-                  {
-                    \int_eval:n
-                      {
-                        #1 +
-                        \exp_args:Nf \@@_randint_narrow:n
-                          { \int_eval:n { #2 - #1 + 1 } }
-                      }
-                  }
-                  { \fp_to_int:n { randint(#1,#2) } }
+            \exp_after:wN \@@_randint:ww
+            \int_value:w \int_eval:n {#1} \exp_after:wN ;
+            \int_value:w \int_eval:n {#2} ;
           }
       }
-    \cs_new:Npn \@@_randint_narrow:n #1
-      {
-        \exp_args:Nf \@@_randint_narrow:nn
-          { \int_div_truncate:nn \c_@@_rand_size_int {#1} } {#1}
-      }
-    \cs_new:Npn \@@_randint_narrow:nn #1
-      {
-        \exp_args:Nf \@@_randint_narrow:nnn
-          {
-            \int_div_truncate:nn
-              { \tex_uniformdeviate:D \c_@@_rand_size_int } {#1}
-          }
-          {#1}
-      }
-    \cs_new:Npn \@@_randint_narrow:nnn #1#2#3
+    \cs_new:Npn \@@_randint:ww #1; #2;
       {
-        \int_compare:nNnTF {#1} < {#3}
-          {#1}
-          { \@@_randint_narrow:nn {#2} {#3} }
+        \if_int_compare:w #1 > #2 \exp_stop_f:
+          \__kernel_msg_expandable_error:nnnn
+            { kernel } { randint-backward-range } {#1} {#2}
+          \@@_randint:ww #2; #1;
+        \else:
+          \if_int_compare:w \@@_int_eval:w #2
+              \if_int_compare:w #1 > \c_zero
+                - #1 < \@@_int_eval:w
+              \else:
+                < \@@_int_eval:w #1 +
+              \fi:
+              \c_@@_rand_critical_int
+              \@@_int_eval_end:
+            \@@_randint:nn {#1}
+              { \@@_int_eval:w #2 - #1 + 1 \@@_int_eval_end: }
+          \else:
+            \fp_to_int:n { randint(#1,#2) }
+          \fi:
+        \fi:
       }
 %    \end{macrocode}
 % \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
 %
 % End the initial conditional that ensures these commands are only
 % defined in engines that support random numbers.
diff --git a/l3kernel/testfiles/m3rand001.tlg b/l3kernel/testfiles/m3rand001.tlg
index ff4d480..138d849 100644
--- a/l3kernel/testfiles/m3rand001.tlg
+++ b/l3kernel/testfiles/m3rand001.tlg
@@ -4,40 +4,40 @@ Author: Bruno Le Floch
 ============================================================
 TEST 1: Random floating point number
 ============================================================
-0.6462368894354278
-0.9436367545979044
-0.4852789154091621
-0.177206678619497
-0.5709297441167938
+0.240735155533066
+0.5852214353430764
+0.7180176671856077
+0.3615756894278821
+0.4478174648957779
 ============================================================
 ============================================================
 TEST 2: Random integer
 ============================================================
 2
 1
-2
 1
+2
 1
 -9999999999999999
--9999999999999999
 -9999999999999998
 -9999999999999999
 -9999999999999999
-444814453
-983095862
-958440641
-448535503
-361158142
-6698906031397161
-4488284872942466
-6862012124606703
-4745501327166187
-9558266238979667
--8367420653049596
--616799027378363
-1497734804749083
-6603444483764452
--9258032600444501
+-9999999999999999
+174462024
+43469076
+964237967
+281796922
+571531749
+647645817006079
+6227277400369880
+4448422924240445
+5041817501660381
+2577799052913806
+8570669930344415
+4534632579885364
+9640121450586277
+928036911121429
+-493197266565851
 ============================================================
 ============================================================
 TEST 3: Random bad arguments
@@ -218,7 +218,7 @@ nan
 nan
 nan
 nan
-1
+0
 nan
 nan
 nan
@@ -226,52 +226,52 @@ nan
 ============================================================
 TEST 4: Random integer in a range
 ============================================================
-|0|-1|0|1|0|1|0|0|-1|1|-1|0|1|1|0|
+|-1|1|-1|1|0|0|1|-1|1|0|1|1|-1|-1|1|
 |-2147483647|-2147483647|-2147483647|-2147483647|-2147483647|-2147483647|-2147483647|-2147483647|-2147483647|-2147483647|-2147483647|-2147483647|-2147483647|-2147483647|-2147483647|
-|-2147483638|-2147483643|-2147483642|-2147483642|-2147483642|-2147483645|-2147483641|-2147483646|-2147483646|-2147483644|-2147483647|-2147483639|-2147483642|-2147483645|-2147483646|
-|-1383802119|1847464213|1896138077|1141097059|-1805716453|303683616|630789435|83660916|1553919714|2065890169|-143934675|-1879794472|-640520242|1146446601|-1026621466|
+|-2147483645|-2147483647|-2147483645|-2147483639|-2147483646|-2147483641|-2147483637|-2147483641|-2147483637|-2147483643|-2147483639|-2147483642|-2147483644|-2147483637|-2147483645|
+|-596602719|-440743317|1859629516|1035797053|-83292061|1733661836|-1455563191|-1354595511|1588647823|215900686|-1947340226|-1997076099|-1997696982|378940000|1354742325|
 ============================================================
 ============================================================
 TEST 5: Random tl item
 ============================================================
 ||
 |\A |\A |\A |\A |\A |\A |\A |\A |\A |\A |\A |\A |\A |\A |\A |
-|\A |\A | \B |\A | \B | \B | \B | \B | \B |\A | \B | \B | \B |\A | \B |
-|\C |\A |\C |\C |\A |{\B }|\A |{\B }|\C |{\B }|\A |\A |{\B }|\C |{\B }|
+| \B |\A |\A |\A | \B |\A |\A |\A | \B |\A | \B |\A |\A | \B |\A |
+|{\B }|{\B }|\C |\A |{\B }|\A |\A |\C |\A |{\B }|\C |\C |\A |{\B }|\A |
 ||||||||||||||||
 ||||||||||||||||
-| | | | | |||| | || | | ||
-| | | |\A |&|\A |##|##|##| |##|##|\A | |\A |
+| | || | |||| | | | || ||
+|\A |&| |&|&|##|&|&| |\A |##| |##|&| |
 ============================================================
 ============================================================
 TEST 6: Random clist item
 ============================================================
 ||
-|\B |\A |\A |\B |\B |\B |\A |\B |\B |\A |\B |\B |\B |\A |\B |
-|{}|\A |\A |\A |\A |\A |\A |\A |\A | \B |\A |\A |{}|\A | ,|
+|\A |\B |\A |\B |\A |\A |\A |\B |\A |\B |\A |\A |\A |\B |\B |
+| ,|\A |\A | \B |{}|\A | ,|{}| \B | \B |\A |\A | ,| \B | \B |
 ||||||||||||||||
-| ,|| ,|| ,|| ,||||| ,|| ,||
-|,|,|##|,|,|\\|##|,|&|\\|\\|##|,|,|,|
+| ,| ,|| ,| ,| ,| ,| ,|| ,| ,| ,|| ,||
+|##|##|,|,|##|##|##|&|\A |,|\A |,|\\|\A |&|
 ============================================================
 ============================================================
 TEST 7: Random seq item
 ============================================================
 ||||||||||||||||
-| ,| ,| ,|| ,| ,| ,||| ,| ,||| ,||
-|,|\A |##|&|\A |,|\A |\A |,|##|\A |\A |##|&|##|
+| ,|| ,| ,| ,||||| ,|| ,| ,|||
+|\A |,|##|{\\}|\A |\A |\A |##|{\\}|,|##|\A |&|,|&|
 ============================================================
 ============================================================
 TEST 8: Random prop key/value
 ============================================================
 ||||||||||||||||
-|{\a }{\A }|{\b }{{\B }}|{\a }{\A }|{\e }{}|{\a }{\A }|{\a }{\A }|{\b }{{\B }}|{\e }{}|{\a }{\A }|{\a }{\A }|{\a }{\A }|{\e }{}|{\b }{{\B }}|{\a }{\A }|{\a }{\A }|
+|{\b }{{\B }}|{\a }{\A }|{\b }{{\B }}|{\b }{{\B }}|{\a }{\A }|{\a }{\A }|{\e }{}|{\a }{\A }|{\e }{}|{\e }{}|{\a }{\A }|{\e }{}|{\b }{{\B }}|{\e }{}|{\b }{{\B }}|
 ============================================================
 ============================================================
 TEST 9: sys rand commands
 ============================================================
 1,71
 0,25
-0,73
+0,36
 1,71
-1,10
+1,55
 ============================================================





More information about the latex3-commits mailing list