Cómo ejecutar [(A * B) mod C] sin desbordamiento, si A y B son menores que C

  1. Si necesita la solución más rápida y no necesita ser portátil, puede hacerlo con el ensamblador en x86–64 y PPC / Power (y quizás algunos otros). La instrucción mulq para x86–64 hace una multiplicación completa de 64 × 64-> 128, luego divq le da el cociente y el resto. No hay traducción 1: 1 de estos con C, por lo que debe hacerlo con ensamblador. Esto funciona con gcc, clang, MSVC, etc.
  2. Utilice un tipo intermedio más grande, por ejemplo, uint64_t u uint128_t. Modern GCC y Clang le brindan estos tipos en cualquier plataforma. Boost también te ofrece un tipo de 128 bits. Esto probablemente será más lento que el ASM anterior, pero más rápido que hacerlo usted mismo. Algunos compiladores incluso serán lo suficientemente inteligentes como para convertirlo en el # 1 si es posible.
  3. Use una biblioteca de multiprecisión como GMP. Probablemente más pesado de lo que necesita.
  4. Su propio método, por ejemplo, aquí es un ejemplo bastante optimizado (para los accesos directos pequeños a, b, el intercambio a / b puede ahorrar mucho tiempo, ambas rutas evitan mod y se dividen por completo en su bucle, una pequeña optimización para el caso de 63 bits). Para mis pruebas, esto es 10 veces más rápido que las funciones que muestran Tonmoy y Bind. Todavía es más de 10 veces más lento que la solución ASM (no sorprende que dos instrucciones de ensamblaje superen un bucle corto).

estático uint64_t mulmod (uint64_t a, uint64_t b, uint64_t n) {
uint64_t r = 0;
si (a> = n) a% = n; / * Atención cuidadosa de la persona que llama * /
si (b> = n) b% = n; / * debería hacerlos innecesarios. * /
if ((a | b) <(1ULL << 32)) return (a * b)% n;
if (a <b) {uint64_t t = a; a = b; b = t; }
if (n <= (1ULL << 63)) {
mientras que (b> 0) {
si (b & 1) {r + = a; si (r> = n) r – = n; }
b >> = 1;
si (b) {a + = a; si (a> = n) a – = n; }
}
} más {
mientras que (b> 0) {
si (b & 1) r = ((nr)> a)? r + a: r + an; / * r = (r + a)% n * /
b >> = 1;
si (b) a = ((na)> a)? a + a: a + an; / * a = (a + a)% n * /
}
}
volver r;
}

Asegúrese de probar algunas pruebas unitarias. Por ejemplo, lo siguiente:

mulmod(13151668037435042619,15457154184134767374,17762506985585424311)

debe devolver 1599139733680599118 . Un par de otros ejemplos de código en otras respuestas no lo hacen bien, pero probablemente funcionen bien para entradas de 63 bits; ese último bit es complicado. Compare ejemplos con un programa que hace multiprecisión como Pari / GP, Mathematica, Perl con bigint, Python con GMPY, etc.

También puede encontrar código de ejemplo en Wikipedia: Aritmética modular – Wikipedia

Puede usar Divide & Conquer para encontrar (A * B)% C. A continuación se muestra el código en C ++.

#include
using namespace std;
long long mulmod(long long a,long long b,long long c) {
if (a == 0 || b == 0)
return 0;
if (a == 1)
return b;
if (b == 1)
return a;
long long a2 = mulmod(a, b / 2, c);
if ((b & 1) == 0)
{
return (a2 + a2) % c;
}
else
{
return ((a % c) + (a2 + a2)) % c;
}
}
int main() {
long long a,b,c;
cin>>a>>b>>c;
cout << mulmod(a,b,c) < return 0;
}

Si [math] (C-1) ^ 2 [/ math] no desborda el límite de entero largo largo sin signo, simplemente puede calcular la solución en tiempo constante.

