summaryrefslogtreecommitdiffstats
path: root/lib/randint.c
blob: ccf268bed945a82901dbc1fe308c13bc03e63ad1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
/* Generate random integers.

   Copyright (C) 2006-2023 Free Software Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */

/* Written by Paul Eggert.  */

#include <config.h>

#include "randint.h"

#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>


#if TEST
# include <inttypes.h>
# include <stdio.h>

int
main (int argc, char **argv)
{
  randint i;
  randint n = strtoumax (argv[1], nullptr, 10);
  randint choices = strtoumax (argv[2], nullptr, 10);
  char const *name = argv[3];
  struct randint_source *ints = randint_all_new (name, SIZE_MAX);

  for (i = 0; i < n; i++)
    printf ("%"PRIuMAX"\n", randint_choose (ints, choices));

  return (randint_all_free (ints) == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
}
#endif


#include "xalloc.h"

/* A source of random data for generating random integers.  */
struct randint_source
{
  /* The source of random bytes.  */
  struct randread_source *source;

  /* RANDNUM is a buffered random integer, whose information has not
     yet been delivered to the caller.  It is uniformly distributed in
     the range 0 <= RANDNUM <= RANDMAX.  If RANDMAX is zero, then
     RANDNUM must be zero (and in some sense it is not really
     "random").  */
  randint randnum;
  randint randmax;
};

/* Create a new randint_source from SOURCE.  */

struct randint_source *
randint_new (struct randread_source *source)
{
  struct randint_source *s = xmalloc (sizeof *s);
  s->source = source;
  s->randnum = s->randmax = 0;
  return s;
}

/* Create a new randint_source by creating a randread_source from
   NAME and ESTIMATED_BYTES.  Return nullptr (setting errno) if
   unsuccessful.  */

struct randint_source *
randint_all_new (char const *name, size_t bytes_bound)
{
  struct randread_source *source = randread_new (name, bytes_bound);
  return (source ? randint_new (source) : nullptr);
}

/* Return the random data source of *S.  */

struct randread_source *
randint_get_source (struct randint_source const *s)
{
  return s->source;
}

/* HUGE_BYTES is true on hosts hosts where randint and unsigned char
   have the same width and where shifting by the word size therefore
   has undefined behavior.  */
enum { HUGE_BYTES = RANDINT_MAX == UCHAR_MAX };

/* Return X shifted left by CHAR_BIT bits.  */
static inline randint shift_left (randint x)
{
  return HUGE_BYTES ? 0 : x << CHAR_BIT;
}


/* Consume random data from *S to generate a random number in the range
   0 .. GENMAX.  */

randint
randint_genmax (struct randint_source *s, randint genmax)
{
  struct randread_source *source = s->source;
  randint randnum = s->randnum;
  randint randmax = s->randmax;
  randint choices = genmax + 1;

  while (1)
    {
      if (randmax < genmax)
        {
          /* Calculate how many input bytes will be needed, and read
             the bytes.  */

          size_t i = 0;
          randint rmax = randmax;
          unsigned char buf[sizeof randnum];

          do
            {
              rmax = shift_left (rmax) + UCHAR_MAX;
              i++;
            }
          while (rmax < genmax);

          randread (source, buf, i);

          /* Increase RANDMAX by appending random bytes to RANDNUM and
             UCHAR_MAX to RANDMAX until RANDMAX is no less than
             GENMAX.  This may lose up to CHAR_BIT bits of information
             if (HUGE_BYTES ? 0 : RANDINT_MAX >> CHAR_BIT) < GENMAX,
             but it is not worth the programming hassle of saving
             these bits since GENMAX is rarely that large in practice.  */

          i = 0;

          do
            {
              randnum = shift_left (randnum) + buf[i];
              randmax = shift_left (randmax) + UCHAR_MAX;
              i++;
            }
          while (randmax < genmax);
        }

      if (randmax == genmax)
        {
          s->randnum = s->randmax = 0;
          return randnum;
        }
      else
        {
          /* GENMAX < RANDMAX, so attempt to generate a random number
             by taking RANDNUM modulo GENMAX+1.  This will choose
             fairly so long as RANDNUM falls within an integral
             multiple of GENMAX+1; otherwise, LAST_USABLE_CHOICE < RANDNUM,
             so discard this attempt and try again.

             Since GENMAX cannot be RANDINT_MAX, CHOICES cannot be
             zero and there is no need to worry about dividing by
             zero.  */

          randint excess_choices = randmax - genmax;
          randint unusable_choices = excess_choices % choices;
          randint last_usable_choice = randmax - unusable_choices;
          randint reduced_randnum = randnum % choices;

          if (randnum <= last_usable_choice)
            {
              s->randnum = randnum / choices;
              s->randmax = excess_choices / choices;
              return reduced_randnum;
            }

          /* Retry, but retain the randomness from the fact that RANDNUM fell
             into the range LAST_USABLE_CHOICE+1 .. RANDMAX.  */
          randnum = reduced_randnum;
          randmax = unusable_choices - 1;
        }
    }
}

/* Clear *S so that it no longer contains undelivered random data.  */

void
randint_free (struct randint_source *s)
{
  explicit_bzero (s, sizeof *s);
  free (s);
}

/* Likewise, but also clear the underlying randread object.  Return
   0 if successful, -1 (setting errno) otherwise.  */

int
randint_all_free (struct randint_source *s)
{
  int r = randread_free (s->source);
  int e = errno;
  randint_free (s);
  errno = e;
  return r;
}