diff options
| author | Paul Eggert | 2022-03-16 17:21:55 -0700 |
|---|---|---|
| committer | Paul Eggert | 2022-03-16 17:52:41 -0700 |
| commit | 2ef037c0dd3510a51ad73fdead1ded09848166f4 (patch) | |
| tree | a44593bba870cf70add2d0615efd07e3193926d9 /src | |
| parent | 31a2428d6f2ca792af18b43ceca5cec1ecce862f (diff) | |
| download | emacs-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.c | 93 | ||||
| -rw-r--r-- | src/bignum.h | 1 | ||||
| -rw-r--r-- | src/fns.c | 77 | ||||
| -rw-r--r-- | src/lisp.h | 1 | ||||
| -rw-r--r-- | src/sysdep.c | 10 |
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 | |||
| 482 | static mp_limb_t | ||
| 483 | get_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 | |||
| 502 | static mp_limb_t | ||
| 503 | get_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 | |||
| 525 | Lisp_Object | ||
| 526 | get_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) | |||
| 51 | extern void emacs_mpz_pow_ui (mpz_t, mpz_t const, unsigned long) | 51 | extern void emacs_mpz_pow_ui (mpz_t, mpz_t const, unsigned long) |
| 52 | ARG_NONNULL ((1, 2)); | 52 | ARG_NONNULL ((1, 2)); |
| 53 | extern double mpz_get_d_rounded (mpz_t const) ATTRIBUTE_CONST; | 53 | extern double mpz_get_d_rounded (mpz_t const) ATTRIBUTE_CONST; |
| 54 | extern Lisp_Object get_random_bignum (struct Lisp_Bignum const *); | ||
| 54 | 55 | ||
| 55 | INLINE_HEADER_BEGIN | 56 | INLINE_HEADER_BEGIN |
| 56 | 57 | ||
| @@ -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. */ | ||
| 58 | static Lisp_Object | 60 | static Lisp_Object |
| 59 | get_random_bignum (Lisp_Object limit) | 61 | get_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 | ||
| 95 | DEFUN ("random", Frandom, Srandom, 0, 1, 0, | 78 | DEFUN ("random", Frandom, Srandom, 0, 1, 0, |
| @@ -103,32 +86,26 @@ With a string argument, set the seed based on the string's contents. | |||
| 103 | See Info node `(elisp)Random Numbers' for more details. */) | 86 | See 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); | |||
| 4926 | extern void setup_pty (int); | 4926 | extern void setup_pty (int); |
| 4927 | extern int set_window_size (int, int, int); | 4927 | extern int set_window_size (int, int, int); |
| 4928 | extern EMACS_INT get_random (void); | 4928 | extern EMACS_INT get_random (void); |
| 4929 | extern unsigned long int get_random_ulong (void); | ||
| 4929 | extern void seed_random (void *, ptrdiff_t); | 4930 | extern void seed_random (void *, ptrdiff_t); |
| 4930 | extern void init_random (void); | 4931 | extern void init_random (void); |
| 4931 | extern void emacs_backtrace (int); | 4932 | extern 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. */ | ||
| 2204 | unsigned long int | ||
| 2205 | get_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. */ |
| 2205 | int | 2215 | int |