C中线性同余发生器的快速模乘模素数
Fast modular multiplication modulo prime for linear congruential generator in C
我正在尝试实现一个以梅森素数 (231-1) 作为模数的随机数生成器。以下工作代码基于几个相关帖子:
- How do I extract specific 'n' bits of a 32-bit unsigned integer in C?
- Fast multiplication and subtraction modulo a prime
然而,
它不适用于 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);
}
Sue 提供了这个解决方案:
With some experimentation (new code at the bottom), I was able to use
uint32_t
, which further suggests that I do not understand how the
signed integers work with bit operations.
The following code uses uint32_t
for input as well as hi
and 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);
}
The issue was that my assumption that the reduction will be complete
after one pass. If (x > 231-1), then by definition the
reduction has not occurred and a second pass is necessary. Subtracting
231-1, in that case does the trick. In the second attempt
above, r = 2^31-1
and is therefore the modulus. x -= r
achieves
the final reduction.
Perhaps someone with expertise in random numbers or modular reduction
could explain it better.
Cleaned function without 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);
}
下面的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
到 int32_t
的转换是模 2^31。虽然许多 C 编译器以这种方式处理转换,但这并不是 C 标准强制要求的。实际结果是实现定义的。
第二种解决方案避免了溢出并适用于 int32_t
和 uint32_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) >= 2^32
),就得用64位算术:
uint32_t x1 = ((uint64_t)a * x) % ((1 << p) - k)
根据平台的不同,后者可能更快。
我正在尝试实现一个以梅森素数 (231-1) 作为模数的随机数生成器。以下工作代码基于几个相关帖子:
- How do I extract specific 'n' bits of a 32-bit unsigned integer in C?
- Fast multiplication and subtraction modulo a prime
然而,
它不适用于 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);
}
Sue 提供了这个解决方案:
With some experimentation (new code at the bottom), I was able to use
uint32_t
, which further suggests that I do not understand how the signed integers work with bit operations.The following code uses
uint32_t
for input as well ashi
andlo
.#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); }
The issue was that my assumption that the reduction will be complete after one pass. If (x > 231-1), then by definition the reduction has not occurred and a second pass is necessary. Subtracting 231-1, in that case does the trick. In the second attempt above,
r = 2^31-1
and is therefore the modulus.x -= r
achieves the final reduction.Perhaps someone with expertise in random numbers or modular reduction could explain it better.
Cleaned function without
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); }
下面的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
到 int32_t
的转换是模 2^31。虽然许多 C 编译器以这种方式处理转换,但这并不是 C 标准强制要求的。实际结果是实现定义的。
第二种解决方案避免了溢出并适用于 int32_t
和 uint32_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) >= 2^32
),就得用64位算术:
uint32_t x1 = ((uint64_t)a * x) % ((1 << p) - k)
根据平台的不同,后者可能更快。