L3 Mathématiques pour l'informatique 4 - TD 7

Restes chinois.

Écrivez une fonction solve_chinese(xx, mm) qui résout le système de congruences $$ \left\{ \begin{matrix} x &\equiv& x_1& [m_1] \\ x &\equiv& x_2& [m_2]\\ \vdots & \cdots &\vdots \\ x&\equiv &x_n&[m_n]\end{matrix}\right.$$

xx est la liste des seconds membres $x_i$ et mm celle des modules $m_i$. La fonction renverra l'unique solution $x$ dans l'intervalle $[0,m-1]$. On suppose que les $m_i$ sont deux à deux premiers entre eux.

In [1]:
# On reprend les fonctions du TD 6

def xgcd(a,b):
    if b==0:
        return (a,1,0)
    else:
        d,u,v = xgcd(b,a % b)
        return (d,v,u-(a//b)*v)

def inversemod(a, n):
    d, x, y = xgcd(a, n)
    assert d == 1, "a doit etre premier avec n."
    return x%n

# Algorithme vu en cours
from functools import reduce

def solve_chinese(yy,mm):
    assert len(yy) == len(mm), "Les arguments doivent etre de meme longueur"
    k = len(yy); m = reduce(lambda x,y:x*y, mm); x=0
    for i in range(k):
        u = reduce(lambda x,y:x*y, [mm[j] for j in range(k) if j!=i])
        v = inversemod(u,mm[i])
        x = (x + yy[i]*v*u) % m
    return x


solve_chinese([1,2,3,4], [11,13,17,19])
Out[1]:
41823
In [2]:
solve_chinese([3,4,5],[17,11,6])
Out[2]:
785

Exponentiation modulaire, test de Fermat

Écrivez une fonction powermod(a,m,n) qui calcule $a^m\mod n$ (la fonction pow de Python le fait aussi, mais on aura besoin de ce code pour la suite), et programmez le test de Fermat :

un entier $n$ est dit pseudo-premier de Fermat pour la base $a$ si $a^{n-1}\equiv 1\,[n]$.

Écrivez une fonction test_fermat(n, tests=5) qui va essayer tests bases $a$ au hasard et renvoie True si on obtient 1 pour chacune (et False sinon).

Voir aussi ici.

Les nombres de Fermat sont les $F_n = 2^{(2^n)}+1$. Ainsi, $F_0=3, F_1= 5, F_2= 17, F_3= 257, F_4= 65537, F_5= 4294967297\ldots$. Ils sont premiers pour $n\le 4$. Fermat pensait qu'il étaient premiers en général, mais Euler a prouvé que $F_5$ était divisible par 641.

Vérifiez par quelques tests de Fermat pour de petites bases $a=2,3,5,7 ...$ que $F_5,F_6,F_7, F_8$ ne sont pas premiers. Pouvez vous en tester de plus grands ?

In [3]:
def powermod(a,m,n):
    res = 1; 
    while m:
        if m&1:  res = (res * a) % n
        m >>= 1
        a = (a *a ) % n
    return res   
In [4]:
powermod(2,666,2**101-1)
Out[4]:
1152921504606846976
In [5]:
pow(2,666,2**101-1) # la fonction de Python
Out[5]:
1152921504606846976
In [6]:
# Les nombres de Fermat
def F(n):
    return 2**(2**n)+1

for n in range(9): print (F(n))
3
5
17
257
65537
4294967297
18446744073709551617
340282366920938463463374607431768211457
115792089237316195423570985008687907853269984665640564039457584007913129639937
In [7]:
# On vérifie que F(8) n'est pas premier
m = F(8)
for a in [2,3,5,7]: print (powermod(a,m-1,m))
1
113080593127052224644745291961064595403241347689552251078258028018246279223993
63917531333650471651353505715673973878358329190708031798737007023636906166728
42292655872463866595282799088611729949438617670861386191995035462150949680389

Test de Miller-Rabin.

En modifiant le test de Fermat, programmer le test de Miller-Rabin. Pour cela, on écrit $n-1=2^km$ avec $m$ impair, et on commence par calculer $b=a^m \mod n$. Si $b=1$, on renvoie True (probablement premier). Sinon, on calcule les $b^{(2^i)}\mod n$ et la première fois que le résultat vaut 1, on teste si la valeur précédente était $\equiv -1\mod n$. Si ce n'est pas le cas, on renvoie False (composé). Si on arrive à $b^{(2^k)}$ sans avoir rencontré de racine carrée non triviale de 1, on renvoie True.

In [8]:
from random import randrange

def test_base(a,n):
    m = n-1; k = 0
    while not m&1:
        k+=1; m >>=1
    b = powermod(a,m,n)
    if b==1: return True
    for i in range(k):
        x = b
        b = powermod(b,2,n)
        if b==1:
            if x != n-1: return False
            else: return True
    return False

def miller_rabin(n, tests=4):
    if n in [2,3]: return True
    if not n%2: return False
    for i in range(tests):
        a = randrange(2,n-1)
        if not test_base(a,n): return False
    return True
In [9]:
miller_rabin(F(8))
Out[9]:
False
In [10]:
miller_rabin(2**117-1)
Out[10]:
False
In [11]:
# Test des nombres de Fermat
for n in range(1,10): print(n,miller_rabin(F(n)))
1 True
2 True
3 True
4 True
5 False
6 False
7 False
8 False
9 False

Les nombres de Mersenne sont les $M_n=2^n-1$. Ils ne peuvent être premiers que si $n$ est lui-même premier, car $$2^{ab}-1=(2^a)^b-1=(2^a-1)(2^{a(b-1)}+2^{a(b-2)}+\cdots +1)$$ mais lorsque $p$ est premier, $M_p$ est souvent premier. Le plus grand nombre premier connu (qui change de temps en temps) est généralement un nombre de Mersenne.

In [12]:
# Une petite liste de nombres (probablement) premiers
pp = [p for p in range(2,500) if miller_rabin(p)]
In [13]:
print(pp)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499]
In [14]:
# Quelques nombres de Mersenne (probablement) premiers
def M(p): return 2**p-1
MM = [p for p in pp if miller_rabin(M(p))]
In [15]:
print(MM)
[2, 3, 5, 7, 13, 17, 19, 31, 61, 89, 107, 127]

On voit qu'il n'y en a plus entre $p=127$ et $500$. Est-ce que c'est fini ? Notre résultat est correct, voir la page Wikipedia. On y apprend que le suivant est $M_{521}$. Et ça continue...

In [16]:
M(521)
Out[16]:
6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057151
In [17]:
miller_rabin(M(521))
Out[17]:
True

Pour le moment, ces nombres de Mersenne sont seulement probablement premiers. Leur forme très particulière permet, grace au test de Lucas-Lehmer, de prouver qu'ils sont premiers lorsqu'ils le sont effectivement.

In [ ]: