Fattorizzare i numeri interi

annuncio pubblicitario
Fattorizzare i numeri interi
(di Cristiano Armellini, [email protected])
Ci sono molti algoritmi efficienti per fattorizzare numeri interi e molti sono i testi dove è possibile trovare una
completa trattazione sull'argomento, Qui vogliamo proporre dei sistemi (in parte abbastanza originali e forse
relativamente veloci ma senza nessuna pretesa da parte nostra) per cercare di trovare i fattori di un numero
intero con metodi algebrici e facilmente implementabili in un moderno calcolatore. L'idea è comunque quella
di trovare dei limiti entro cui cercare con maggiori probabilità le soluzioni. Nella ricerca delle soluzioni
prendiamo principalmente in esame solo valori interi positivi benché in linea di principio potremmo
considerare anche i negativi dal momento che 9 = 3*3 = (-3)*(-3). Facendo girare tutti gli algoritmi in
parallelo aumenta la probabilità di trovare soluzioni in tempi rapidi. Ogni algoritmo sarà implementato con
una applicazione scritta in C/C++ o in PARI/Gp per la gestione dei grandi numeri
Considerazione preliminare (algoritmo 0)
Dato un numero p da fattorizzare di n cifre è facile provare che almeno un fattore deve essere dell'ordine di
W  10
n
int  1
2
cioè deve avere almeno int(n/2) cifre con int(x) la parte la funzione parte intera di x.
Questa considerazione parte dal fatto che se un fattore in un problema tipo RSA fosse troppo piccolo
sarebbe fin troppo facile individuarlo con metodi banali di ricerca per tentativi.
1
Se p = ab con a, b numeri primi allora entrambi i fattori sono entrambi maggiori di W cioè tali per cui
a > W, b > W cioè S= a+b > W. In questo particolare caso possiamo porre anche
W  10
n
int 
2
Considerare W ci è particolarmente utile quando trattiamo di grandi dimensioni perché come vedremo meglio
con gli altri algoritmi ci può aiutare a trovare dei limiti per i fattori da ricercare.
LISTATO IN C++
#include <stdio.h>
#include <math.h>
#include <iostream.h>
int main(int argc, char *argv[])
{
long double x, p;
long i = 0;
int cifre;
cout << "inserisci il numero di cifre del numero intero";
cin >> cifre;
cout <<" inserisci il numero da fattorizzare";
cin >> p;
x = pow(10, int(cifre/2)-1 );
do{
x = x + 1;
i = i + 1;
} while(int(p/x) != (p/x));
cout << "un fattore e' " << x;
cout <<" passi di elaborazione: " << i;
cin >> "---------";
return 0;
}
2
LISTATO IN PARI/GP
{ algo2(cifre) = local(x, p);
p = nextprime(10^10)*nextprime(10^12);
x = 10^(floor(cifre/2)-1);
while(floor(p/x) != (p/x), x=x+1);
print(x);
print("-----");
print(p/x);
return (1);}
Primo algoritmo (AL-1) (variante della fattorizzazione alla Fermat)
p  x1 x2 (in generale non è detto che ci siano solo due fattori come nell'RSA ma il procedimento di
fattorizzazione può essere ripetuto per ognuno dei due fattori trovati finché non si arriva a fattori che hanno
decomposizione banale cioè che sono primi).
x1 
p , x1 
x2 
p
p
2
x1, 2  0
x1ORx 2  W dove OR sta ad indicare che la relazione vale per una radice o per entrambe
tutti i numeri interi positivi (ove la ricerca può essere fatta in modo sequenziale oppure
random)
3
Attenzione: per P molto grande A(p) cioè il numero dei primi minori di P è approssimativamente
p/log(p)
LISTATI IN C++ E IN VISUAL BASIC .NET
Nei codici sorgenti in C++ che seguono ho usato l'espressione 'int(p/x)' per semplicità tuttavia
sarebbe meglio sostituirla con la più esatta sintatticamente floor(fabs(p/x)).
#include <stdio.h>
#include <iostream.h>
#include <math.h>
int main(int argc, char *argv[])
{
long double x, p;
long i, j;
j = 0;
cout << "inserisci il numero da fattorizzare ";
cin >> p;
for( i = int(sqrt(p)); i <= int(p/2); i++)
{
j = j +1;
if(int(p/i) == (p/i))
{
cout << "fattore: " << i << "\n";
cout << "passi: " << j << "\n";
}
}
cin >> "-----------------"
}
return 0;
lo stesso algoritmo può essere implementato in Visual Basic .Net usando per la ricerca dei fattori
un generatore di numeri casuali: la ricerca si rileva molto più veloce:
Module Module1
Sub Main()
Dim p As Decimal
Dim generator As New Random
Dim a As Decimal
Dim i As Integer
i=0
4
Console.WriteLine("inserisci il numero da fattorizzare")
p = Console.ReadLine()
Do
a = generator.Next(2, Int(Math.Sqrt(p)))
i=i+1
Loop While ((p / a) <> Int(p / a))
Console.Write("fattore: ")
Console.Write(a)
Console.Write("passi: ")
Console.Write(i)
Console.ReadLine()
End Sub
End Module
LISTATI IN PARI/GP
{fermat(p) = local(x);
x = floor(sqrt(p));
while (floor(p/x) != (p/x), x--);
print(x);
print("----");
print(p/x);
return (1);}
{fermat2(p) = local(a, x, y);
a = floor(sqrt(p));
x = random(a);
y = precprime(x);
while (floor(p/y) != (p/y), x = random(a);y = precprime(x));
print(y);
print("----");
print(p/y);
return (1);}
{fermat3(p) = local(a, x, y);
a = floor(sqrt(p));
x = random(a);
while (floor(p/x) != (p/x), x = random(a));
print(x);
print("----");
5
print(p/x);
return (1);}
{fattore2(p) =local(s, x);
s = truncate(sqrt(p));
x = precprime(s);
while (truncate(p/x) != (p/x),
s--;
x = precprime(s));
print(x);
return (1);}
{fattore3(p)= local(s, x);
s=0;
x = nextprime(s);
while (truncate(p/x) != (p/x),
s++;
x = nextprime(s));
print(x);
return (1);}
{fattore4(p)= local(s, x);
s=truncate(p/2);
x = precprime(s);
while (truncate(p/x) != (p/x),
s--;
x = precprime(s));
print(x);
return (1);}
{fattore5(p)= local(s, x);
s=truncate(p/2);
x = nextprime(s);
while (truncate(p/x) != (p/x),
s++;
x = nextprime(s));
6
print(x);
return (1);}
Secondo algoritmo (Al-2)
Dalla nota relazione
x 2  sx  p  0
s  x1  x2
p  x1 x2
x1, 2
s  s2  4 p

2
s  2 p , s  2 p
Supponiamo di stare a considerare un tipico problema RSA quindi p ha solo due fattori ed s è sicuramente
un numero pari essendo la somma di due primi quindi di due numeri dispari.
Questo algoritmo è
particolarmente veloce quando 𝑥1 , 𝑥2 ~10𝑛/2 dove 𝑝~10𝑛 ma è comunque molto oneroso quando le radici
differiscono per almeno 3-4 cifre e perché comunque fa uso delle radici quadrate che sono complesse da
calcolare per valori elevati.
Cerchiamo di velocizzare la ricerca dei fattori usando l’elemento W precedentemente descritto.
x1 
s  s2  4 p
 W mi porta a dover risolvere il seguente sistema:
2
7
s  2 p oppure
s
s  2 p
W2  p
, s  2W  0
W
s  s2  4 p
x2 
 W mi porta a dover risolvere il seguente sistema:
2
2W  s  0
s2  4 p  0
s2  4 p  0
unito alle soluzioni del sistema 2W  s  0
W2  p
s
W
Osservazione
x y
1) s  4 p  0 equivale alla ben nota relazione 
  xy
 2 
2
2
2) se d 
s
2
allora x1, 2  d  d  p (più avanti approfondiremo questo caso)
2
int( )
x 2 k  pk  px
xy
px
, xy  p porta a
dove pi può supporre k  10 2
 0 ovvero k  2
