File 8691-Use-more-bits-when-shuffling.patch of Package erlang

From 8916501b5059c161a87a9e898396b7fd0520e593 Mon Sep 17 00:00:00 2001
From: Raimo Niskanen <raimo@erlang.org>
Date: Fri, 21 Nov 2025 16:16:58 +0100
Subject: [PATCH 1/2] Use more bits when shuffling

Also, use specified algorithms when measuring.
---
 lib/stdlib/src/rand.erl        | 62 +++++++++++++++++++---------------
 lib/stdlib/test/rand_SUITE.erl | 45 +++++++++++++++++-------
 2 files changed, 67 insertions(+), 40 deletions(-)

diff --git a/lib/stdlib/src/rand.erl b/lib/stdlib/src/rand.erl
index 7298e65b32..ff9562c90b 100644
--- a/lib/stdlib/src/rand.erl
+++ b/lib/stdlib/src/rand.erl
@@ -533,6 +533,7 @@ the generator's range:
 		   exs1024_next/1, exs1024_calc/2,
                    exro928_next_state/4,
                    exrop_next/1, exrop_next_s/2,
+                   shuffle_new_bits/1,
                    mwc59_value/1,
 		   get_52/1, normal_kiwi/1]}).
 
@@ -606,7 +607,7 @@ the generator's range:
 -type alg_handler() ::
         #{type := alg(),
           bits => non_neg_integer(),
-          weak_low_bits => non_neg_integer(),
+          weak_low_bits => 0..3,
           max => non_neg_integer(), % Deprecated
           next :=
               fun ((alg_state()) -> {non_neg_integer(), alg_state()}),
@@ -1503,17 +1504,9 @@ and the [`NewState`](`t:state/0`).
               when
       List         :: list(),
       State        :: state().
