【问题标题】:Fast modular multiplication modulo prime for linear congruential generator in CC语言中线性同余生成器的快速模乘模素数
【发布时间】:2015-06-24 21:10:04
【问题描述】:

我正在尝试实现一个以梅森素数 (231-1) 作为模数的随机数生成器。以下工作代码基于几个相关帖子:

  1. How do I extract specific 'n' bits of a 32-bit unsigned integer in C?
  2. Fast multiplication and subtraction modulo a prime
  3. Fast multiplication modulo 2^16 + 1

然而,

它不适用于uint32_t hi, lo;,这意味着我不了解问题的签名与未签名方面。

根据上面的#2,我期待答案是 (hi+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);
}

【问题讨论】:

  • 查看 sgined 操作的汇编输出。您通常会看到逻辑左移,但算术右移带符号值。其他位运算符的行为应该相同。 ASL 运算符在向下移动时将保留符号位。
  • 谢谢。第一次尝试已签署,因此增加了混乱。第二种解决方案使用无符号并揭示了真正的问题。
  • uint64_t x1 = (uint64_t ) ((hi + lo) ); 嗯也许uint64_t x1 = (uint64_t ) hi + lo;?
  • 次要:r |= 1 &lt;&lt; i; --> r |= (uint32_t)1 &lt;&lt; i;(应对 16 位 int. 和签名溢出。)
  • 最好从问题中删除解决方案,将其作为答案发布(它是什么!)并接受它。目前,该问题显示为未回答的问题。

标签: c random primes modular-arithmetic


【解决方案1】:

下面的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_tint32_t 的转换是模 2^31。虽然许多 C 编译器以这种方式处理转换,但 C 标准并未强制要求这样做。实际结果是实现定义的。

第二种解决方案避免了溢出,并且适用于int32_tuint32_t

您还可以为r 使用整数常量:

uint64_t r = 0x7FFFFFFF; // 2^31 - 1

或者干脆

uint64_t r = INT32_MAX;

编辑:对于 2^p-k 形式的素数,您必须使用带有 p 位的掩码并使用

uint32_t x1 = (k * hi + lo) % ((1 << p) - k)

如果k * hi + lo 可以溢出uint32_t(即(k + 1) * (2^p - 1) &gt;= 2^32),则必须使用64 位算术:

uint32_t x1 = ((uint64_t)a * x) % ((1 << p) - k)

根据平台的不同,后者可能会更快。

【讨论】:

  • 谢谢。即使代码“有效”,我也并不真正理解发生了什么。您的回答解决了所有的困惑。
【解决方案2】:

Sue 提供了这个解决方案:

通过一些实验(底部的新代码),我能够使用 uint32_t,这进一步表明我不明白 有符号整数适用于位操作。

以下代码使用uint32_t 以及hilo 作为输入。

 #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);
  }

问题是我假设减少将完成 一通之后。如果 (x > 231-1),那么根据定义 减少尚未发生,需要第二次通过。减法 231-1,在这种情况下就可以了。在第二次尝试中 上面,r = 2^31-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);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-08-22
    • 1970-01-01
    • 2020-12-23
    • 2015-11-15
    • 2013-10-09
    • 1970-01-01
    • 1970-01-01
    • 2016-07-03
    相关资源
    最近更新 更多