k
xk
x p
n
3) x  y 
con n il numero delle cifre di P. Trovare k  int(
px
) ci aiuta a calcolare una stima superiore per s =
x p
2
x+y per aumentare anche grazie al punto 1) la ricerca delle soluzioni.
LISTATO IN C++
8
#include <stdio.h>
#include <math.h>
#include <iostream.h>
int main(int argc, char *argv[])
{
long double p, x, y, s;
long i;
cout << "inserisci il numero da fattorizzare ";
cin >> p;
s = 2* int(sqrt(p));
i = 0;
do {
x = (s + sqrt(pow(s,2)-4*p))/2;
y = (s - sqrt(pow(s,2)-4*p))/2;
s = s+1;
i = i+1;
}while((int(p/x) != (p/x)) || (int(p/y) != (p/y))) ; // si può mettere && (and) al posto di || (OR) ma una
delle due
cout << "un fattore e' " << x << "\n";
//soluzioni trovate potrebbe non essere intera
cout << "un fattore e' " << y << "\n";
cout << "passi: " << i;
cin >> "--------------------";
return 0;
}
LISTATI IN PARI/GP
{fatto(p) =local(s, x);
s = 2 * truncate(sqrt(p));
x = (s - sqrt(abs(s^2-4*p)))/2;
while (truncate(p/x) != (p/x), s++);
print(x);
return (1);}
{algo1(p) = local( s, d, x, y);
s = 2 * floor(sqrt(p));
d = sqrt(abs(s^2-4*p));
x = (s + d)/2;
y = (s - d)/2;
while (floor(p/x) != (p/x) && floor(p/y) != (p/y),
s++;
d = sqrt(abs(s^2-4*p));
9
x = (s + d)/2;
y = (s - d)/2);
print(x);
print("----");
print(y);
return (1);}
{algo1(p) = local(s, d, x, y);
s = floor(sqrt(p));
d = sqrt(abs(s^2-p));
x = (s + d);
y = (s - d);
while (floor(p/x) != (p/x) && floor(p/y) != (p/y),
s = s+1;
d = sqrt(abs(s^2-p));
x = (s + d);
y = (s - d));
print(x);
print("----");
print(y);
return (1);}
{algo2b(p) = local(b, d, x, y);
b = floor(sqrt(p));
d = sqrt(abs(b^2-p));
x = b + d;
y = b - d;
while (floor(p/x) != (p/x) && floor(p/y) != (p/y),
b++ ;
d = sqrt(abs(b^2-p));
x = b + d;
y = b - d);
print(x);
print("----");
print(y);
return (1);}
VARIANTE
10
s 2  4 p s 2  4 p  a, a  0, p  xy a numero pari perché differenza di due numeri pari
Ciò porta all’equazione biquaratica
x 4  x 2 (a  2 p)  p 2  0
x1, 2 
a  2 p  a 2  4ap
2
11
LISTATO IN C++
#include <stdio.h>
#include <math.h>
#include <iostream.h>
int main(int argc, char *argv[])
{
float a, x, y, p;
long double i;
cout << "inserisci il numero da fattorizzare ";
cin >> p;
a = 0;
i = 0;
do{
x = sqrt( (a+2*p+sqrt(pow(a,2)+4*a*p))/2);
y = sqrt( (a+2*p-sqrt(pow(a,2)+4*a*p))/2);
a = a+1;
i = i+1;
} while ((int(p/x) != (p/x)) || (int(p/y) != (p/y))) ; // si può mettere && (and) al posto di || (OR) ma una
}
cout << "fattore: " << x << "\n";
cout << "fattore: " << y << "\n";
cout << "passi: " <<i << "\n";
cin >> " -----";
return 0;
// delle due soluzioni trovate potrebbe non essere intera
LISTATO IN PARI/GP
{algo3(p) = local(a, x, y, d);
a = 0;
d = sqrt(abs(a^2+4*a*p));
x = sqrt(abs((a+2*p+d)/2));
y = sqrt(abs((a+2*p-d)/2));
while (floor(p/x) != (p/x) && floor(p/y) != (p/y),
a++;
d = sqrt(abs(a^2+4*a*p));
x = sqrt(abs((a+2*p+d)/2));
y = sqrt(abs((a+2*p-d)/2)));
print(x);
print("----");
print(y);
return (1);}
12
Terzo algoritmo (Al-3)
Dalle Osservazioni precedenti deduciamo che
2
2
 s  m
      p dove m è intero
2  2 
x2 
s m

2 2
x1 
s m

2 2
allora
x12  mx1  p  0 , x22  mx2  p  0
da cui si può partire direttamente da m per trovare le soluzioni
x1 
m  m2  4 p
2
con m   s  4 p ,
2
x2 
 m  m2  4 p
2
s  m, s  2 p
(possiamo considerare anche le soluzioni negative di X1, X2 quelle con il segno - ovvero
x1 
m  m2  4 p
2
x2 
 m  m2  4 p
ma il ragionamento sarebbe identico a quello già fatto)
2
13
Sempre nell’ipotesi di considerare un problema di fattorizzazione RSA usiamo il fattore W per accelerare la
ricerca delle soluzioni. Consideriamo per semplicità le soluzioni positive ma lo stesso discorso vale per quelle
negative
m  m2  4 p
 W porta a risolvere il seguente sistema
2
x
m2  4 p  0
unito alle soluzioni di
2W  m  0
x=
m  2W  0
W2  p
m
W
 m  m2  4 p
 W porta a dover risolvere il sistema
2
m2  4 p  0
2W  m  0
m
p W 2
W
m  2W
 m  m2  4 p
x
 W allora m 2  4 p  0
2
W2  p
m
W
14
LISTATO IN C++
#include <stdio.h>
#include <math.h>
#include <iostream.h>
int main(int argc, char *argv[])
{
long double m, x, y, p;
long i;
cout << "inserisci il numero da fattorizzare ";
cin >> p;
m = 0;
i = 0;
do{
x = (m + sqrt(pow(m, 2) + 4*p))/2;
y = (-m + sqrt(pow(m, 2) + 4*p))/2;
m = m+1;
i = i+1;
} while ( (int(p/x) != (p/x)) || (int(p/y) != (p/y))); // si può mettere && (and) al posto di || (OR) ma una
cout << "fattore: " << x << "\n";
cout << "fattore: " << y << "\n";
cout << "passi: " <<i << "\n";
cin >> " -----";
}
// delle due soluzioni trovate potrebbe non essere intera
return 0;
LISTATO IN PARI/GP
{algo4(p) = local(m, x, y);
m= 0;
d = sqrt(m^2+4*p);
x = (m+d)/2;
y = (-m+d)/2;
while (floor(p/x) != (p/x) && floor(p/y) != (p/y),
m++ ;
d = sqrt(m^2+4*p);
x = (m+d)/2;
y = (-m+d)/2);
print(x);
print("----");
print(y);
return (1);}
15
Quarto algoritmo (Al-4)
p  x1 x2  a 2  b 2  (a  b)(a  b)
x1  a  b, x2  a  b
x1  x2  2b
x1  x2  2a ciò porta a
x12  2bx1  p  0 , x12  2ax1  p  0
x1  a  a 2  p , x1  b  b 2  p (possiamo considerare anche la soluzione negativa quella con
segno - , però poi occorre calcolare il suo valore assoluto)
Come nei casi precedenti
x  a  a 2  p  W porta a:
a  p,a   p
a2  p  0
unito alle soluzioni di a  W  0
W a 0
W2  p
a
W
x  a  a 2  p  W porta al sistema di condizioni
a
p, a   p
a W
W2  p
a
W
16
x  b  b 2  p  W
allora
b2  p  0
b  p0
unito a W  b  0
W b  0
p W 2
b
2W
2
x  b  b 2  p  W ciò porta a:
b  W
b2  p  0
p W 2
b
2W
osservazione: se p  x1 x2 è un problema RSA allora x1  x2  2a x1  2a  x2 quindi
x22  2ax2  p  0 x 2  a  a 2  p con a 2  p
LISTATO IN C++
#include <stdio.h>
#include <math.h>
#include <iostream.h>
int main(int argc, char *argv[])
{
long double x, y, p;
long a;
long i = 0;
cout<< "inserisci il numero da fattorizzare ";
cin >> p;
a = int(sqrt(p));
a = a+1;
do {
x = a + sqrt(pow(a,2) -p);
y = a - sqrt(pow(a,2) - p);
a = a+1;
17
i = i+1;
} while( (int(p/x)!=(p/x)) ||(int(p/y) != p/y)) ; // si può mettere && (and) al posto di || (OR) ma una
delle due
cout << "fattore: " << x << "\n";
// soluzioni trovate potrebbe non essere intera
cout << "fattore: " << y << "\n";
cout << "passaggi: " << i;
cin >> "-------------"; return 0;}
LISTATO IN PARI/GP
{algo5(p) = local(a, d, x, y);
a = floor(sqrt(p));
d = sqrt(abs(a^2-p));
x = a+d;
y = a-d;
while (floor(p/x) != (p/x) && floor(p/y) != (p/y),
a = a+1 ;
d = sqrt(abs(a^2-p));
x = a+d;
y = a-d);
print(x);
print(y);
return (1);}
LISTATO IN C++
#include <stdio.h>
#include <math.h>
#include <iostream.h>
int main(int argc, char *argv[])
{
long double x, p;
long b;
long i = 0;
cout<< "inserisci il numero da fattorizzare ";
cin >> p;
b = int(sqrt(p));
b = b+1;
do {
x = - b + sqrt(pow(b,2) + p);
18
b = b+1;
i = i+1;
} while((int(p/x)!=(p/x)));
cout << "fattore: " << x << "\n";
cout << "fattore: " << y << "\n";
cout << "passaggi: " << i;
cin >> "-------------";
return 0; }
LISTATO IN PARI/GP
{algo6(p) = local(s, x);
b = floor(sqrt(p));
x = -b + sqrt(abs(b^2 +p));
while (floor(p/x) != (p/x),
b++;
x = (s + sqrt(abs(s^2-4*p)))/2);
print(x);
print("----");
print(p/x);
return (1);}
Quinto algoritmo (Al-5)
Se
p  x1 x2 , x2  x1  a allora sostituendo le variabili ottengo che
x12  x1a  p  0 quindi x1 
 a  a2  4 p
2
Consideriamo la soluzione positiva come negli altri casi
x
 a  a2  4 p
 W allora dobbiamo risolvere
2
19
a  2W
a  2W
a2  4 p  0
unione a
a
p W 2
W
a  a2  4 p
x
W allora il sistema diventa:
2
2W  a  0
a2  4 p  0
W2  p
a
W
analogamente si arriva agli stessi risultati nel caso opposto cioè che x2
che
x
 x1  a abbiamo
a  a2  4 p
.
2
Quindi
a2  4 p  0
a  4p  0
unito a 2W  a  0
2W  a  0
W2  p
a
W
2
Come negli altri casi si sarebbe potuto considerare per X1 e X2 anche le soluzioni negative , quello con
segno - poi si sarebbe dovuto calcolare il loro valore assoluto
20
21
LISTATO IN C++
#include <stdio.h>
#include <math.h>
#include <iostream.h>
int main(int argc, char *argv[])
{
long double x, y, p;
long a;
long i = 0;
cout<< "inserisci il numero da fattorizzare ";
cin >> p;
a = 0;
a = a+1;
do {
x = (-a+sqrt(pow(a,2)+4*p))/2;
y = (a+sqrt(pow(a,2)+4*p))/2;
a = a+1;
i = i+1;
} while( (int(p/x)!=(p/x)) ||(int(p/y) != p/y)) ; // si può mettere && (and) al posto di || (OR) ma una delle
due
cout << "fattore: " << x << "\n";
// soluzioni trovate potrebbe non essere intera
cout << "fattore: " << y << "\n";
cout << "passaggi: " << i;
cin >> "-------------";
return 0;
}
LISTATO IN PARI/GP
{algo7(p) = local(a, d, x, y);
a = 0;
d = sqrt(a^2+4*p);
x = (-a + d)/2;
y = (a + d)/2;
while (floor(p/x) != (p/x) && floor(p/y) != (p/y),
a = a+1;
d = sqrt(a^2+4*p);
x = (-a + d)/2;
y = (a + d)/2);
print(x);
print("----");
print(y);
return (1);}
22
Sesto algoritmo (Al-6)
Partiamo da un ben nota relazione algebrica sempre vera:
x2  y2
 xy quindi x 2  y 2  2 xy  2a a > 0 y = p/x
