Cálculo del Inverso Modular

Ya en esta publicación se habló de lo que básicamente es el inverso modular, este post es para explicar las formas más conocidas para hallarlo.

El inverso modular de A en modulo N lo representamos como:

(i) X ≡ A^(-1) mod N
ó
(ii) X * A ≡ 1 mod N

Nos interesa el cálculo de X, del cual podemos afirmar que solo existe si MCD(A,N) = 1, es decir cuando A y N no tienen factores comunes aparte del 1.


Primero, apliquemos la propiedad de congruencia en (ii), así tenemos:

N | (X * A – 1)
Es decir el valor de X*A – 1 debe ser múltiplo de N

Es decir, existe un entero K tal que:

(α) N * K = X * A – 1

Hemos llevado la expresión (ii) a la forma de (α). Ahora veamos la primera forma de poder hallar el inverso de A en módulo N, es decir X.

1ra Forma – Método Ingenuo

Tiempo: O(N)
Espacio: O(1)

Despejamos en (α) a K:

K = (X * A – 1) / N
Dado que K es entero la división debería ser entera también

Nuestro parámetro o variable es X.
Los valores de X para los que (X * A – 1) / N no es un entero serán erróneos, mientras que el valor que haga que (X * A – 1) / N sea entero será el valor que estamos buscando.

Podemos implementar el siguiente algoritmo:

int inverse(int A, int N) {
    if (mcd(A, N) != 1) return 0; /* Inverso no existe /*
    for (int X=0; X<N; X++) {
        if ((A*X-1)%N == 0)
            return X;
    }
}

Complejidad: O(n)

El algoritmo anterior implementado en C++ comienza por comprobar la existencia del inverso y en caso no exista retorna 0, luego busca cada posible valor de X que cumpla la condición dicha anteriormente la cual podemos observar por la sentencia IF en el código.
Solo comprobamos los valores de 0 a N ya que los demás números no hacen falta comprobar ya que dichos número son congruentes (en módulo N) a algún número que ya esta en el rango de 0 a N los cuales son los que ya evaluamos.

Ahora hallemos los inversos de todos los números en módulo 7:

int main() {
    const int mod = 7;
    cout<<"MODULO "<<mod<<endl;
    for (int i=0; i<mod; i++){ 
        int inv = inverse(i, mod);
        if (inv)
            cout<<"inverso de "<<i<<": "<<inv<<endl;
        else
            cout<<"inverso de "<<i<<" ¡no existe!"<<endl;
    }
    return 0;
}
OUTPUT:
        EN MODULO 7
inverso de 0 ¡no existe!
inverso de 1: 1
inverso de 2: 4
inverso de 3: 5
inverso de 4: 2
inverso de 5: 3
inverso de 6: 6

Podemos ver en la salida que el inverso de 0 no existe, lo cual es cierto como se vio en esta publicación, luego tenemos los inversos de 1, 2, 3, 4, 5 y 6 los cuales es fácil notar que existen, dado que 7 es número primo por lo que el MCD con cualquier otro número (no múltiplo de 7) será 1, por lo que todos los números en módulo 7 tienen inverso, esto no es asi con los números no primos como el 9, ya que por ejemplo en módulo 9 no existen los inversos para 0, 3, 6:

        EN MODULO 9
inverso de 0 no existe!
inverso de 1: 1
inverso de 2: 5
inverso de 3 no existe!
inverso de 4: 7
inverso de 5: 2
inverso de 6 no existe!
inverso de 7: 4
inverso de 8: 8

Este algoritmo para hallar el inverso funciona y en una complejidad lineal O(n), sin embargo para números mucho más grandes esta complejidad lineal puede terminar siendo demasiado lenta, podemos ajustar esto hasta una complejidad logarítmica:

2da Forma – Algoritmo Extendido de Euclides

Tiempo: O(log N)
Espacio: O(1)

El algoritmo extendido de Euclides se explica en este post, el cual se muestra a continuación para ser usado en el cálculo de el inverso modular:

#include <iostream>
#include <tuple>

std::tuple<int,int,int> extended_euclidean(int a, int b) {

    int r0 = a>b? a:b;
    int r1 = r0==a? b:a;
    int s0=1, s1=0, t0=0, t1=1;

    while (r1 != 0) {
        int r = r0 % r1;
        int q = int(r0/r1);
        int s = s0 - q * s1;
        int t = t0 - q * t1;

        r0 = r1;
        r1 = r;
        s0 = s1;
        s1 = s;
        t0 = t1;
        t1 = t;
    }

    return std::make_tuple(r0, s0, t0);
}

int inverse_mod(int n, int m) {
    int mcd, s, t;
    std::tie(mcd, s, t) = extended_euclidean(n, m);
    if (mcd != 1)
        throw std::overflow_error("GCD is not one, inverse modular doesn't exist\n");
    return n<m? t%m:s%m;
}

Probamos el algoritmo:


int main() {
    int mcd, s, t;
    int a=4013, b=4057;
    std::tie(mcd, s, t)= extended_euclidean(a,b);
    printf("\t.::Bezout Coeficients::.\n");
    printf("MCD(%d, %d) = %d = %d*(%d) + %d*(%d) \n", a,b,mcd,a,s,b,t);

    printf("\t.:: 4013^(-1) mod 4057 ::.\n");
    printf(" = %d\n", inverse_mod(4013, 4057));

    printf("\t.:: 4057^(-1) mod 4013 ::.\n");
    printf(" = %d\n", inverse_mod(4057, 4013));

    printf("\t.:: 2^(-1) mod 4 ::.\n");
    try {
        printf(" = %d\n", inverse_mod(2, 4));
    } catch (std::overflow_error e) {
        std::cout << e.what() ;
    }

    printf("\t.:: END ::.");

}
OUTPUT
        .::Bezout Coeficients::.
MCD(4013, 4057) = 1 = 4013*(-456) + 4057*(461)
        .:: 4013^(-1) mod 4057 ::.
 = 461
        .:: 4057^(-1) mod 4013 ::.
 = -456
        .:: 2^(-1) mod 4 ::.
GCD is not one, inverse modular doesn't exist
        .:: END ::.

Un comentario sobre “Cálculo del Inverso Modular

Deja un comentario