aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorPaul Eggert2022-03-16 17:21:55 -0700
committerPaul Eggert2022-03-16 17:52:41 -0700
commit2ef037c0dd3510a51ad73fdead1ded09848166f4 (patch)
treea44593bba870cf70add2d0615efd07e3193926d9 /src
parent31a2428d6f2ca792af18b43ceca5cec1ecce862f (diff)
downloademacs-2ef037c0dd3510a51ad73fdead1ded09848166f4.tar.gz
emacs-2ef037c0dd3510a51ad73fdead1ded09848166f4.zip
Improve random bignum generation
* src/bignum.c (get_random_limb, get_random_limb_lim) (get_random_bignum): New functions, for more-efficient generation of random bignums without using Frem etc. * src/fns.c (get_random_fixnum): New function. (Frandom): Use it, and get_random_bignum. Be consistent about signalling nonpositive integer arguments; since zero is invalid, Qnatnump is not quite right here. * src/sysdep.c (get_random_ulong): New function.
Diffstat (limited to 'src')
-rw-r--r--src/bignum.c93
-rw-r--r--src/bignum.h1
-rw-r--r--src/fns.c77
-rw-r--r--src/lisp.h1
-rw-r--r--src/sysdep.c10
5 files changed, 132 insertions, 50 deletions
diff --git a/src/bignum.c b/src/bignum.c
index cb5322f291a..e4e4d45d686 100644
--- a/src/bignum.c
+++ b/src/bignum.c
@@ -476,3 +476,96 @@ check_int_nonnegative (Lisp_Object x)
476 CHECK_INTEGER (x); 476 CHECK_INTEGER (x);
477 return NILP (Fnatnump (x)) ? 0 : check_integer_range (x, 0, INT_MAX); 477 return NILP (Fnatnump (x)) ? 0 : check_integer_range (x, 0, INT_MAX);
478} 478}
479
480/* Return a random mp_limb_t. */
481
482static mp_limb_t
483get_random_limb (void)
484{
485 if (GMP_NUMB_BITS <= ULONG_WIDTH)
486 return get_random_ulong ();
487
488 /* Work around GCC -Wshift-count-overflow false alarm. */
489 int shift = GMP_NUMB_BITS <= ULONG_WIDTH ? 0 : ULONG_WIDTH;
490
491 /* This is in case someone builds GMP with unusual definitions for
492 MINI_GMP_LIMB_TYPE or _LONG_LONG_LIMB. */
493 mp_limb_t r = 0;
494 for (int i = 0; i < GMP_NUMB_BITS; i += ULONG_WIDTH)
495 r = (r << shift) | get_random_ulong ();
496 return r;
497}
498
499/* Return a random mp_limb_t I in the range 0 <= I < LIM.
500 If LIM is zero, simply return a random mp_limb_t. */
501
502static mp_limb_t
503get_random_limb_lim (mp_limb_t lim)
504{
505 /* Return the remainder of a random mp_limb_t R divided by LIM,
506 except reject the rare case where R is so close to the maximum
507 mp_limb_t that the remainder isn't random. */
508 mp_limb_t difflim = - lim, diff, remainder;
509 do
510 {
511 mp_limb_t r = get_random_limb ();
512 if (lim == 0)
513 return r;
514 remainder = r % lim;
515 diff = r - remainder;
516 }
517 while (difflim < diff);
518
519 return remainder;
520}
521
522/* Return a random Lisp integer I in the range 0 <= I < LIMIT,
523 where LIMIT is a positive bignum. */
524
525Lisp_Object
526get_random_bignum (struct Lisp_Bignum const *limit)
527{
528 mpz_t const *lim = bignum_val (limit);
529 mp_size_t nlimbs = mpz_size (*lim);
530 eassume (0 < nlimbs);
531 mp_limb_t *r_limb = mpz_limbs_write (mpz[0], nlimbs);
532 mp_limb_t const *lim_limb = mpz_limbs_read (*lim);
533 mp_limb_t limhi = lim_limb[nlimbs - 1];
534 eassert (limhi);
535 bool edgy;
536
537 do
538 {
539 /* Generate the result one limb at a time, most significant first.
540 Choose the most significant limb RHI randomly from 0..LIMHI,
541 where LIMHI is the LIM's first limb, except choose from
542 0..(LIMHI-1) if there is just one limb. RHI == LIMHI is an
543 unlucky edge case as later limbs might cause the result to be
544 exceed or equal LIM; if this happens, it causes another
545 iteration in the outer loop. */
546
547 mp_limb_t rhi = get_random_limb_lim (limhi + (1 < nlimbs));
548 edgy = rhi == limhi;
549 r_limb[nlimbs - 1] = rhi;
550
551 for (mp_size_t i = nlimbs - 1; 0 < i--; )
552 {
553 /* get_random_limb_lim (edgy ? limb_lim[i] + 1 : 0)
554 would be wrong here, as the full mp_limb_t range is
555 needed in later limbs for the edge case to have the
556 proper weighting. */
557 mp_limb_t ri = get_random_limb ();
558 if (edgy)
559 {
560 if (lim_limb[i] < ri)
561 break;
562 edgy = lim_limb[i] == ri;
563 }
564 r_limb[i] = ri;
565 }
566 }
567 while (edgy);
568
569 mpz_limbs_finish (mpz[0], nlimbs);
570 return make_integer_mpz ();
571}
diff --git a/src/bignum.h b/src/bignum.h
index 5f94ce850cf..de9ee17c027 100644
--- a/src/bignum.h
+++ b/src/bignum.h
@@ -51,6 +51,7 @@ extern void emacs_mpz_mul_2exp (mpz_t, mpz_t const, EMACS_INT)
51extern void emacs_mpz_pow_ui (mpz_t, mpz_t const, unsigned long) 51extern void emacs_mpz_pow_ui (mpz_t, mpz_t const, unsigned long)
52 ARG_NONNULL ((1, 2)); 52 ARG_NONNULL ((1, 2));
53extern double mpz_get_d_rounded (mpz_t const) ATTRIBUTE_CONST; 53extern double mpz_get_d_rounded (mpz_t const) ATTRIBUTE_CONST;
54extern Lisp_Object get_random_bignum (struct Lisp_Bignum const *);
54 55
55INLINE_HEADER_BEGIN 56INLINE_HEADER_BEGIN
56 57
diff --git a/src/fns.c b/src/fns.c
index e8cf1857550..6e89fe3ca5f 100644
--- a/src/fns.c
+++ b/src/fns.c
@@ -55,41 +55,24 @@ DEFUN ("identity", Fidentity, Sidentity, 1, 1, 0,
55 return argument; 55 return argument;
56} 56}
57 57
58/* Return a random Lisp fixnum I in the range 0 <= I < LIM,
59 where LIM is taken from a positive fixnum. */
58static Lisp_Object 60static Lisp_Object
59get_random_bignum (Lisp_Object limit) 61get_random_fixnum (EMACS_INT lim)
60{ 62{
61 /* This is a naive transcription into bignums of the fixnum algorithm. 63 /* Return the remainder of a random integer R (in range 0..INTMASK)
62 I'd be quite surprised if that's anywhere near the best algorithm 64 divided by LIM, except reject the rare case where R is so close
63 for it. */ 65 to INTMASK that the remainder isn't random. */
64 while (true) 66 EMACS_INT difflim = INTMASK - lim + 1, diff, remainder;
67 do
65 { 68 {
66 Lisp_Object val = make_fixnum (0); 69 EMACS_INT r = get_random ();
67 Lisp_Object lim = limit; 70 remainder = r % lim;
68 int bits = 0; 71 diff = r - remainder;
69 int bitsperiteration = FIXNUM_BITS - 1;
70 do
71 {
72 /* Shift by one so it is a valid positive fixnum. */
73 EMACS_INT rand = get_random () >> 1;
74 Lisp_Object lrand = make_fixnum (rand);
75 bits += bitsperiteration;
76 val = CALLN (Flogior,
77 Fash (val, make_fixnum (bitsperiteration)),
78 lrand);
79 lim = Fash (lim, make_fixnum (- bitsperiteration));
80 }
81 while (!EQ (lim, make_fixnum (0)));
82 /* Return the remainder, except reject the rare case where
83 get_random returns a number so close to INTMASK that the
84 remainder isn't random. */
85 Lisp_Object remainder = Frem (val, limit);
86 if (!NILP (CALLN (Fleq,
87 CALLN (Fminus, val, remainder),
88 CALLN (Fminus,
89 Fash (make_fixnum (1), make_fixnum (bits)),
90 limit))))
91 return remainder;
92 } 72 }
73 while (difflim < diff);
74
75 return make_fixnum (remainder);
93} 76}
94 77
95DEFUN ("random", Frandom, Srandom, 0, 1, 0, 78DEFUN ("random", Frandom, Srandom, 0, 1, 0,
@@ -103,32 +86,26 @@ With a string argument, set the seed based on the string's contents.
103See Info node `(elisp)Random Numbers' for more details. */) 86See Info node `(elisp)Random Numbers' for more details. */)
104 (Lisp_Object limit) 87 (Lisp_Object limit)
105{ 88{
106 EMACS_INT val;
107
108 if (EQ (limit, Qt)) 89 if (EQ (limit, Qt))
109 init_random (); 90 init_random ();
110 else if (STRINGP (limit)) 91 else if (STRINGP (limit))
111 seed_random (SSDATA (limit), SBYTES (limit)); 92 seed_random (SSDATA (limit), SBYTES (limit));
112 if (BIGNUMP (limit)) 93 else if (FIXNUMP (limit))
94 {
95 EMACS_INT lim = XFIXNUM (limit);
96 if (lim <= 0)
97 xsignal1 (Qargs_out_of_range, limit);
98 return get_random_fixnum (lim);
99 }
100 else if (BIGNUMP (limit))
113 { 101 {
114 if (0 > mpz_sgn (*xbignum_val (limit))) 102 struct Lisp_Bignum *lim = XBIGNUM (limit);
115 xsignal2 (Qwrong_type_argument, Qnatnump, limit); 103 if (mpz_sgn (*bignum_val (lim)) <= 0)
116 return get_random_bignum (limit); 104 xsignal1 (Qargs_out_of_range, limit);
105 return get_random_bignum (lim);
117 } 106 }
118 107
119 val = get_random (); 108 return make_ufixnum (get_random ());
120 if (FIXNUMP (limit) && 0 < XFIXNUM (limit))
121 while (true)
122 {
123 /* Return the remainder, except reject the rare case where
124 get_random returns a number so close to INTMASK that the
125 remainder isn't random. */
126 EMACS_INT remainder = val % XFIXNUM (limit);
127 if (val - remainder <= INTMASK - XFIXNUM (limit) + 1)
128 return make_fixnum (remainder);
129 val = get_random ();
130 }
131 return make_ufixnum (val);
132} 109}
133 110
134/* Random data-structure functions. */ 111/* Random data-structure functions. */
diff --git a/src/lisp.h b/src/lisp.h
index 8053bbc9777..c90f901ebca 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -4926,6 +4926,7 @@ extern void child_setup_tty (int);
4926extern void setup_pty (int); 4926extern void setup_pty (int);
4927extern int set_window_size (int, int, int); 4927extern int set_window_size (int, int, int);
4928extern EMACS_INT get_random (void); 4928extern EMACS_INT get_random (void);
4929extern unsigned long int get_random_ulong (void);
4929extern void seed_random (void *, ptrdiff_t); 4930extern void seed_random (void *, ptrdiff_t);
4930extern void init_random (void); 4931extern void init_random (void);
4931extern void emacs_backtrace (int); 4932extern void emacs_backtrace (int);
diff --git a/src/sysdep.c b/src/sysdep.c
index b5b18ee6c0f..1632f46d13e 100644
--- a/src/sysdep.c
+++ b/src/sysdep.c
@@ -2200,6 +2200,16 @@ get_random (void)
2200 return val & INTMASK; 2200 return val & INTMASK;
2201} 2201}
2202 2202
2203/* Return a random unsigned long. */
2204unsigned long int
2205get_random_ulong (void)
2206{
2207 unsigned long int r = 0;
2208 for (int i = 0; i < (ULONG_WIDTH + RAND_BITS - 1) / RAND_BITS; i++)
2209 r = random () ^ (r << RAND_BITS) ^ (r >> (ULONG_WIDTH - RAND_BITS));
2210 return r;
2211}
2212
2203#ifndef HAVE_SNPRINTF 2213#ifndef HAVE_SNPRINTF
2204/* Approximate snprintf as best we can on ancient hosts that lack it. */ 2214/* Approximate snprintf as best we can on ancient hosts that lack it. */
2205int 2215int