2
arriviamo a
x 4  2 x 2 ( p  a)  p 2  0 quindi le soluzioni sono x1, 2 
p  a  a 2  2 pa dove però per
evitare errori di calcolo sostituiamo ad a a/2 per effetto della disuguaglianza di partenza
LISTATO C++
#include <stdio.h>
#include <math.h>
#include <iostream.h>
int main(int argc, char *argv[])
{
long double x, y, p;
long int i, a, c;
a = 0;
i = 0;
cout << "inserisci numero ";
cin >> p;
do{
x = sqrt(p+a+sqrt(pow(a,2)+2*p*a));
y = sqrt(p+a-sqrt(pow(a,2)+2*p*a));
a = a+1; // qui è meglio mettere a = a+ 1/2 se non è un problema RSA;
i = i +1;
}while( (int(p/y) != (p/y)) || (int(p/x) != (p/x)) ) ; // si può mettere && (and) al posto di || (OR) ma
cout << "fattore: " << x << "\n";
// una delle due soluzioni trovate potrebbe non essere intera
cout << "fattore: " << y << "\n";
cout << "passi: " << i;
}
23
LISTATO IN PARI/GP
{algo8(p) = local(a, d, x, y);
a = 0;
d = sqrt(a^2+2*p*a);
x = sqrt(abs(p+a+d));
y = sqrt(abs(p+a-d));
while (floor(p/x) != (p/x) && floor(p/y) != (p/y),
a = a+0.5;
d = sqrt(abs(a^2+2*p*a));
x = sqrt(abs(p+a+d));
y = sqrt(abs(p+a-d)));
print(x);
print("----");
print(y);
return (1);}
Fattorizzazione con le disequazioni
Nel campo della fattorizzazione dei numeri possono essere impiegate le disuguaglianze notevoli
(a  b) 2  a 2  b 2  2ab
ab 
a 2  b 2 ( a  b) 2

2
2
quindi
ab 
a2  b2
2
ora da
(a  b) 2  a 2  b 2  2ab
ab 
( a  b) 2 a 2  b 2

2
2
24
quindi
ab 
( a  b) 2
2
ma allora
m 2  ab 
a 2  b 2 ( a  b) 2