De lo contrario, tendrá que usar la multiplicación campesina rusa ( http://en.wikipedia.org/wiki/Anc …) para encontrar la solución.

Aquí está mi implementación:

unsigned long long russianPeasant(unsigned long long a, unsigned long long b, unsigned long long c) {
unsigned long long ret=0;

mientras que (b) {
si (b & 1) {
ret + = a;
ret% = c;
}
a * = 2;
a% = c;
b / = 2;
}

volver ret;
}

La respuesta de Dana Jacobsen es realmente buena.

Sin embargo, no evita completamente el desbordamiento. Aquí hay una versión similar, que evita el desbordamiento:

static uint64_t slowModulo (uint64_t A, uint64_t B, uint64_t C)
{
uint64_t r = 0;
uint64_t C_down = C >> 1;
uint64_t C_up = C – C_down;
mientras que (B> 0)
{
si (B & 1)
r = ((r> = C- A)
? (A> = C_up? A – C_up + r: r – C_up + A) – C_down
: r + A);
si (A> = C_up)
A = (A-C_down) + (A-C_up);
más
A = A + A;

B >> = 1;
}
volver r;
}

Tenga en cuenta que evitar el desbordamiento aquí no es útil, porque en C ++ el desbordamiento en tipos sin signo simplemente se envuelve, y el código de Dana produce el mismo resultado. Con suerte, gcc produce el mismo código para ambos. Todavía es un buen ejercicio mental para deshacerse de todos los desbordamientos.

También probé una optimización, que elimina tantos bits de B como sea posible en cada iteración del bucle. Sin embargo, resulta que no es notablemente más rápido, incluso cuando se utiliza la función incorporada gcc “contar los ceros iniciales” (clz). Sin embargo, el código está abajo:

static long fastModulo (uint64_t A, uint64_t B, uint64_t C, uint64_t stepSize)
{
uint64_t mask = (1 << stepSize) - 1;
uint64_t r = 0;

mientras que (B> 0)
{
r + = A * (B y máscara);
r% = C;

A << = stepSize;
B >> = tamaño de paso;
A% = C;
}
volver r;
}

estático uint64_t bigModulo (uint64_t A, uint64_t B, uint64_t C)
{
afirmar (C> A);
afirmar (C> B);
if ((A | B) <(1ULL << 32))
retorno (A * B)% C;
if (A

int stepSize = __builtin_clz ((uint32_t) (C >> 32));
if (stepSize == 0)
return slowModulo (A, B, C);
return fastModulo (A, B, C, stepSize);
}

Me parece que puedes calcular la cantidad de bits que cada número tomaría para almacenar. Súmelos y reste uno. Esa es la cantidad de bits que se necesitarían para almacenar el resultado. Verifique ese valor en comparación con su tamaño de almacenamiento más grande y antes de realizar la operación, sabrá si la multiplicación se desbordará. Si desea que A & B sea absolutamente arbitrariamente grande, deberá cambiar a una versión BCD con memoria asignada para almacenar los números BCD pero, en primer lugar, A & B no son un tipo de datos interno.

Si estás atrapado en un mundo entero (como sospecho que es este problema de tarea), necesitarás un poco de contador. Aquí hay uno simple:

int
num_of_bits_unsigned(long long int i) {
int bits=1;
if(!i)
return(0);
while((i = i >> 1) > 0)
bits++;
return(bits);
}

Dejaré descubrir cómo manejar enteros firmados y el resto del problema.

Es posible calcular la multiplicación sin división utilizando operaciones de bit o búsqueda de tabla. En general, solo he visto operaciones de bits utilizadas para campos finitos muy pequeños (GF (2) o GF (3)), y las tablas explotan rápidamente y nunca las he visto utilizadas en código de alto rendimiento.

Usar plus en lugar de multi, pero solo usar plus puede ser demasiado lento, así que pruebe binary plus como el algoritmo de potencia binaria.
ej .: 3 * 5 escribe 5 como código binario 101, entonces 3 * 5 = 3 * 2 ^ 0 + 3 * 2 ^ 2.