有没有办法建立,例如没有BigInteger库的(853467 * 21660421200929) % 100000000000007
(请注意,每个数字都适合64位整数,但乘法结果不适合)?
此解决方案似乎效率低下:
int64_t mulmod(int64_t a, int64_t b, int64_t m) {
if (b < a)
std::swap(a, b);
int64_t res = 0;
for (int64_t i = 0; i < a; i++) {
res += b;
res %= m;
}
return res;
}
答案 0 :(得分:22)
您应该使用Russian Peasant multiplication。它使用重复加倍来计算所有值(b*2^i)%m
,并在设置i
的{{1}}时添加它们。
a
它会改进您的算法,因为它需要uint64_t mulmod(uint64_t a, uint64_t b, uint64_t m) {
int64_t res = 0;
while (a != 0) {
if (a & 1) res = (res + b) % m;
a >>= 1;
b = (b << 1) % m;
}
return res;
}
时间,而不是O(log(a))
时间。
警告:无符号,仅当O(a)
为63位或更少时才有效。
答案 1 :(得分:14)
Keith Randall's answer很好,但正如他所说的那样,需要注意的是,只有当m
为63位或更少时才有效。
这是一个有两个优点的修改:
m
为64位也可以。(注意,res -= m
和temp_b -= m
行依赖于64位无符号整数溢出,以便给出预期的结果。这应该没问题,因为无符号整数溢出在C和C中定义良好C ++。因此它是important to use unsigned integer types。)
uint64_t mulmod(uint64_t a, uint64_t b, uint64_t m) {
uint64_t res = 0;
uint64_t temp_b;
/* Only needed if b may be >= m */
if (b >= m) {
if (m > UINT64_MAX / 2u)
b -= m;
else
b %= m;
}
while (a != 0) {
if (a & 1) {
/* Add b to res, modulo m, without overflow */
if (b >= m - res) /* Equiv to if (res + b >= m), without overflow */
res -= m;
res += b;
}
a >>= 1;
/* Double b, modulo m */
temp_b = b;
if (b >= m - b) /* Equiv to if (2 * b >= m), without overflow */
temp_b -= m;
b += temp_b;
}
return res;
}
答案 2 :(得分:4)
重复加倍算法的改进是检查一次可以计算多少位而没有溢出。可以对两个参数进行提前退出检查 - 加快N不是素数的(不太可能?)事件。
e.g。 100000000000007 == 0x00005af3107a4007,允许每次迭代计算16(或17)位。使用示例,实际迭代次数为3。
// just a conceptual routine
int get_leading_zeroes(uint64_t n)
{
int a=0;
while ((n & 0x8000000000000000) == 0) { a++; n<<=1; }
return a;
}
uint64_t mulmod(uint64_t a, uint64_t b, uint64_t n)
{
uint64_t result = 0;
int N = get_leading_zeroes(n);
uint64_t mask = (1<<N) - 1;
a %= n;
b %= n; // Make sure all values are originally in the proper range?
// n is not necessarily a prime -- so both a & b can end up being zero
while (a>0 && b>0)
{
result = (result + (b & mask) * a) % n; // no overflow
b>>=N;
a = (a << N) % n;
}
return result;
}
答案 3 :(得分:2)
你可以尝试一些能够将乘法分解为附加内容的东西:
// compute (a * b) % m:
unsigned int multmod(unsigned int a, unsigned int b, unsigned int m)
{
unsigned int result = 0;
a %= m;
b %= m;
while (b)
{
if (b % 2 != 0)
{
result = (result + a) % m;
}
a = (a * 2) % m;
b /= 2;
}
return result;
}
答案 4 :(得分:2)
这两种方法对我都有用。第一个与你的相同,但是我将你的数字改为显性ULL。第二个使用汇编程序表示法,它应该更快。 密码学中也使用了算法(基本上我猜测基于RSA和RSA的密码学),就像已经提到的蒙哥马利减少一样,但我认为实施它们需要时间。
#include <algorithm>
#include <iostream>
__uint64_t mulmod1(__uint64_t a, __uint64_t b, __uint64_t m) {
if (b < a)
std::swap(a, b);
__uint64_t res = 0;
for (__uint64_t i = 0; i < a; i++) {
res += b;
res %= m;
}
return res;
}
__uint64_t mulmod2(__uint64_t a, __uint64_t b, __uint64_t m) {
__uint64_t r;
__asm__
( "mulq %2\n\t"
"divq %3"
: "=&d" (r), "+%a" (a)
: "rm" (b), "rm" (m)
: "cc"
);
return r;
}
int main() {
using namespace std;
__uint64_t a = 853467ULL;
__uint64_t b = 21660421200929ULL;
__uint64_t c = 100000000000007ULL;
cout << mulmod1(a, b, c) << endl;
cout << mulmod2(a, b, c) << endl;
return 0;
}
答案 5 :(得分:1)
我可以建议你的算法有所改进。
您实际上通过每次添加a * b
来迭代地计算b
,在每次迭代后进行模数运算。最好每次b * x
添加,而x
确定为b * x
不会溢出。
int64_t mulmod(int64_t a, int64_t b, int64_t m)
{
a %= m;
b %= m;
int64_t x = 1;
int64_t bx = b;
while (x < a)
{
int64_t bb = bx * 2;
if (bb <= bx)
break; // overflow
x *= 2;
bx = bb;
}
int64_t ans = 0;
for (; x < a; a -= x)
ans = (ans + bx) % m;
return (ans + a*b) % m;
}
答案 6 :(得分:0)
a * b % m
等于a * b - (a * b / m) * m
使用浮点运算来近似a * b / m
。近似值为正常的64位整数运算留下足够小的值,m
最多为63位。
此方法受double
的有效数限制,通常为52位。
uint64_t mod_mul_52(uint64_t a, uint64_t b, uint64_t m) {
uint64_t c = (double)a * b / m - 1;
uint64_t d = a * b - c * m;
return d % m;
}
此方法受long double
的有效数限制,通常为64位或更大。整数运算限制为63位。
uint64_t mod_mul_63(uint64_t a, uint64_t b, uint64_t m) {
uint64_t c = (long double)a * b / m - 1;
uint64_t d = a * b - c * m;
return d % m;
}
这些方法要求a
和b
小于m
。要处理任意a
和b
,请在计算c
之前添加这些行。
a = a % m;
b = b % m;
在这两种方法中,最终的%
操作都可以成为条件。
return d >= m ? d % m : d;
答案 7 :(得分:0)
O(log(b)) 解决方案,但实际上它会运行得更快,因为它每次都会检查 ab 是否适合 64 位整数。这个算法不会无限循环,因为a和b都适合64位整数,而且每次递归调用后,b /= 2,所以在最坏的情况下,b = 1,a * 1适合。使用 mult() 方法,我们可以计算大数的 a^b mod m,即使是 LLONG_MAX!请注意,在mult和addition方法中,a < mod, b < mod!
long exp(long a, long b, long mod){
long x = a, y = 1, z = b;
while(z > 0){
if(z % 2 == 1)
y = mult(y, x, mod);
x = mult(x, x, mod);
z /= 2;
}
return y;
}
long mult(long a, long b, long mod){
if(a == 0)
return 0;
if(LLONG_MAX / a >= b)
return (a * b) % mod;
long rec = mult(a, b / 2, mod);
long res = addition(rec, rec, mod);
if(b % 2 == 1)
res = addition(res, a, mod);
return res;
}
long addition(long a, long b, long mod) {
if(a > mod / 2)
a = a - mod;
if(b > mod / 2)
b = b - mod;
long ans = a + b;
if(ans < 0)
ans = mod + ans;
return ans;
}