2
2
con m = min(a,b)
in modo del tutto analogo da
(a  b) 3  a 3  3a 2 b  3ab 2  b 3
procedendo come sopra ho che
ab 
( a  b) 2
3
delle altre disuguaglianze abbiamo già detto di quest'ultima no.
s 2  3 p, s 2  3 p  a, a  0
se y = P/x allora
x 4  x 2 ( p  a)  p 2  0
x
p  a   3 p 2  2 pa  a 2
2
con a > 0 a > p, (a < -3p per a < 0)
25
LISTATO IN C++
#include <cstdlib>
#include <iostream>
#include <math.h>
using namespace std;
int main(int argc, char *argv[])
{
double x, p, a, start;
cout << "inserisci il numero da fattorizzare ";
cin >> p;
a = p;
do
{
x = sqrt( (p+a+sqrt(-3*pow(p,2)+2*p*a + pow(a,2)) )/2);
a = a+1;
} while(int(p/x) != (p/x));
cout << "fattore " << x;
system("PAUSE");
return EXIT_SUCCESS;
}
LISTATO IN C++
{algo10(p) = local(a, d, x, y);
a = p;
d = sqrt(abs(-3*p^2 + 2*p*a+a^2));
x = sqrt(abs((p+a+d)/2));
y = sqrt(abs((p+a-d)/2));
while (floor(p/x) != (p/x) && floor(p/y) != (p/y),
a = a+1;
d = sqrt(abs(-3*p^2 + 2*p*a+a^2));
x = sqrt(abs((p+a+d)/2));
y = sqrt(abs((p+a-d)/2)));
print(x);
print("----"); print(y); return (1);}
26
Fattorizzazione con il teorema di Bezout
Il famoso teorema di Bezout ci può aiutare a trovare i fattori di un problema RSA. Sia p = ap dal teorema di
Bezout sappiamo che se d=MCD(a,b) esistono u, v interi tali che d = ua + bv. Ora dato che
d=MCD(a,b) = 1 abbiamo che 1= au + bv. Da queste considerazioni esplicitando a e sapendo che p = ab
troviamo:
b 2 v  b  up  0
b
1  1  4uvp
2v
ove uv = k < 0 mentre v può essere v > 0, v < 0. in pratica il programma prima trova i possibili valori di k
poi cerca i valori di v:
LISTATO C++
#include <stdio.h>
#include <iostream.h>
#include <stdio.h>
#include <math.h>
int main(int argc, char *argv[])
{
double x, y, p, k, v;
double a, b;
int j, i;
k = 0;
v = 0;
cout << "inserisci un numero da fattorizzare ";
cin >> p;
do
{
k = k-1;
i = i+1;
a = 1-sqrt(1-4*k*p);
b = 1+sqrt(1-4*k*p);
27
} while (((a) != int(a)) && ((b) != int(b))) ;
do
{
v = v-1;
i = i+1;
x = a/(2* v);
y = b/(2* v);
} while (((p/x) != int(p/x)) && ((p/y) != int(p/y)));
if ((p/x) == int(p/x))
{
cout << "fattore "<< x << "\n";
}
else if ((p/y) == int(p/y))
{
cout << "fattore "<< y << "\n";
}
cout << "passi " << i;
cin >> j;
return 0;
}
Fattorizzazione con i logaritmi
Sia n = ab. Per le note proprietà dei logaritmi sappiamo che Log(n) = Log(ab) = Log(a) + Log(b).
Supponiamo che Log(a) = Log(b) quindi Log(n) = 2Log(a), cioè Log(a) = Log(n)/2
a  10
Log ( n )
2
Questo ci induce a cercare i fattori come
x1  10
Log ( n )
2
, x2  10
Log ( n )
2
possiamo utilizzare il parametro a al posto del già citato W o della radice
quadrata di n negli algoritmi precedenti per avere un migliore intervallo dove cercare le soluzioni
28
LISTATO C++
#include <cstdlib>
#include <iostream>
#include <math.h>
using namespace std;
int main(int argc, char *argv[])
{
long double m, x, y, p;
long i;
cout << "inserisci il numero da fattorizzare ";
cin >> p;
x = int(pow(10, log10(p)/2))+1;
y = int(pow(10, log10(p)/2))+1;
do{
x = x-1;
y = y+1;
i = i+1;
} while ( (int(p/x) != (p/x)) && (int(p/y) != (p/y)) );
cout << "fattore: " << x << "\n";
cout << "fattore: " << y << "\n";
cout << "passi: " <<i << "\n";
system("PAUSE");
return EXIT_SUCCESS;
}
LISTATO PARI/GP
{algo9(p) = local(p, d, x, y);
d = log(p)/log(10);
x = floor(10^d);
y = floor(10^d);
while (floor(p/x) != (p/x) && floor(p/y) != (p/y),
y = y+1;
x =x-1);
print(x);
print("----");
print(y);
return (1);}
29
Fattorizzazione con il metodo della bisezione
Sia p = ab il numero da fattorizzare con a, b numeri primi (nel caso si voglia trattare un problema RSA).
supponiamo di considerare p', p'', p' =a'b', p'' = a''b''
p' < p < p'' con a' < a < a'', b' < b < b''
ove a', a'', b', b'' non sono necessariamente numeri primi ma sono certamente dispari oppure tutti numeri
pari.
l'algoritmo procede come segue:
1) x = (a'' + a') / 2, y = (b'' + b') / 2
2) xy = p oppure (p è divisibile per x o per y) ?
2.1) se si abbiamo trovato la soluzione a = x, b = y (oppure x o y) Fine
2.2) altrimenti xp < p
2.2.1) se si poniamo a' := x, b' := y e torniamo al punto 1)
2.2.2) se no poniamo a'':= x, b'' := y e torniamo al punto 2)
la velocità dell'algoritmo dipende da come scegliamo p', p'' che ovviamente dovrebbero essere abbastanza
vicini a p, per questo motivo possiamo utilizzare come fattori di p', p''
la cosa più semplice è considerare p' = p-1, p'' = p+1, nel caso in cui p non sia un numero pari.
30
Esempio:
p = 91 = 13*7
p' = 91 +1 = 92 = 23 * 4
p'' = 91 -1 = 90 = 9 * 10
ora (23+9)/2 = 16 (non è la soluzione 16 = 13*2), (4+10)/2 = 7 (è la soluzione)
variante: a' = b', a'' = b''
L'approccio combinatorio - economico del problema
Guardiamo la cosa da un altro punto di vista. Supponiamo di poter disporre di molti calcolatori in rete e di
poter distribuire l'enorme carico di elaborazione dati tra questi computer per trovare velocemente la
soluzione al problema della fattorizzazione di grandi numeri (RSA). E' possibile ? Di quanti pc avrò bisogno ?
E' conveniente dal punto di vista economico ? Sia n il numero da fattorizzare. Ci basa trovare un fattore. Per
Fermat sappiamo che una soluzione deve essere minore della radice quadrata di n. Sia allora W il numero di
cifre della radice quadrata di n.
P = il numero di calcolatori in rete
C = numero di combinazioni (possibili soluzioni intere al problema, cioè possibili divisori di n)
che ogni calcolatore può valutare ogni secondo (o ogni unità di tempo)
31
T = secondi o (altra unità di tempo)
Q = numeri di cifre della radice quadrata di n
n = ab numero da fattorizzare
vale la relazione P*C*T = 10^Q
10^Q è il numero delle disposizioni con ripetizione di 10 numeri su k posti
a questo punto si sia j il costo medio unitario di ogni PC occorre valutare che il costo totale dei
PC in rete non superi una certa ben definita quota oltre la quale non converrebbe implementare
il sistema (sum(jC) < M). Infine il fattore tempo. T < Tmax perché occorre fissare un limite
massimo ragionevole oltre il quale non ha senso andare. Considerando tutte queste condizioni
possiamo progettare un sistema di calcolo distribuito
Fattorizzazione e ricerca operativa
Possiamo vedere le cose anche da un altro punto di vista, dal punto di vista della ricerca operativa
(programmazione non lineare). Il nostro problema diventa un problema di programmazione non lineare a
variabili intere:
32
funzione obiettivo P = X1 X2
variabili: X1, X2
vincoli: X1, X2 >0 e interi
e applicare i metodi della ricerca operativa e/o i software già predisposti per questo tipo di problemi
33
Fattorizzazione per differenza dei fattori
Se p  xy allora è ben evidente che: 0  x  y 
possiamo considerare anche
Ora x = p/y e x-y = k
p
 2 essendo 2 il primo possibile fattore (ingenerale
2
p
 q ove q è tale che non esiste nessun fattore di p minore o uguale a q .
q
troviamo che
y 2  yk  p  0 con y 
 k  k2  4p
dove possiamo
2
considerare k crescente (k = 0, 1, 2, …) o decrescente (K = p/2 –2, k =k-1, ……)
k  k2  4p
Se invece vogliamo trovare x y = p/x, quindi x  xk  p  0 con x 
dove possiamo
2
2
considerare k crescente (k = 0, 1, 2, …) o decrescente (K = p/2 –2, k =k-1, ……)
Dal fatto che 0  x  y  p / 2  2 e che
2 p  x  y  p troviamo facilmente che
p x
3
p  1.
4
k  k2  4p
 W che porta a
Anche in questo caso se stiamo trattando un problema RSA poniamo x 
2
k 2  4 p  (2W  k ) 2
k  4p  0
2
unito a k  4 p  0
2W  k  0
2W  k  0
2
k  2W  0
k  k2  4p
 W ho che
se x 
(k  2W ) 2  k 2  4 p
2
nel caso in cui
x
 k  2W  0
 k  k2  4p
 W allora
(k  2W ) 2  k 2  4 p
2
ed infine l’ultimo caso
34
x
 k  k2  4p
 W mi porta a
2
k2  4p  0
k  4p  0
unito a 2W  k  0
2W  k  0
k 2  4 p  (2W  k ) 2
2
35
LISTATO IN PARI/Gp
{diff1(p) =local(k, y, x);
k = 0;
y = (-k + sqrt(k^2+4*p))/2;
x = (-k - sqrt(k^2+4*p))/2;
while (truncate(p/y) != (p/y) && truncate(p/x) != (p/x) ,
k=k+1;
y = (-k + sqrt(k^2+4*p))/2;
x = (-k - sqrt(k^2+4*p))/2);
print(y);
print(x);
return(1);}
Fattorizzazione per individuazione dell’intervallo migliore
Partendo dalla già nota relazione
x2  y2
 xy e ponendo y = x+a arriviamo a risolvere una disequazione
2
di secondo grado le cui soluzioni sono:
 a  4 p  a2
 a  4 p  a2
2
con 4 p  a  0
x
x
2
2
Nell’ipotesi invece che y = x-a ho che
a  4 p  a2
a  4 p  a2
2
con 4 p  a  0
x
x
2
2
Il problema è capire quale valore di a assegnare. Nei problemi RSA le radici non sono né troppo vicine tra
loro né troppo lontane quindi un buon valore per a potrebbe essere
Stesso discorso se partiamo da
per y = x-a ottengo che
x
a p
( x  y) 2  4 p :
a2 p
a2 p
a2 p
a2 p
per y = x+a x 
,x 
,x 
2
2
2
2
36
Teoria dei grandi numeri
Come è possibile gestire in un normale calcolatore numeri molto grandi, diciamo numeri interi
a 200 cifre ? Vediamo l'algoritmo con un esempio e poi generalizziamo con il formalismo matematico
Supponiamo di voler calcolare 321*44 = 14124
sappiamo che ogni numero intero lo possiamo scrivere nella sua notazione polinomiale:
(3x 2  2 x  1)(4 x  4)
con X = 10
svolgiamo la moltiplicazione tra i polinomi e consideriamo che 12 mod 2 = 2 e che 12-(12 mod
10) = 10 (per i coefficienti del polinomio prodotto usiamo l'aritmetica modulo 10 ovvero
se abbiamo coefficienti maggiori o uguali a 10 cioè ad esempio 12 = 10 +2 scriviamo 12 = X+2
per poi continuare a semplificare e a ridurre modulo 10 finché tutti i coefficienti del polinomio
sono in base 10)
tralasciano i passaggi abbiamo che 1x  4 x  1x  2 x  4 cioè 14124 che è la soluzione
4
3
2
Il caso della somma è ancora più semplice perché
37
3x 2  2 x  1  4 x  4  3x 2  6 x  5 cioè 321 + 44 = 365 (anche per la somma se ce ne fosse stato
bisogno avremmo dovuto ridurre i coefficienti del polinomio in base l'aritmetica modulo 10)
Idem è il caso della differenza tra numeri
Questo ci permetterà di programmare un calcolatore per fargli compiere operazioni molto complesse che un
normale PC non riuscirebbe a fare. Infatti dall'algebra elementare esistono formule che generalizzano la
somma, la differenza la moltiplicazioni e la divisione tra polinomi di qualunque grado
h
k
 (ri x i )   (ui x i ) 
i 0
i 0
h
max( h , k )
 (r  u ) x
i 0
 r x u x  (  r u
i 0
i
i 0
i
i
hk
k
i
i
i
i
j 0 i  p  j
i
p
)x j
caso con la virgola:
12,6 +15,4 = x  2 
3
4
 x  5   2x  7  1  2x  8
x
x
Il caso della divisione è un po',più complesso anche se in realtà una divisione non è altro che una serie di
sottrazioni come una moltiplicazione non è che una serie di somme
38
Il Crivello di Eratostene
crivello di Eratostene (formulazione grafica: le x sono i primi perché non hanno intersezioni con "gialli")
14
13
Primo
12
11
Primo
10
9
8
7
Primo
6
5
Primo
4
3
2
Primo
1
1
2
3
4
5
6
7
39
Analisi della complessità computazionale di alcuni algoritmi di
fattorizzazione
In precedenti occasioni ci siamo occupati di studiare nuovi algoritmi di fattorizzazione in parte varianti della
fattorizzazione alla Fermat e del metodo delle divisioni banali. In generale gli algoritmi descritti risultano
essere efficienti solo nel caso in cui il numero da fattorizzare p si compone di fattori anche molto grandi ma
dello stesso ordine di grandezza o che comunque hanno un numero di cifre simile ovvero che non
differiscano di più di 3-4 cifre.
Vediamo per alcuni casi semplici come si può affrontare una stima della complessità computazionale.
Sia 𝑝 = 𝑥1 𝑥2 , 𝑠 = 𝑥1 + 𝑥2 da 𝑥 2 − 𝑠𝑥 + 𝑝 = 0 abbiamo che
𝑥1,2 =
𝑠 ± √𝑠 2 − 4𝑝
2
Che impone la condizione per l’esistenza del radicale 𝑠 ≥ 2√𝑝, s intero e pari perché somma di due primi
Quindi se cerchiamo s con s < n e 𝑠 ≥ 2√𝑝, e pari al massimo arriveremo alla soluzione in un numero di
𝑝
passi pari a: 2 − √𝑝 che in ogni caso è enorme rispetto alle dimensioni di p
40
D’altra parte un po’ meglio va se valutiamo di considerare le soluzioni tra i numeri dispari compresi tra 2 e
√𝑝 (in questo intervallo ci deve essere sempre la soluzione più piccola) perché in questo caso otterrei la
𝑝
soluzione in al massimo √2 − 1 passi.
Nelle considerazioni precedenti abbiamo supposto di cercare le soluzioni tra i numeri dispari ipotizzando di
non avere una tavola di primi già a disposizione.
Considerazioni identiche valgono per altri metodi già esposti come per
𝜑(𝑝) = (𝑥1 − 1)(𝑥2 − 1) = 4𝑎 ≤ 𝑝 => 𝑎 ≤
𝑝
, 𝑝 = 𝑥1 𝑥2
4
Dove ovviamente va aggiunta la condizione di a per l’esistenza del radicale.
41
Stima della complessità computazionale della fattorizzazione con il
metodo dell’attacco casuale (rapporti tra statistica e numeri primi)
Supponiamo che p sia un numero da fattorizzare molto grande (es RSA). Sappiamo che il fattore più piccolo
deve necessariamente trovarsi tra 2 e la radice quadrata di p. Supponiamo di avere un calcolatore che
generi numeri casuali e che ogni numero casuale generato sia un numero primo tra 2 e la radice quadrata
di p. Per ogni numero casuale estratto si verifica se è o no un divisore di p e , in caso negativo, si passa al
successivo numero. Ipotizziamo che il nostro programma tenga “memoria” dei numeri estratti in modo che
l generatore di “primi casuali” non possa ripetere mai lo stesso numero.
Al primo tentativo la probabilità di aver trovato la soluzione sarà
𝑃1 =
1
𝑙𝑜𝑔𝑥
𝑥 = 𝑥
𝑙𝑜𝑔𝑥
Ove è stato usato il ben noto teorema dei numeri primi e 𝑥 = 𝑖𝑛𝑡(√𝑝) e log(x) è il logaritmo naturale di x.
𝑥
In luogo di x/log(x) avremmo potuto anche usare una stima di ∫2
𝑑𝑦
log(𝑦)
Al secondo tentativo
1
𝑃2 = 𝑥
−1
𝑙𝑜𝑔𝑥
… al k-esimo tentativo
1
𝑃𝑘 = 𝑥
−𝑘+1
𝑙𝑜𝑔𝑥
42
Questa formula ci dà una stima della probabilità che al k-esimo tentativo (k intero) abbiamo di “indovinare”
la soluzione. Come si vede man mano che aumentano i tentativi aumenta la probabilità di “vittoria”,
probabilità che è comunque condizionata dalla grandezza di p numero da fattorizzare. Come si può ben
vedere all’aumentare delle dimensioni di p la probabilità diminuisce in modo esponenziale
Primi e probabilità
Sia datato ora un intervallo [a, b]
Prendiamo a caso un qualunque numero dispari in questo intervallo la probabilità che sia primo è
(𝑏 − 𝑎)/2
𝑃=(
)
𝑏/log(𝑏) − 𝑎/ log(𝑎)
Oppure
𝑃=
(𝑏 − 𝑎)/2
𝑏 𝑑𝑦
∫𝑎 𝑙𝑜𝑔(𝑦)
Con log(x) il logaritmo naturale di x e il simbolo = sta per “circa, valore approssimato”
Dove abbiamo usato il teorema dei numeri primi, se consideriamo intervalli che contengono numeri primi
molto grandi
43
44
Codice in C++ per generare una tabella di primi (per applicazioni reali è consigliabile usare una libreria
specifica o un programma per la gestione dei grandi numeri come PARI/GP o Python
#include <cstdlib>
#include <iostream>
#include <math.h>
long mcd(long a, long b) {
long t;
while(b !=0){
t = b;
b = a%b;
a = t;
}
return a;
}
using namespace std;
int main(int argc, char *argv[])
{
long k, p;
int j;
k = 3;
p = 2;
for(j= 1; j <= 20; j++)
{
if (mcd(p, k) ==1)
{
cout << k << "\n";
p = k*p;
}
k = k+2;
}
system("PAUSE");
return EXIT_SUCCESS;
}
45
Complessità computazionale degli algoritmi di fattorizzazione
In questo articolo valuteremo la complessità computazionale del migliore algoritmi di fattorizzazione
possibile (escludendo quelli di tipo quantistico).
Il teorema di numeri primi afferma che
𝜋(𝑥)
=1
𝑥→+∞ 𝑥
ln(𝑥)
lim
ovvero
𝜋(𝑥)~
𝑥
𝑙𝑛(𝑥)
Dove 𝜋(𝑥) è il numero dei primi minori o uguali a x. (per x opportunamente grande).
Un formulazione più evoluta dello stesso teorema mostra che
𝑥
𝜋(𝑥)~𝐿𝑖(𝑥) = ∫
0
𝑑𝑦
ln(𝑦)
Ove Li(x) è il logaritmo integrale
Ora prendiamo in esame il problema della fattorizzazione p = ab con a, b primi e supponiamo a < b.
Sappiamo che a deve essere compreso tra 2 e la radice quadrata di p. Il nostro obiettivo è trovare a
perché così b è facilmente calcolabile con b = p/a (problemi di tipo RSA). Per fattorizzare un qualunque
numero intero di procederà ricorsivamente come per p=ab con a, b primi.
Il numero dei numeri primi compresi tra 2 la radice quadrata di p (per p grande) è approssimativamente:
𝜋(√𝑝) −
2
2
√𝑝
~
−
ln(2) 𝑙𝑛(√𝑝) ln(2)
46
(anche se l’ultimo termine possiamo trascurarlo)
𝜋(√𝑝)~
√𝑝
𝑙𝑛(√𝑝)
Oppure:
√𝑝
𝜋(√𝑝)~ ∫
2
𝑑𝑦
ln(𝑦)
Grazie all’aiuto del software Mathematica stimiamo i passi necessari (nel caso peggiore) per trovare il
fattore più piccolo del numero p = ab con a, b primi molto grandi e a < b. Ci poniamo cioè nella condizione
di disporre di un algoritmo che ci fornisce tutti i numeri primi da 2 alla radice quadrata di p (anzi
supponiamo di averli già calcolati) e che altro non ci rimane da fare che verificare quali di questi numeri
divide p. Ci poniamo nel caso migliore possibile (algoritmo ideale).
Con il Mathematica:
p:=10^12
Sqrt[p]/Log[Sqrt[p]]
N[%]
NIntegrate[1/Log[x], {x, 2, Sqrt[p]}]
Facendo variare p (numero da fattorizzare) costruiamo la seguente tabella:
dimensione (dim)
P (numero da fattorizzare = ab
Steps (caso peggiore) stimati con
il teorema dei numeri primi
20
10^20
k*10^8
25
10^25
k*10^11
47
30
10^30
k*10^13
35
10^35
k*10^15
40
10^40
k*10^18
Ove k < 10, k > 1, k diverso per ogni P.
È facile provare che gli steps (passi nel caso peggiore del migliore algoritmo possibile) seguono la legge:
𝑆𝑡𝑒𝑝𝑠~𝑘 10
𝑖𝑛𝑡(
𝑑𝑖𝑚
)−2
2
Questa formula ci dà una stima per confrontare tutti gli algoritmi conosciuti e quelli nuovi per capire
quanto ancora è possibile migliorare. Nel nostro ragionamento ci siamo posti nel “caso peggiore” perché
ovviamente la soluzione potrebbe essere trovata anche in un solo passo o perché p è un quadrata perfetto
o magari perché un suo divisore è un numero relativamente piccolo che qualunque calcolatore può
facilmente trovare in pochi secondi.
Deduciamo che gli steps seguono una legge di tipo esponenziale rispetto alla dimensione del numero da
fattorizzare e questo nel peggiore del migliore algoritmo di fattorizzazione possibile di tipo non quantistico
m algebrico.
Deduciamo che P != NP.
48
Funzioni che generano molti numeri primi e i software che generano
numeri primi al computer
𝑓(𝑥) = 𝑥 2 + 𝑥 + 𝑞,
𝑞 = 2,3,5,11,17,41; 0 ≤ 𝑥 ≤ 𝑞 − 2
𝑓(𝑥) = 𝑥 2 − 𝑥 + 41, 𝑥 = 1, 2, 3, … . , 40
𝑓(𝑥) = 𝑥 2 − 79𝑥 + 1601, 𝑥 = 1, 2, 3, … . , 79
𝑓(𝑥) = 2𝑥 2 + 𝑝; 𝑝 = 2, 3, 5, 11, 29; 0 ≤ 𝑥 ≤ 𝑝 − 1
Sono tutte funzioni che generano numeri primi sotto le condizioni restrittive indicate nelle formule. Da
verifiche di tipo statistico fatte al computer risulta che in ogni caso queste funzioni continuano a generare
una gran quantità di numeri primi (non solo numeri primi) anche al di fuori dei limiti descritti, quindi
potrebbero essere usate per tentare di trovare i fattori di numeri primi molto grandi. A queste possiamo
aggiungere i numeri primi di Mersenne e di Fermat ovvero:
𝑥
𝑓(𝑥) = 22 +1, 𝑓(𝑥) = 2𝑥 − 1
49
Per realizzare delle tabelle di numeri primi al computer possiamo usare specifici software come il PARI/GP
e il Mathematica
In PARI/GP:
{primi1() = local(i);
i = 2;
while(i<=100,i++;write("primi1.txt", print(prime(i))));
return(1);}
{primi2() = local(i);
i = 3;
while(i<=100,i = i+2; if(isprime(i), write("primi2.txt",i)));
return(1);}
Mentre in Mathematica abbiamo la funzione
Table[Prime[n], {n, 1, 100}]
Oppure per verificare se un numero è primo o no PrimeQ[numero]
50
Per fattorizzare numeri interi anche molto grandi possiamo ancora usare il Mathematica :
scomposizione = FactorInteger[numero]
In PARI/GP invece per fattorizzare un numero si usa il comando
factor(numero)
e la funzione isprime(numero) per verificare se un numero è primo o no (test di primalità);
nextprime(numero) e precprime(numero) indicano invece rispettivamente il successivo e il precedente
numero primo di un numero dato. Per finire prime(i) indica semplicemente l’i-esimo numero primo.
51
Numeri primi in Java
Per generare numeri primi in JAVA (potentissimo linguaggio di programmazione che consente di gestire
grandi numeri grazie alle librerie java.math.biginteger e java.math.bigdecimal) possiamo usare il seguente
codice:
package primi2;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.*;
public class Main {
public static void main(String[] args) {
long j =1;
BigInteger inizio = new BigInteger("3");
BigInteger passo = new BigInteger("2");
inizio = inizio.add(passo);
System.out.println(inizio);
while(j < 100000){
if( inizio.isProbablePrime(100000000))
System.out.println(inizio);
inizio = inizio.add(passo);
j = j+1;
}
j = j+1;
inizio = inizio.add(passo); }
}
Il programma altro non fa che considerare tutti i numeri dispari e poi verifica se sono dei numeri primi. In
caso affermativo li stampa a video altrimenti passa al numero primo successivo
52
Fattorizzare grandi numeri con JAVA
Con questo codice realizziamo un potente programma di fattorizzazione in JAVA che però richiede di sapere
in anticipo l’ordine di grandezza dei fattori in bit (es : 512- nel nostro caso abbiamo messo 40 per
semplificare e 187 il numero da fattorizzare). Quando viene stampato il resto zero abbiamo trovato un
fattore. Il programma altro non fa che generare numeri casuali primi di una certa grandezza e quindi
verifica se sono fattori del numero p:
package fattore;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.*;
import java.security.*;
public class Main {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
BigInteger p = new BigInteger("187");
long i = 2;
BigInteger fatt = BigInteger.probablePrime(40 ,new SecureRandom());
while( i < 100 ){
fatt = BigInteger.probablePrime(40 , new SecureRandom());
System.out.println("fattore" + fatt);
System.out.println("resto" + p.mod(fatt));
}
}
}
53
Un algoritmo per fattorizzare numeri RSA
Supponiamo di trovarci nella seguente condizione n = pq dove p, q sono numeri primi anche molto grandi.
𝜑(𝑛) = 𝜑(𝑝𝑞) = 𝜑(𝑝)𝜑(𝑞) = (𝑝 − 1)(𝑞 − 1) = 4𝑎
Dove ho usato il fatto che MCD(p, q)= 1, essendo p, q primi e le note proprietà della funzione phi di Eulero.
Svolgendo i calcoli (q = p/ n) si arriva facilmente a:
𝑝2 − 𝑝(𝑛 + 1 − 4𝑎) + 𝑛 = 0
Le cui soluzioni sono
𝑝=
𝑛 + 1 − 4𝑎 ± √(𝑛 + 1 − 4𝑎)2 − 4𝑛
2
E operando sull’esistenza del radicale ho le condizioni (si cerca a tra gli interi) :
𝑎>
𝑛 + 1 + 2√𝑛
𝑛 + 1 − 2√𝑛
∪𝑎 <
4
4
Quindi una possibile implementazione in C++ è:
caso 1) in C++
#include <cstdlib>
#include <iostream>
#include <math.h>
using namespace std;
int main(int argc, char *argv[])
{
double p, q, n;
long i;
i = 0;
double a;
cout << "inserisci il numero da fattorizzare ";
cin >> n;
a = floor((n+1-2*sqrt(n))/4);
do{
p = ((n+1-4*a)- sqrt(pow(n+1-4*a, 2)-4*n))/2;
a = a-1;
i = i+1;
}while(floor(p) != p);
cout << "fattore " << p << "\n";
cout << "fattore " << n/p << "\n";;
cout << "passaggi " << i << "\n";
system("PAUSE");
return EXIT_SUCCESS;
}
54
Questo algoritmo risulta essere particolarmente veloce e affidabile anche per numeri molto grandi
caso 2) in C++
#include <cstdlib>
#include <iostream>
#include <math.h>
using namespace std;
int main(int argc, char *argv[])
{
double p, q, n;
long i;
i = 0;
double a;
cout << "inserisci il numero da fattorizzare ";
cin >> n;
a = floor((n+1-2*sqrt(n))/4);
do{
p = ((n+1-4*a)+sqrt(pow(n+1-4*a, 2)-4*n))/2;
a = a-1;
i = i+1;
}while(floor(p) != p);
cout << "fattore " << p << "\n";
cout << "fattore " << n/p << "\n";;
cout << "passaggi " << i << "\n";
system("PAUSE");
return EXIT_SUCCESS;
}
caso 3) in C++
#include <cstdlib>
#include <iostream>
#include <math.h>
using namespace std;
int main(int argc, char *argv[])
{
double p, q, n;
long i;
i = 0;
double a;
cout << "inserisci il numero da fattorizzare ";
cin >> n;
a = floor((n+1+2*sqrt(n))/4);
do{
p = ((n+1-4*a)+ sqrt(pow(n+1-4*a, 2)-4*n))/2;
a = a+1;
i = i+1;
}while(floor(p) != p);
cout << "fattore " << p << "\n";
cout << "fattore " << n/p << "\n";;
cout << "passaggi " << i << "\n";
55
system("PAUSE");
return EXIT_SUCCESS;}
caso 4) in C++
#include <cstdlib>
#include <iostream>
#include <math.h>
using namespace std;
int main(int argc, char *argv[])
{
double p, q, n;
long i;
i = 0;
double a;
cout << "inserisci il numero da fattorizzare ";
cin >> n;
a = floor((n+1+2*sqrt(n))/4);
do{
p = ((n+1-4*a)- sqrt(pow(n+1-4*a, 2)-4*n))/2;
a = a+1;
i = i+1;
}while(floor(p) != p);
cout << "fattore " << p << "\n";
cout << "fattore " << n/p << "\n";;
cout << "passaggi " << i << "\n";
system("PAUSE");
return EXIT_SUCCESS;}
Per I grandi numero si consiglia l’uso del PARI/GP con I codici che riporto di seguito
Caso 1) in PARI/GP
{fatto(n) =
local(p, q, a);
a = truncate((n+1-2*sqrt(n))/4);
p = ((n+1-4*a)-sqrt((n+1-4*a)^2-4*n))/2;
while ((truncate(n/p) != (n/p)),
p = ((n+1-4*a)-sqrt((n+1-4*a)^2-4*n))/2;
a=a-1;);
print(p);
print(n/p);
return (1);}
Caso 2) in PARI/GP
56
{fatto(n) =
local(p, q, a);
a = truncate((n+1-2*sqrt(n))/4);
p = ((n+1-4*a)-sqrt((n+1-4*a)^2-4*n))/2;
while ((truncate(n/p) != (n/p)),
p = ((n+1-4*a)+sqrt((n+1-4*a)^2-4*n))/2;
a=a-1;);
print(p);
print(n/p);
return (1);}
Caso 3) in PARI/GP
{fatto(n) =
local(p, q, a);
a = truncate((n+1+2*sqrt(n))/4);
p = ((n+1-4*a)+sqrt((n+1-4*a)^2-4*n))/2;
while ((truncate(n/p) != (n/p)),
p = ((n+1-4*a)+sqrt((n+1-4*a)^2-4*n))/2;
a=a+1;);
print(p);
print(n/p);
return (1);}
Caso 4) in PARI/GP
{fatto(n) =
local(p, q, a);
a = truncate((n+1+2*sqrt(n))/4);
p = ((n+1-4*a)+sqrt((n+1-4*a)^2-4*n))/2;
while ((truncate(n/p) != (n/p)),
p = ((n+1-4*a)-sqrt((n+1-4*a)^2-4*n))/2;
a=a+1;);
print(p);
print(n/p);
return (1);}
Questi algoritmi sono parte integrante di altri sistemi già descritti nell’articolo “teoria dei numeri” e
pubblicati in questo sito.
Ora vediamo le cosa da una prospettiva leggermente diversa. Sia
P = xy , x = p-a, y = p-b con a > 0, b > 0
𝑝 = (𝑝 − 𝑎)(𝑝 − 𝑏) = 𝑝2 − 𝑝𝑏 − 𝑝𝑎 + 𝑎𝑏
𝑎=
𝑝2 − 𝑝 − 𝑝𝑏
>0
𝑝−𝑏
57
Allora possiamo scegliere b < p-1 e decrementare b fino a trovare un a intero che soddisfi le condizioni
iniziali per poi calcolare x, y ma questo procedimento ci porterebbe al metodo delle divisioni banali che è
utile solo quando un fattore è molto più piccolo rispetto all’altro quindi non nel caso di un problema RSA.
Codice C++ ( variante a divisioni banali)
#include <cstdlib>
#include <iostream>
#include <math.h>
using namespace std;
int main(int argc, char *argv[])
{
double a, b, p, i;
i =0;
cout << "Inserisci il numero da fattorizzare ";
cin >> p;
b = p-1;
do{
a = (pow(p,2)-p-p*b)/(p-b);
b--;
i++;
} while (floor(a) != a);
cout << p-a << "\n";
cout << p/(p-a) << "\n";
cout << "passaggi" << i << "\n";;
system("PAUSE");
return EXIT_SUCCESS;
}
Facciamo un ulteriore passo avanti cercando di migliorare il modo per trovare le soluzioni intere:
58
Possiamo però ipotizzare che a = b ( a è circa b). Questo ci porta a all’equazione:
𝑏 2 − 2𝑝𝑏 + 𝑝2 − 𝑝 = 0
𝑏 = 𝑝 ± √𝑝
Consideriamo per semplicità la radice più piccola di b quella con il segno – e incrementiamo o
decrementiamo b di una unità alla volta, allora il codice del nostro programma diventa:
using namespace std;
int main(int argc, char *argv[])
{
double a, b, p, i;
i =0;
cout << "Inserisci il numero da fattorizzare ";
cin >> p;
b = floor(p-sqrt(p));
do{
a = (pow(p,2)-p-p*b)/(p-b);
b++; // va bene anche b--;
i++;
} while (floor(a) != a);
cout << p-a << "\n";
cout << p/(p-a) << "\n";
cout << "passaggi" << i << "\n";;
system("PAUSE");
return EXIT_SUCCESS;
}
Variante
Possiamo considerare P = xy, con x, y numeri primi diversi da 2 e y = x-a con a numero intero ovviamente
pari essendo la differenza di numeri primi quindi di numeri dispari. Facendo le sostituzioni otteniamo che:
𝑥 2 − 𝑥𝑎 − 𝑝 = 0
Analogamente se x = y-b ho che
𝑦 2 − 𝑦𝑏 − 𝑝 = 0
Ove
𝑥=
𝑎 ± √𝑎2 + 4𝑝
2
Quindi lì applicazione in C++ che ne deriva sarà:
59
#include <cstdlib>
#include <iostream>
#include <math.h>
using namespace std;
int main(int argc, char *argv[])
{
double a, p, i, x;
i =0;
cout << "Inserisci il numero da fattorizzare ";
cin >> p;
a =0;
do{
x = (a+sqrt(pow(a,2)+4*p))/2;
a = a-2;;
i++;
} while (floor(x) != x);
cout << x << "\n";
cout << p/x << "\n";
cout << "passaggi" << i << "\n";;
system("PAUSE");
return EXIT_SUCCESS;
}
Un crivello per generare numeri primi (poco efficiente)
NUMERO DISPARI
3
5
7
9
11
13
15
17
19
21
23
25
MASSIMO COMUN
DIVISORE
MCD(3,5)=1 =< 3, 5
primi
MCD(15,7)=1 => 7 primo
MCD(105, 9) != 1 => 9
non è primo
MCD(105, 11) =1 => 11
primo
MCD(1155, 13)=1 => 13
è primo
MCD(15015, 15) != 1 =>
15 non è primo
MCD(15015, 17) = 1 =>
17 è primo
MCD(255255, 19)=1 =>
19 è primo
MCD(4849845, 21) != 1
=> 21 non è primo
MCD(4849845, 23) = 1
=> 23 è primo
MCD(111546435, 25) !=
LISTA DEI PRIMI
PRODOTTO DEI PRIMI
3, 5
3*5 = 15
3, 5, 7
15*7 = 105
3, 5, 7, 11
105*7 =1155
3, 5, 7, 11, 13
1155*13 =15015
3, 5, 7, 11, 13, 17
15015*17= 255255
3, 5, 7, 11, 13, 17, 19
255255*19= 4849845
3, 5, 7, 11, 13, 17, 19, 23
4849845*23=
111546435
60
1 => 25 non è primo
MCD(111546435, 27)
!=1 => non è primo
ecc
ecc
…
…
IL MCD va calcolato con il metodo di Euclide
27
ecc
…
ecc
…
Vantaggi del metodo:

