Быстрое модульное умножение по модулю простого числа для линейного конгруэнтного генератора в @

Я пытаюсь реализовать генератор случайных чисел с простым Мерсенном (231-1) в качестве модуля. Следующий рабочий код был основан на нескольких связанных сообщениях:

  1. как извлечь определенные " n " бит 32-разрядного целого числа без знака в C?
  2. быстрое умножение и вычитание по модулю простого
  3. быстрое умножение по модулю 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);
}