Быстрое модульное умножение по модулю простого числа для линейного конгруэнтного генератора в @
Я пытаюсь реализовать генератор случайных чисел с простым Мерсенном (231-1) в качестве модуля. Следующий рабочий код был основан на нескольких связанных сообщениях:
- как извлечь определенные " n " бит 32-разрядного целого числа без знака в C?
- быстрое умножение и вычитание по модулю простого
- быстрое умножение по модулю 2^16 + 1
не работает с uint32_t hi, lo;
, что означает, что я не понимаю подписанный и неподписанный аспект проблемы.
основываясь на #2 выше, я ожидал, что ответ будет (Привет+lo). Это означает, что я не понимаю, зачем нужно следующее утверждение.
if (x1 > r)
x1 += r + 2;
может кто-нибудь прояснить источник моего замешательства?
можно ли улучшить сам код?
Если генератор избежать 0 или 231-1 в качестве семян?
как изменится код для простого (2p - k)?
исходный код
#include <inttypes.h>
// x1 = a*x0 (mod 2^31-1)
int32_t lgc_m(int32_t a, int32_t x)
{
printf("x %"PRId32"n", x);
if (x == 2147483647){
printf("x1 %"PRId64"n", 0);
return (0);
}
uint64_t c, r = 1;
c = (uint64_t)a * (uint64_t)x;
if (c < 2147483647){
printf("x1 %"PRId64"n", c);
return (c);
}
int32_t hi=0, lo=0;
int i, p = 31;//2^31-1
for (i = 1; i < p; ++i){
r |= 1 << i;
}
lo = (c & r) ;
hi = (c & ~r) >> p;
uint64_t x1 = (uint64_t ) (hi + lo);
// NOT SURE ABOUT THE NEXT STATEMENT
if (x1 > r)
x1 += r + 2;
printf("c %"PRId64"n", c);
printf("r %"PRId64"n", r);
printf("tlo %"PRId32"n", lo);
printf("thi %"PRId32"n", hi);
printf("x1 %"PRId64"n", x1);
printf("n" );
return((int32_t) x1);
}
int main(void)
{
int32_t r;
r = lgc_m(1583458089, 1);
r = lgc_m(1583458089, 2000000000);
r = lgc_m(1583458089, 2147483646);
r = lgc_m(1583458089, 2147483647);
return(0);
}
2 ответов
следующая инструкция if
if (x1 > r)
x1 += r + 2;
должно быть написано как
if (x1 > r)
x1 -= r;
оба результата одинаковы по модулю 2^31:
x1 + r + 2 = x1 + 2^31 - 1 + 2 = x1 + 2^31 + 1
x1 - r = x1 - (2^31 - 1) = x1 - 2^31 + 1
первое решение переполняет int32_t
и предполагает, что преобразование uint64_t
to int32_t
по модулю 2^31. Хотя многие компиляторы C обрабатывают преобразование таким образом, это не предусмотрено стандартом C. Фактический результат определяется реализацией.
второе решение избегает переполнения и работает с обоими int32_t
и uint32_t
.
вы также можете использовать целочисленную константу для r
:
uint64_t r = 0x7FFFFFFF; // 2^31 - 1
или просто
uint64_t r = INT32_MAX;
EDIT: для простых чисел вида 2^p-k вы должны использовать маски с P битами и вычислить результат с
uint32_t x1 = (k * hi + lo) % ((1 << p) - k)
если k * hi + lo
может привести к переполнению uint32_t
(то есть (k + 1) * (2^p - 1) >= 2^32
), вы должны использовать 64-битную арифметику:
uint32_t x1 = ((uint64_t)a * x) % ((1 << p) - k)
в зависимости от платформы, последняя может быть все равно быстрее.
Сью предоставила это в качестве решения:
С некоторыми экспериментами (новый код внизу), я смог использовать
uint32_t
, что далее предполагает, что я не понимаю, как целые числа со знаком работают с битовыми операциями.следующий код использует
uint32_t
для ввода, а такжеhi
иlo
.#include <inttypes.h> // x1 = a*x0 (mod 2^31-1) uint32_t lgc_m(uint32_t a, uint32_t x) { printf("x %"PRId32"\n", x); if (x == 2147483647){ printf("x1 %"PRId64"\n", 0); return (0); } uint64_t c, r = 1; c = (uint64_t)a * (uint64_t)x; if (c < 2147483647){ printf("x1 %"PRId64"\n", c); return (c); } uint32_t hi=0, lo=0; int i, p = 31;//2^31-1 for (i = 1; i < p; ++i){ r |= 1 << i; } hi = c >> p; lo = (c & r) ; uint64_t x1 = (uint64_t ) ((hi + lo) ); // NOT SURE ABOUT THE NEXT STATEMENT if (x1 > r){ printf("x1 - r = %"PRId64"\n", x1- r); x1 -= r; } printf("c %"PRId64"\n", c); printf("r %"PRId64"\n", r); printf("\tlo %"PRId32"\n", lo); printf("\thi %"PRId32"\n", hi); printf("x1 %"PRId64"\n", x1); printf("\n" ); return((uint32_t) x1); } int main(void) { uint32_t r; r = lgc_m(1583458089, 1583458089); r = lgc_m(1583458089, 2147483645); return(0); }
проблема заключалась в том, что мое предположение о том, что сокращение будет полным после одного прохода. Если (Х > 231-1), то по определению сокращение не произошло, и необходим второй проход. Вычитание 231-1, в этом случае делает трюк. Во второй попытке выше, и-модуль.
x -= r
достигает окончательное сокращение.возможно, кто-то с опытом в случайных числах или модульной редукции мог бы объяснить лучше.
очищенная функция без
printf()
s.uint32_t lgc_m(uint32_t a, uint32_t x){ uint64_t c, x1, m = 2147483647; //modulus: m = 2^31-1 if (x == m) return (0); c = (uint64_t)a * (uint64_t)x; if (c < m)//no reduction necessary return (c); uint32_t hi, lo, p = 31;//2^p-1, p = 31 hi = c >> p; lo = c & m; x1 = (uint64_t)(hi + lo); if (x1 > m){//one more pass needed //this block can be replaced by x1 -= m; hi = x1 >> p; lo = (x1 & m); x1 = (uint64_t)(hi + lo); } return((uint32_t) x1); }