Il metodo è facilmente programmabile con qualunque linguaggio (PARI/GP, C/C++, ecc)

Il sistema genera effettivamente tutti e soli numeri primi ovvero la lista dei primi
Svantaggi del metodo:

Il metodo richiede un enorme capacità di calcolo ( ad ogni passo si calcola il prodotto di n primi
consecutivi) tuttavia grazie ai moderni PC in grado di moltiplicare numeri a centinaia di cifre questo
problema è solo in parte risolto (vedi es PARI/GP, Python, Mathematica).
Applicazione: a primo  MCD (a, prodotto dei primi < int(Radq(a))=1
La formula per i numeri primi
Se p = ab e p dispari , con a < b a, b primi allora MCD(p, floor(sqrt(p)!!))= a oppure a p ove il simbolo !! sta
ad indicare il semifattoriale cioè il prodotto di tutti i dispari minori della radice quadrata di p (sqrt(p)). In
tutti i casi la potenza di calcolo necessaria è davvero impressionante.
Più in generale Se p = ab, allora MCD(p, floor(sqrt(p)!))= a oppure a p ove ! è il fattoriale di p.
In codice PARI/GP
{fattore31(p) = local(w);
w = gcd(p,floor(sqrt(p))!);
print(w);
print(p/w);
return(1);}
Se invece vogliamo semplicemente trovare una lista di primi, basterà
{tabella2() = local(p, i, k);
k = 3;
61
k = k+2;
p = 3;
for(i=1, 30,
if(gcd(p, k) == 1, print(k), p = p*k);
k = k+2);
return(1);}
{tabella() = local(p, i);
p = 3;
p = p+2;
for(i=1, 30,
if(gcd(p, floor(sqrt(p))!) == 1, print(p));
p = p+2);
return(1);}
SISTEMI “BRUTE FORCE” PER LA FATTORIZZAZIONE DI GRANDI NUMERI
USANDO IL CALCOLO PARALLELO
Supponiamo di dover fattorizzare un numero molto grande (ad esempio un classico problema RSA).
Ovviamente ci poniamo nell’ipotesi che non abbia divisori pari (2, 4, 6, ecc) e che non sia un primo di
Mersenne o di Fermat perché in questi casi la soluzione sarebbe molto facile da trovare.
Un tipico algoritmo “brute force” (forza bruta) è:
VERSIONE IN PYTHON
import math
def fattore(s):
i=3
while (i*int(s/i) != s) and( i <= math.sqrt(s)):
i = i+2
print(i)
print(s/i)
return ('fine')
62
VERSIONE IN PARI/GP
{fattore(s) = local(i);
i = 3;
while(i*floor(s/i) != s && i <= sqrt(s), i = i+2);
print(i);
print(s/i);
return(1);}
Dove ho specificato sia la versione in Python che in PARI/GP (due ottime soluzioni software per la gestione
di numeri di grandi dimensioni). In questo caso la verifica viene fatta su semplicemente tutti i possibili
numeri dispari (che ovviamente comprendono anche i primi) da 3 alla radice quadrata del numero da
fattorizzare in quanto dalla teoria dei numeri sappiamo che in questo intervallo deve esserci il fattore più
piccolo
Usando un solo computer ci potrebbero volere molti anni per trovare la soluzione cioè per trovare i fattori
di p = ab con a, b numeri primi molto grandi
Una soluzione consiste nel far “girare” il programma di fattorizzazione in diversi computer assegnando ad
ogni PC un intervallo diverso.
Un possibile schema potrebbe essere
PC1: da 3 a 10^10-1
PC2: da 10^10-1 a 10^20-1
PC3 da 10^20-1 a 10^30-1
Ecc.
Il limite superiore no potrà essere superiore alla radice quadrata di p (numero da fattorizzare)
Quindi nel generico PC del nostro laboratorio avremo
VERSIONE IN PARI/GP
{fattore(s, lim_inf, lim_sup) = local(i, lim_inf, lim_sup);
63
i = lim_inf;
while(i*floor(s/i) != s && i <= lim_sup, i = i+2);
print(i);
print(s/i);
return(1);}
Analogamente possiamo scrivere il codice in altri linguaggi ad es il Python o il C++
A questo punto bisogna stabilire oltre gli intervalli opportuni anche il numero di calcolatori necessari per
ottenere la soluzione in tempi ragionevoli.
Per abbattere i costi si potrebbe stabilire gli intervalli e assegnare ogni intervallo a un PC differente
tramite una specie di ” concorso” via internet: coloro che vogliono partecipare installeranno il PARI/GP
nel loro PC e poi faranno girare il programma prendendo l’intervallo che verrà loro assegnato per e-mail
nel momento dell’iscrizione al concorso. Il vincitore avrà un premio in denaro.
La classica versione (in Java) di un programma che fattorizza un qualunque numero con i meccanismi della
forza bruta è:
package fattore2;
public class Main {
public static void main(String[] args) {
double p, i;
p = 1234567891273.0;
i = 3;
while ( p%i != 0.0 ){
i = i+2.0;
}
System.out.println(i);
System.out.println(p/i);
64
}
}
Fattorizzare con il massimo comun divisore
Un possibile algoritmo di fattorizzazione che usa il massimo comun divisore MCD è:

sia d un valore di partenza e p il numero da fattorizzare (d=2, d=p, , d = p/2, d=int(sqrt(p))

c = MCD(p, d)

c = 1 ? se no ovvero se c <> 1 abbiamo finito un fattore di p è c

altrimenti c = c+1 (oppure c = c-1) e si ritorna al punto precedente
Abbiamo sviluppato degli esempi in Python variando il valore d iniziale:
import math
def mcd(a,b):
if b == 0:
return a
else:
return mcd(b, a%b)
def fatt(p):
d = p+1;
k = mcd(p, d);
while k == 1:
d = d+1;
k = mcd(p, d);
print (k);
print (p/k);
return (1);
def fatt2(p):
d = p-1;
k = mcd(p, d);
while k == 1 and d > 1:
d = d-1;
k = mcd(p, d);
print (k);
print (p/k);
return (1);
def fatt3(p):
d = math.floor(math.sqrt(p));
k = mcd(p, d);
65
while k == 1 and d > 1:
d = d-1;
k = mcd(p, d);
print (k);
print (p/k);
return (1);
def fatt4(p):
d = math.floor(math.sqrt(p));
k = mcd(p, d);
while k == 1:
d = d+1;
k = mcd(p, d);
print (k);
print (p/k);
return (1);
def fatt5(p):
d = 2;
k = mcd(p, d);
while k == 1:
d = d+1;
k = mcd(p, d);
print (k);
print (p/k);
return (1);
def fatt6(p):
d = math.floor(p/2);
k = mcd(p, d);
while k == 1:
d = d+1;
k = mcd(p, d);
print (k);
print (p/k);
return (1);
def fatt7(p):
d = math.floor(p/2);
k = mcd(p, d);
while k == 1:
d = d-1;
k = mcd(p, d);
print (k);
print (p/k);
return (1);
La velocità e l'efficacia di questo algoritmo dipendono da come sono distribuiti i fattori di n=pq e
dal valore iniziale d
66
Infatti non sempre è più veloce di altri sistemi già precedentemente descritti come quello che,
partendo dalle relazioni p-q = 2a e n = pq arriva a q^2 +2aq-n=0 ovvero , sempre in Python
import math
def potenza(a,b):
if b == 0:
return 1
else:
return a*potenza(a,b-1)
def fatto(p):
n = 0.0;
x = -n+math.sqrt(p+potenza(n,2));
while p/x != math.floor(p/x):
n = n+1;
x = -n +math.sqrt(p+potenza(n,2));
print(x);
print(p/x);
return (1);
Un metodo per fattorizzare grandi numeri
Supponiamo ancora una volta di dover trovare i fattori di un numero p = ab dove p ha almeno 21
cifre ovvero è dell'ordine dei 10^(21-1).
Il primo passo sarà quello di trovare tutti gli x, y interi tali che x+y = 21 a partire (se esiste) dalla
combinazione in cui x, y sono uguali(stiamo supponendo che i due fattori non abbiano dimensioni
troppo diverse)
Nel nostro caso p = 10^(21-1) ovvero p di 21 cifre
Cifre del
fattore a
Cifre del
fattore b
Dimension Intervallo
i di a
di ricerca
per a
Costo
Dimension Intervallo
computazi i di b
di ricerca
onale per a
per b
10
11
10^(10-1)
10^9 10^10
(10^1010^9)/2
10^(11-1)
10^10 –
10^11
(10^1110^10)/2
9
12
10^(9-1)
10^8-10^9 (10^910^8)/2
10^(12-1)
10^1110^12
(10^1210^11)/2
…
…
….
…
…
…
….
…
Costo
computazi
onale per b
Ovviamente ma mano la dimensioni di a diminuiscono quelle di b aumentano quindi nella ricerca ci
67
conviene concentrarci sul fattore più piccolo ovvero su a e considerare nell'intervallo della ricerca
solo i numeri dispari (se p fosse divisibile per un numero pari l'ultima cifra dovrebbe terminate per
0, 2, 4, 6, 8 quindi sarebbe facilmente individuabile)
Purtroppo questo sistema ha dei costi ancora esponenziali ovvero dell'ordine di (10^n-10^n-1)/2
tuttavia per riga della tabella possiamo pensare di impegnare differenti calcolatore per accelerare i
tempi di elaborazione. Nei problemi RSA l'esperienza mostra che anche nel caso di centinaia di
cifre i fattori di p differivano al massimo di 3-4 cifre.
La fattorizzazione Geometrica
La fattorizzazione geometrica consente (almeno teoricamente) di trovare i fattori di un numero usando
solo considerazioni di tipo geometrico e non algebrico. Dato che tutti i numeri interi e non si possono
rappresentare in una retta reale questo procedimento consentirebbe, almeno in linea teorica, di
fattorizzare in modo molto veloce (quasi istantaneo) qualunque tipo di numero. Il problema è che è
impossibile rappresentare numeri di grandi dimensioni in una retta reale e quindi il metodo per questo
motivo è di scarsa praticità. Di seguito proponiamo alcuni schemi che usano il teorema di Euclide e la
similitudine per spiegare in dettaglio come avviene il procedimento.
68
69
70
Generare una lista di primi usando il teorema di Wilson e la primalità con
Fermat
Il teorema di Wilson afferma che:
Sia N un intero > 2. allora N è primo sse (N-1)! = -1 (mod N)
il simbolo = sta per congruente
Questo criterio di primalità non è molto efficiente perché comunque richiede una certa potenza di
calcolo. Per questo motivo proponiamo un programma scritto con il potente linguaggio di
programmazione Python che permette di gestire numeri interi molto grandi :

tramite la funzione Wilson() di generare una lista di primi

tramite la funzione testwilson(p) di effettuare un test di primalità

tramite la funzione fattore(p) vengono generati numeri pseudocasuali tra 2 la radice
quadrata del numero da fattorizzare fino a trovare una soluzione.

Tramite la funzione testprimo(p) si fa un test di primalità e nel caso il numero non sia
primo viene fornito un fattore
Nel codice sono presenti anche altre funzioni utili alla teoria dei numeri, alla fattorizzazione degli
interi e alla ricerca dei numeri primi
Ecco il codice in Python
import math
def mcd(a,b):
if b == 0:
return a
else:
return mcd(b, a%b)
def fattoriale(x):
if x < 2:
return 1
else:
return x*fattoriale(x-1)
71
Per il fattoriale c'è anche la funzione math.factorial(x) e per la potenza math.pow(x,y)
oppure a**n
def divisore(num,den):
if den ==1:
return num
else:
return float(num)/den
def potenza(a,b):
if b == 0:
return 1
else:
return a*potenza(a,b-1)
def wilson():
n = 3;
while(n < 100):
if fattoriale(n-1)%n == -1%n:
print(n);
n = n+1;
return 1;
def testwilson(n):
if fattoriale(n-1)%n == -1%n:
print('è primo');
else:
print('non è primo');
return 1;
def fattore(p):
import random;
import math;
x = math.floor(random.random()*math.sqrt(p));
while(p/x != math.floor(p/x)):
x = math.floor(random.random()*math.sqrt(p));
print (x);
print (p/x);
return 1;
def testprimo(p):
i = 2;
while i < p:
if mcd(i,p) != 1:
print('non è primo');
print(mcd(i,p));
return 0;
i = i+1;
return 1;
Per rimanere nel tema della primalità, possiamo costruire un programma in Python che sfrutti il
72
piccolo teorema di Fermat ovvero se N è primo, per ogni intero a primo con N ho che
a^(N-1)= 1 mod N. Questo dal punto di vista logico equivale a dire che se esiste un a tale che
MCD(a,N) = 1 e a^(N-1) != 1 allora N non è primo. Quindi possiamo scrivere il seguente test di
primalità:
def test2primo(N):
j = 2;
while j < N-1:
if mcd(j, N) == 1:
if (j** (N-1))%N != 1%N :
return 'non è primo';
j = j+1;
return 'è primo';
Ove ** è la funzione potenza che potevamo sostituire con potenza(a,b).
Nota:
Per generare numeri interi casuali che possono essere fattori di un numero intero p possiamo usare
anche il PARI/Gp secondo il codice
{fattor(p) = local(w);
w = 1+random(floor(sqrt(p)));
while( p/w != floor(p/w), w = 1+random(floor(sqrt(p))));
print(w);
print(p/w);
return(1);}
Algoritmi di primalità
Un noto problema matematico è quello di determinate se un numero p è primo o no. E’ ben evidente che
se MCD(p, (p-1)!) = 1 p non potrebbe avere divisori banali quindi deduciamo che p deve essere primo
73
mentre se MCD(p, (p-1)!) <> 1 ovviamente dovremmo dedurre che p deve essere composto. Quindi
arriviamo alla conclusione che
P primo  MCD(p, (p-1)!) = 1
Il fattoriale di un numero n ! = n(n-1)(n-2)…….3*2*1 ma si può anche calcolare usando la formula ricorsiva:
n! = n(n-1)! Ove 0! = 1! = 1. Il MCD(p, (p-1)!) si può calcolare agevolmente con il famoso algoritmo di
Euclide. Con numeri abbastanza grandi il fattoriale potrebbe costituire un problema perché occorrerebbe
disporre di calcolatori molto potenti capaci di gestire numeri interi a miglia di cifre. Un aiuto ci potrebbe
venire da software specifici come il PARI/gp o il Mathematica ma ovviamente entro un certo limite.
Possiamo diminuire la complessità computazione sostituendo a (p-1)! Il termine (int(√p))! applicando il
principio della fattorizzazione alla Fermat. Quindi P primo  MCD(p, int((√p)!) = 1.
Test di primalità probabilistici
Il piccolo teorema di Fermat afferma che
Se a è in intero e p è un numero primo ap=a modulo p
Applicando la legge logica che afferma essere equivalenti p => q e non q => non p abbiamo
Se esiste un intero a 1 < a < p tale che ap != a modulo p allora p non è primo Allo stesso modo ricordiamo
un altro importante teorema che afferma:
per ogni coppia di interi x, y e per ogni p primo => (x+y)p=xp+yp (mod p)
74
La dimostrazione di questo teorema molto semplice si basa sul teorema del binomio (sviluppo)
Quindi in modo analogo:
Se esiste una coppia di interi x, y | (x+y)p != xp+yp (mod. p) allora p non è primo . Possiamo allora tentare
di unire i due teoremi per formulare un interessante algoritmo probabilistico che ci dirà in modo veloce se
un numero è un primo o è composto.
ALGORITMO:

Sia dato un p intero dispari e non un quadrato perfetto

Tramite un generatore di numeri casuali formiamo delle coppie (un certo numero, più è alto più è
alta la probabilità di non fallire) di interi casuali x, y con

1 < x < p, 1 < y < p
Se troviamo per tutte le coppie x, y (x+y)p = xp+yp (mod. p) e (xp=x mod p, yp=y mod p) allora p è
probabilmente primo.

Altrimenti se esistono x, y | (x+y)p = xp+yp (mod. p) e (xp != x mod p oppure yp != y mod p) allora p
non è primo.
Nota:
(x+y)p=xp+yp=x+y=(x+y) mod p ovvero zp=z mod p , con z= x+y
Un nuovo test di primalità
Il piccolo teorema di Fermat afferma che:
se p è un numero primo allora per ogni intero a con MCD(a, p)=1 (p non divide a) , allora
a^(p-1) = 1 mod p (qui l'= sta per congruo)
Il teorema sui residui quadratici afferma che
75
per ogni numero primo p > 2 e per ogni numero a non divisibile per p (MCD(a, p)=1) si
verifica uno dei seguenti fatti:
a^((p-1)/2) = 1 oppure a^((p-1)/2)=-1 mod p (qui l'= sta per congruo)
Da questi due importantissimi teoremi applicando l'equivalenza logica che
p=>q equivale a non q => non p
e che un numero intero è primo o non è primo
formuliamo il seguente test di primalità ove assumiamo che N sia un numero dispari e non un
quadrato perfetto (in questi casi sarebbe immediato verificarne la primalità anche per numeri con
moltissime cifre):

N dispari e non un quadrato perfetto (altrimenti N non è primo)

Se esiste un a intero 2 < a < N tale che

MCD(a,N)= 1 e

a^(N-1) != 1 mod N e

a^((N-1)/2) !=1 mod N AND a^((N-1)/2) !=-1
■

allora N non è primo
altrimenti è primo
Osserviamo che a causa dei pseudo primi di Carmichael i test di primalità proposti se indicano che
un numero non è primo certamente non è un numero primo, mentre se indicano che è primo è da
intendersi come “probabilmente primo”
Il codice seguente è scritto in Pyton ed è di semplice lettura
def mcd(a,b):
if b == 0:
return a
else:
return mcd(b, a%b)
76
def test2primo(N):
j = 2;
while j < N:
if mcd(j, N) == 1:
if (j**(N-1)%N != 1%N) and ((j** ((N-1)/2))%N != 1%N) and ((j** ((N1)/2))%N != -1%N):
return 'non è primo';
j = j+1;
return 'è primo';
77
Scarica