使用基元类型进行模乘法的方法
Ways to do modulo multiplication with primitive types
>有没有办法构建例如 (853467 * 21660421200929) % 100000000000007
没有 BigInteger 库(请注意,每个数字都适合 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;
}
你应该使用俄罗斯农民乘法。 它使用重复加倍来计算(b*2^i)%m
的所有值,如果设置了a
的第i
位,则将它们相加。
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)
时间。
注意事项:无符号,仅当m
为 63 位或更少时才有效。
Keith Randall 的回答很好,但正如他所说,需要注意的是,它只有在 63 位或更少时才能m
工作。
这是一个具有两个优点的修改:
- 即使
m
是 64 位,它也可以工作。 - 它不需要使用模运算,这在某些处理器上可能很昂贵。
(请注意,res -= m
行和temp_b -= m
行依赖于 64 位无符号整数溢出才能给出预期的结果。这应该没问题,因为无符号整数溢出在 C 和 C++ 中定义得很好。因此,使用无符号整数类型非常重要。
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;
}
这两种方法都对我有用。第一个和你的一样,但我把你的号码改成了 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 %2nt"
"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;
}
重复加倍算法的一个改进是检查一次可以计算多少位而不会溢出。可以对这两个参数进行早期退出检查 - 加速N不是素数的(不太可能?(事件。
例如 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;
}
你可以尝试一些将乘法分解为加法的方法:
// 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;
}
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;
我可以建议改进您的算法。
您实际上通过每次添加b
来迭代计算a * 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;
}
- 如果C++类在类方法中具有动态分配,但没有构造函数/析构函数或任何非静态成员,那么它仍然是POD类型吗
- 有没有一种方法可以通过"typedef"为重新定义的基本类型定义特征和强制转换运算符
- 当无法使用模板和宏时,生成类型变体C++代码的最简单方法是什么?
- 是否有内置方法可以强制转换为不同的基础类型,但保留常量限定符?
- 拥有映射的现代方法,该映射可以指向或引用已在堆栈上分配的不同类型的数据
- 类作用域的类型别名"using":[何时]方法中的用法可以先于类型别名?
- 调用具有未标识类型的类的方法
- 将复杂的非基元C++数据类型转换为 Erlang/Elixir 格式,以使用 NIF 导出方法
- 我的模板类方法返回错误类型?
- 在 C++ 中将非指定类型作为参数传递的最佳方法?
- QtQuick - qml:28:错误:未知方法返回类型:自定义类型
- 构造智能点数据类型以及普通数据类型的通用方法
- 如何在没有实例的情况下获取非静态方法的类型?
- C++方法是否可以根据传递给构造函数的参数具有不同的返回类型?
- 在自定义 std::vector-like 容器中处理指针和非指针模板类型的最佳方法是什么?
- 使用类型id运算符的最佳替代方法
- 检查子类型时的专用方法模板
- C ++类型特征:确保子类实现方法
- 标识派生类类型的正确方法(类型实体VS dynamic_case)
- 类方法类型的decltype