-shuffle_s(List, {#{bits:=_, next:=Next} = AlgHandler, R0})
+shuffle_s(List, {AlgHandler, R0})
   when is_list(List) ->
-    WeakLowBits = maps:get(weak_low_bits, AlgHandler, 0),
-    [P0|S0] = shuffle_init_bitstream(R0, Next, WeakLowBits),
-    {ShuffledList, _P1, [R1|_]=_S1} = shuffle_r(List, [], P0, S0),
-    {ShuffledList, {AlgHandler, R1}};
-shuffle_s(List, {#{max:=_, next:=Next} = AlgHandler, R0})
-  when is_list(List) ->
-    %% Old spec - assume 2 weak low bits
-    WeakLowBits = 2,
-    [P0|S0] = shuffle_init_bitstream(R0, Next, WeakLowBits),
+    [P0|S0] = shuffle_init_bitstream(R0, AlgHandler),
     {ShuffledList, _P1, [R1|_]=_S1} = shuffle_r(List, [], P0, S0),
     {ShuffledList, {AlgHandler, R1}}.
 
@@ -1607,9 +1600,9 @@ shuffle_r([], Acc0, P0, S0, Zero, One, Two, Three) ->
     {Acc3, P3, S3} = shuffle_r(Two, Acc2, P2, S2),
     shuffle_r(Three, Acc3, P3, S3);
 shuffle_r([X | L], Acc, P0, S, Zero, One, Two, Three)
-  when is_integer(P0), 3 < P0, P0 < 1 bsl 57 ->
+  when is_integer(P0), ?BIT(2) =< P0, P0 =< ?MASK(59) ->
     P1 = P0 bsr 2,
-    case P0 band 3 of
+    case ?MASK(2, P0) of
         0 -> shuffle_r(L, Acc, P1, S, [X | Zero], One, Two, Three);
         1 -> shuffle_r(L, Acc, P1, S, Zero, [X | One], Two, Three);
         2 -> shuffle_r(L, Acc, P1, S, Zero, One, [X | Two], Three);
@@ -1621,8 +1614,8 @@ shuffle_r([_ | _] = L, Acc, _P, S0, Zero, One, Two, Three) ->
 
 %% Permute 2 elements
 shuffle_r_2(X, Acc, P, S, Y)
-  when is_integer(P), 1 < P, P < 1 bsl 57 ->
-    {case P band 1 of
+  when is_integer(P), ?BIT(1) =< P, P =< ?MASK(59) ->
+    {case ?MASK(1, P) of
          0 -> [Y, X | Acc];
          1 -> [X, Y | Acc]
      end, P bsr 1, S};
@@ -1636,9 +1629,9 @@ shuffle_r_2(X, Acc, _P, S0, Y) ->
 %% to reject and retry, which on average is 3 * 4/3
 %% (infinite sum of (1/4)^k) = 4 bits per permutation
 shuffle_r_3(X, Acc, P0, S, Y, Z)
-  when is_integer(P0), 7 < P0, P0 < 1 bsl 57 ->
+  when is_integer(P0), ?BIT(3) =< P0, P0 =< ?MASK(59) ->
     P1 = P0 bsr 3,
-    case P0 band 7 of
+    case ?MASK(3, P0) of
         0 -> {[Z, Y, X | Acc], P1, S};
         1 -> {[Y, Z, X | Acc], P1, S};
         2 -> {[Z, X, Y | Acc], P1, S};
@@ -1652,24 +1645,37 @@ shuffle_r_3(X, Acc, _P, S0, Y, Z) ->
     [P|S1] = shuffle_new_bits(S0),
     shuffle_r_3(X, Acc, P, S1, Y, Z).
 
--dialyzer({no_improper_lists, shuffle_init_bitstream/3}).
 %%
-shuffle_init_bitstream(R, Next, WeakLowBits) ->
+shuffle_init_bitstream(R, #{bits:=Bits, next:=Next} = AlgHandler) ->
+    Mask = ?MASK(Bits),
+    Shift = maps:get(weak_low_bits, AlgHandler, 0),
+    shuffle_init_bitstream(R, Next, Shift, Mask);
+shuffle_init_bitstream(R, #{max:=Mask, next:=Next}) ->
+    %% Old spec - assume 2 weak low bits
+    Shift = 2,
+    shuffle_init_bitstream(R, Next, Shift, Mask).
+%%
+-dialyzer({no_improper_lists, shuffle_init_bitstream/4}).
+shuffle_init_bitstream(R, Next, Shift, Mask0) ->
+    Mask = ?MASK(58, Mask0),    % Limit the mask to avoid bignum
     P = 1,                      % Marker for out of random bits
-    W = {Next,WeakLowBits},     % Generator
+    W = {Next,Shift,Mask},      % Generator
     S = [R|W],                  % Generator state
     [P|S].                      % Bit cash and state
 
 -dialyzer({no_improper_lists, shuffle_new_bits/1}).
 %%
-shuffle_new_bits([R0|{Next,WeakLowBits}=W]) ->
-    {V, R1} = Next(R0),
-    %% Setting the top bit M here provides the marker
-    %% for when we are out of random bits: P =:= 1
-    M = 1 bsl 56,
-    P = ((V bsr WeakLowBits) band (M-1)) bor M,
-    S = [R1|W],
-    [P|S].
+shuffle_new_bits([R0|{Next,Shift,Mask}=W])
+  when is_integer(Shift), 0 =< Shift, Shift =< 3,
+       is_integer(Mask), 0 < Mask, Mask =< ?MASK(58) ->
+    case Next(R0) of
+        {V, R1} when is_integer(V) ->
+            %% Setting the top bit here provides the marker
+            %% for when we are out of random bits: P =:= 1
+            P = ((V bsr Shift) band Mask) bor (Mask + 1),
+            S = [R1|W],
+            [P|S]
+    end.
 
 %% =====================================================================
 %% Internal functions
diff --git a/lib/stdlib/test/rand_SUITE.erl b/lib/stdlib/test/rand_SUITE.erl
index b94a21285d..da40195fb8 100644
--- a/lib/stdlib/test/rand_SUITE.erl
+++ b/lib/stdlib/test/rand_SUITE.erl
@@ -55,8 +55,6 @@ suite() ->
 all() ->
     [seed, interval_int, interval_float,
      bytes_count,
-     shuffle_elements, shuffle_reference,
-     basic_stats_shuffle, measure_shuffle,
      api_eq,
      mwc59_api,
      exsp_next_api, exsp_jump_api,
@@ -65,7 +65,8 @@ all() ->
      uniform_real_conv,
      plugin, measure,
      {group, reference_jump},
-     short_jump
+     short_jump,
+     {group, shuffle}
     ].
 
 groups() ->
@@ -75,6 +74,9 @@ groups() ->
     [{basic_stats, [parallel],
       [basic_stats_uniform_1, basic_stats_uniform_2, basic_stats_bytes,
        basic_stats_standard_normal]},
+     {shuffle, [],
+      [shuffle_elements, shuffle_reference,
+       basic_stats_shuffle, measure_shuffle]},
      {distr_stats, [parallel],
       [stats_standard_normal_box_muller,
        stats_standard_normal_box_muller_2,
@@ -89,6 +91,9 @@ group(distr_stats) ->
     %% valgrind needs a lot of time
     [{timetrap,{minutes,10}}];
 group(reference_jump) ->
+    %% valgrind needs a lot of time
+    [{timetrap,{minutes,10}}];
+group(shuffle) ->
     %% valgrind needs a lot of time
     [{timetrap,{minutes,10}}].
 
@@ -414,10 +419,13 @@ bytes_count(Config) when is_list(Config) ->
 %% Check that shuffle doesn't loose or duplicate elements
 
 shuffle_elements(Config) when is_list(Config) ->
-    M = 20,
-    SortedList = lists:seq(0, (1 bsl M) - 1),
+    SortedList = lists:seq(1, 1010_101),
     State = rand:seed(default),
-    case lists:sort(rand:shuffle(SortedList)) of
+    {ShuffledList, NewState} = rand:shuffle_s(SortedList, State),
+    true = ShuffledList =:= rand:shuffle(SortedList),
+    NewSeed = rand:export_seed_s(NewState),
+    NewSeed = rand:export_seed(),
+    case lists:sort(ShuffledList) of
         SortedList -> ok;
         _ ->
             error({mismatch, State})
@@ -428,13 +436,26 @@ shuffle_elements(Config) when is_list(Config) ->
 %% Check that shuffle is repeatable
 
 shuffle_reference(Config) when is_list(Config) ->
-    M = 20,
+    M    = 20,
+    List = lists:seq(0, (1 bsl M) - 1),
     Seed = {1,2,3},
-    MD5 = <<56,202,188,237,192,69,132,182,227,54,33,68,45,74,208,89>>,
-    %%
-    SortedList = lists:seq(0, (1 bsl M) - 1),
-    S = rand:seed_s(default, Seed),
-    {ShuffledList, NewS} = rand:shuffle_s(SortedList, S),
+    Ref  =
+        [{exsss,
+          <<124,54,150,191,198,136,245,103,157,213,96,6,210,103,134,107>>},
+         {exro928ss,
+          <<160,170,223,95,44,254,192,107,145,180,236,235,102,110,72,131>>},
+         {exrop,
+          <<175,236,222,199,129,54,205,86,81,38,92,219,66,71,30,69>>},
+         {exs1024s,
+          <<148,169,164,28,198,202,108,206,123,68,189,26,116,210,82,116>>},
+         {exsp,
+          <<63,163,228,59,249,88,205,251,225,174,227,65,144,130,169,191>>}],
+    [shuffle_reference(M, List, Seed, Alg, MD5) || {Alg, MD5} <- Ref],
+    ok.
+
+shuffle_reference(M, List, Seed, Alg, MD5) ->
+    S = rand:seed_s(Alg, Seed),
+    {ShuffledList, NewS} = rand:shuffle_s(List, S),
     Data = mk_iolist(ShuffledList, M),
     case erlang:md5(Data) of
         MD5 -> ok;
@@ -517,7 +538,7 @@ measure_shuffle(Config) when is_list(Config) ->
     end;
 measure_shuffle(Effort) when is_integer(Effort) ->
     Algs =
-        [default, exs1024 |
+        [exsss, exs1024 |
          case crypto_support() of
              ok -> [crypto];
              _  -> []
-- 
2.51.0

openSUSE Build Service is sponsored by