C++ - Ricerca numeri primi

Pubblicità
In realtà il bitset va bene. L'espansione riguarda i nuovi N valori da sottoporre al crivello, dove N è una costante.
...
Come detto nel precedente post,
eh sì, le ottimizzazioni sul linguaggio le lascio a te, il C++ non lo conosco per cui il codice è arrangiato in base alle scarne info che ho, preferisco concentrarmi sull'algoritmo. Abbiamo postato quasi in contemporanea, credo a distanza di un paio di secondi, non avevo letto il tuo ultimo post e mi riferivo al precedente
Però non tieni conto del fatto che, considerando per esempio i=13, è inutile partire 13*3, perché 13*3, 13*5, 13*7, 13*9 e 13*11 sono già stati cancellati in precedenza. Quindi tanto vale partire da i^2 .
Vero! e all'inizio è così che avevo fatto ed è andato tutto bene fino ad una certa dimensione, poi perde una manciata di primi 😡: ho fatto un po' di controlli con Mathematica online (https://www.wolframalpha.com/input/?i=calculator) e quando iniziavo con i=p^2 su intervalli grandi (a memoria con estremo superiore >16 milioni) perde per strada 4 numeri primi, segno che è "scappato" qualcosa, probabilmente ho fatto un errore di codifica che non vedo solo io, magari ci riprovo perché con i=p^2 tagliavo un bel po' di marcature inutili.
Per la cronaca, con Mahtematica il controllo sulla quantità di numeri primi la fai mettendo come input PrimePi[n]
wa.webp
la cosa mi ha infastidito parecchio e se ho tempo vorrei capire dove diavolo sta l'intoppo che mi ha portato a perdere primi iniziando a setacciare dal quadrati del primo in analisi
Inoltre a questo punto quello che tu chiami limiteCrivello diventa inutile, in quanto la condizione per terminare direttamente il ciclo più esterno diventa i*i>dim-1 (i nomi delle variabili si riferiscono al codice da te postato).
deformazione mentale:, quando sono in gioco numeri grandi non lo faccio mai:
in questo caso porta inefficienza perché la moltiplicazione i*i del ciclo potrebbe essere eseguita (decine di) migliaia/milioni di volte --> conviene usare la funzione di libreria radice quadrata che esegue al più qualche centinaio di istruzioni una sola volta (bisogna usare un funzione che abbia una precisione in cifre maggiore degli interi in gioco);
in generale i*i>n può portare errori runtime/overflow quando i si avvicina al massimo intero rappresentabile, per es. se usi un (u)int a 32 bit hai un limite massimo di (4)2 milardi e spiccioli: eleva al quadrato un numero "piccolo" come 100 000 e vai fuori range!
In questo caso particolare non succederebbe per la dimensione relativamente piccola del bitset che volevo usare, però in generale può succedere.

Ho un sacco di problemi di connessione, non so se potrò ricollegarmi più tardi 😌
 
la cosa mi ha infastidito parecchio e se ho tempo vorrei capire dove diavolo sta l'intoppo che mi ha portato a perdere primi iniziando a setacciare dal quadrati del primo in analisi
Magari se posti il codice in questione possiamo darci un'occhiata.

deformazione mentale:, quando sono in gioco numeri grandi non lo faccio mai:
in questo caso porta inefficienza perché la moltiplicazione i*i del ciclo potrebbe essere eseguita (decine di) migliaia/milioni di volte --> conviene usare la funzione di libreria radice quadrata che esegue al più qualche centinaio di istruzioni una sola volta (bisogna usare un funzione che abbia una precisione in cifre maggiore degli interi in gioco);
in generale i*i>n può portare errori runtime/overflow quando i si avvicina al massimo intero rappresentabile, per es. se usi un (u)int a 32 bit hai un limite massimo di (4)2 milardi e spiccioli: eleva al quadrato un numero "piccolo" come 100 000 e vai fuori range!
In questo caso particolare non succederebbe per la dimensione relativamente piccola del bitset che volevo usare, però in generale può succedere.
Considera i*i>dim-1 come condizione di uscita del ciclo più esterno:
- se non si verifica, allora vai ad eliminare i multipli di i a partire da i^2 , e quindi il calcolo di i*i ti serve comunque;
- se invece si verifica allora abbiamo finito con la crivellatura.
Quindi il calcolo di i^2 risulta "inutile" una sola volta e l'alternativa sarebbe il calcolo di una radice quadrata. Questo dimostra che il metodo che ho descritto (ossia l'utilizzo della condizione i*i>dim-1 ) risulta più efficiente.

Riguardo al rischio di "overflow" nel calcolo di i*i hai ragione; se il crivello lavora su uint32_t basta inserire il suddetto prodotto in un uint64_t.
 
Ultima modifica:
Magari se posti il codice in questione possiamo darci un'occhiata.
non l'ho mantenuto ma sicuramente avrò pasticciato con gli indici i e j, infatti adesso
C++:
for(int j = i*i; j<dim; j += 2*i) // crivello
    p[j] = 0;
ritorna i risultati giusti, quindi devo aver corretto tutto ma avevo lasciato il ciclo più inefficiente (in termini di millisecondi misurati non è cambiato praticamente nulla)
durante le ultime prove ho pure scoperto di non potermi fidare più del compilatore GCC che uso, eppure è il 13.2.0 , non è l'ultima (sub)versione ma pensavo andasse bene, invece su input >16,2 milioni il .exe compilato esce con un errore incomprensibile e lo fa apparentemente a caso (me ne sono accorto variando l'intervallo del bitset)
Per le ultime verifiche sui 2 cicli mi sono affidato a un compilatore online: https://www.onlinegdb.com/
i risultati coincidono con quelli di Mahtematica, per cui il codice, piccole inefficienze a parte, è corretto.

Per il resto concordo, usando long invece di int sugli indici evitere anche l'uso di radice

Al momento sono soddisfatto, penso che cercherò di procurarmi qualche testo di teoria dei numeri, mi è sempre interessata ma non ho mai approfondito come si dovrebbe.
 
durante le ultime prove ho pure scoperto di non potermi fidare più del compilatore GCC che uso, eppure è il 13.2.0 , non è l'ultima (sub)versione ma pensavo andasse bene, invece su input >16,2 milioni il .exe compilato esce con un errore incomprensibile e lo fa apparentemente a caso (me ne sono accorto variando l'intervallo del bitset)
Hai provato a dichiarare il bitset static (o equivalentemente come variabile globale) come ti ho suggerito in precedenza?



Intanto ho aggiornato il programma apportando alcune correzioni e modifiche:

C++:
#include <iostream>
#include <bitset>
#include <cstdint>
#include <vector>

using namespace std;

void espandi_e_applica_crivello(vector<uint32_t> &v, uint32_t &last)
{
    static const uint32_t N = 51'130'563;
    static bitset<N + 1> b;
    uint32_t first = last + 2;
    uint64_t n, q;
    uint32_t dim = N + (v.size() == 1);
    last += 2 * dim;
    if(v.size() == 1)
    {
        for(uint32_t i = 0; ; ++i)
        {
            if(!b[i])
            {
                n = 2 * i + first;
                if((q = n * n) > last)
                {
                    break;
                }
                for(uint32_t j = (q - first) / 2; j < dim; b.set(j), j += n);
            }
        }
    }
    else
    {
        b.reset();
        for(uint32_t i = 1, j; (q = (n = v[i]) * v[i]) <= last ; ++i)
        {
            if(q < first)
            {
                uint32_t r = first % n;
                j = r ? (r % 2 ? (n - r) / 2 : n - r / 2) : 0;
            }
            else
            {
                j = (q - first) / 2;
            }
            for(; j < dim; b.set(j), j += n);
        }
    }
    for(uint32_t i = 0; i < dim; ++i)
    {
        if(!b[i])
        {
            v.push_back(2 * i + first);
        }
    }
}

uint32_t next_prime_number(const uint32_t n)
{
    if(n < 2)
    {
        return 2;
    }
    static vector<uint32_t> v = {2};
    static uint32_t last = 1;
    while(n >= v.back() && last != UINT32_MAX)
    {
        espandi_e_applica_crivello(v, last);
    }
    cout << "\n " << v.size() << " NUMERI PRIMI <= " << last << " (" << (double)v.size() / last * 100 << "%)\n";
    if(n >= v.back())
    {
        return 0;
    }
    uint32_t i_dx = v.size() - 1;
    for(uint32_t i_sx = 0, i; i_dx != i_sx + 1; (n < v[i = (i_sx + i_dx) / 2] ? i_dx : i_sx) = i);
    return v[i_dx];
}

int main()
{
    uint32_t n = 318'912'486;

    uint32_t p = next_prime_number(n);
    cout << "\n NEXT PRIME(" << n << ") = " << p << endl;
}

- giusto per ricapitolare, il programma implementa la funzione next_prime_number(n) sfruttando un crivello di Eratostene espandibile. Il risultato viene ricercato nell'ambito dei 32 bit, e se non esiste, la funzione ritorna 0 (come nel caso di next_prime_number(UINT32_MAX) );

- per la precisione i numeri primi trovati ad ogni espansione del crivello vengono salvati in un vettore v e la funzione espandi_e_applica_crivello() viene richiamata finché n risulta maggiore o uguale all'ultimo elemento del suddetto vettore (e ovviamente fino a quando l'ultimo valore vagliato last risulta inferiore a UINT32_MAX , che come sappiamo è pari a 2^32-1) ;

- la risposta viene ricercata all'interno di v mediante una ricerca binaria (o almeno credo che si chiami così, l'algoritmo l'ho scritto ragionando sul momento);

- se il crivello viene applicato almeno una volta, oltre alla risposta il programma stampa anche una statistica circa il numero di primi minori o uguali al massimo valore vagliato;

- infine penso sia doveroso spiegare perché ho fissato N = 51.130.563 e la dimensione del bitset a N+1 🙂! Al fine di coprire tutti gli interi a 32 bit è essenziale che l'ultimo valore vagliato dall'ultima chiamata a espandi_e_applica_crivello() sia proprio uguale a UINT32_MAX . I valori da vagliare sono i numeri dispari che potenzialmente vanno da 3 a 2^32-1, per un totale quindi di ((2^32-1)-1)/2=2.147.483.647 valori. Il problema è che il suddetto valore è un numero primo, e quindi non può essere scomposto in parti uguali. Quindi ho preso in considerazione il valore precedente 2147483646=2×3^2×7×11×31×151×331 e ho fissato N combinando i precedenti fattori al fine di ottenere un valore prossimo ai 50 milioni (che era il valore a cui avevo settato N nella precedente versione del programma), e per la precisione ho impostato N=3×11×31×151×331=51.130.563 (restano fuori i fattori 2×3×7=42). A questo punto considerando che

2.147.483.647=42×51.130.563+1=51.130.564+41×51.130.563

ho settato la dimensione del bitset a 51.130.564, andando poi nella prima espansione a considerare tutti i 51.130.564 elementi, mentre nelle potenziali 41 espansioni successive alla prima vado a considerare solo i primi 51.130.563 elementi del bitset.


Ovviamente se riscontrate qualche errore fatemi sapere.
 
Cavolo devo riuscire a leggervi con calma e non da smartphone magari...

Vedo che state progredendo parecchio. Complimenti a entrambi (anche per la voglia). 😁
 
Hai provato a dichiarare il bitset static (o equivalentemente come variabile globale) come ti ho suggerito in precedenza?
no, mi premeva aggiustare il codice scritto
però sì, in questo modo funziona, basta scegliere una dimensione corretta buona all'uso e poi usarlo sia per inizializzazione che per successive espansioni.
A me in generale premono di più gli algoritmi generali, meno l'implementazione in uno specifico linguaggio: in questo caso avevo pensato alle sequenze di bit perché permettono un grosso risparmio di RAM ed in particolare perché il C/C++ permettono l'accesso diretto in lettura/scrittura ai singoli bit, quindi una velocità altissima di elaborazione.
Per i prossimi problemi userò Java, in questo caso era teoricamente possibile ma sarebbe stato peggiorativo: Java ha i bitset ma ciascun elemento è rappresentato da un boolean, però per i boolean si usa 1 byte e già questo vanificava il tutto; come se non bastasse ho letto che l'implementazione effettiva usa internamente un long (64 bit) 😅 invece di rispariare RAM la sprecavo!
- se il crivello viene applicato almeno una volta, oltre alla risposta il programma stampa anche una statistica circa il numero di primi minori o uguali al massimo valore vagliato;
solo questo cambierei, rispettando le regole della programmazione a oggetti:
  • la funzione deve restituire solo il valore desiderato (a al limite n valore convenzionale, per es. -1,, se il risultato non rientra nel rangge): sarà compito di chi richiama/uso la funzione analizzare il risultato ed eventualmente stampare un messaggio
  • per la statistica una funzione a parte void che prende in ingersso un vector e, in base alla sua dimensione, stampa le informazioni; ovviamente il vector va definito fuori da next_prime_number, altrimenti non si può fare
queste 2 cose sono "finezze" che comunque non hanno nulla a che fare con l'algoritmo
se riscontrate qualche errore fatemi sapere
non ci sono errori, quando si implementa il programma si fanno delle scelte ponderate e se fai un certo tipo di ragionamento basta che sia coerente. Figurati chela mia idea era di fissare un bitset di inizializzazione molto più piccolo (2^16=65536).

Però conviene fare un ragionamento differente sull'uso di next_prime_number:
se hai deciso che N sia 50 milioni e spiccioli, è conveniente creare direttamente il vector che contiene tali primi, perché la funzione next_prime_number lo farà in ogni caso, quindi tanto vale precalcolrsela una volta per tutte e fare le espansioni avendolo già come base;
tale vector sarà una "costante" (o variabile statica o comunque si faccia in C++) e sarebbe un piccolo database di primi da calcolare una tantum su cui poggeranno le successive chiamate di next_prime_number (che ripeto, in sua assenza dovrebbe comunque calcolare ogni volta).
Vedo che state progredendo parecchio
si ragiona per gioco più che altro: stima maneggiando numeri piccoli, ma la matematica non lascia scampo, possiamo ottimizzare fino all'inverosimile ma funzionerà sono per piccoli intervallli, che però vanno bene a scopo didattico e sono un buon esercizio di programmazione tutto sommato, sempre meglio che istupidirsi davanti ad uno smartphone no? 😆
 
Però conviene fare un ragionamento differente sull'uso di next_prime_number:
se hai deciso che N sia 50 milioni e spiccioli, è conveniente creare direttamente il vector che contiene tali primi, perché la funzione next_prime_number lo farà in ogni caso, quindi tanto vale precalcolrsela una volta per tutte e fare le espansioni avendolo già come base;
tale vector sarà una "costante" (o variabile statica o comunque si faccia in C++) e sarebbe un piccolo database di primi da calcolare una tantum su cui poggeranno le successive chiamate di next_prime_number (che ripeto, in sua assenza dovrebbe comunque calcolare ogni volta).
v e last sono variabili statiche, quindi successive chiamate a next_prime_number(n) richiameranno la funzione espandi_e_applica_crivello()
solo se necessario, ossia se n risulta maggiore o uguale all'ultimo elemento di v ! 🙂


Comunque fissando nel main n=UINT32_MAX il programma setaccerà tutti gli interi a 32 bit (a tal proposito il mio catorcio lo fa in poco meno di un minuto, sarebbe interessante vedere i tempi su un PC più performante), il che consente poi anche di implementare una funzione is_prime_number() molto efficiente su tutti gli interi a 64 bit (se il numero è minore di 2^32 basta una ricerca binaria nel vettore dei primi, in caso contrario basta testare il numero per i primi minori o uguali alla sua radice quadrata intera).
A proposito di radice intera, ho intenzione di scrivere una funzione che implementi in binario l'algoritmo che si utilizza per trovare la radice quadrata con carta e penna (non so chi se lo ricorda, somiglia un po' ad una divisione in colonna).
 
v e last sono variabili statiche
cioè in C++ si possono dichiarare variabili statiche in una funzione e rimangono accessibili? allora sì il concetto è quello
se rimangono disponibili pure per altre funzioni ancora meglio
non conosco il C++, sarebbe da rendere il vector accessibie anche ad alrre funzioni, potrebbe essere utile, per esempio immagina di scrivere un programma che fa la decomposizione i fattori primi, se hai il vector fuori è un vantaggio
se è già così come non detto
il mio catorcio lo fa in poco meno di un minuto
che PC è?
se mi passi il .exe, lo zippi e lo metti su un server di condivisione file, e mi passi il link te lo provo, anche il mio Pc fa abbastanza schifo ma vediamo
per trovare la radice quadrata con carta e penna (non so chi se lo ricorda, somiglia un po' ad una divisione in colonna).
il metodo per farlo a mano non me lo ricordo, è macchinoso
con un ciclo che prova dimezzando ogni volta l'intervallo lo fai in poche istruzioni, basta che eviti il solito overlflow quando inevitabilmente moltiplichi i*i!, sempre che stiamo parlando di radici intere
se invece vuoi proprio replicare il metodo su carta e penna, divertiti 😆
 
cioè in C++ si possono dichiarare variabili statiche in una funzione e rimangono accessibili? allora sì il concetto è quello
se rimangono disponibili pure per altre funzioni ancora meglio
Esatto, una variabile statica viene inizializzata una sola volta (di default a zero in relazione al tipo) e rimane disponibile per tutta la durata del programma. Per quanto riguarda la visibilità invece, essa risulta utilizzabile solo nell'ambito (nella funzione in questo caso) in cui viene definita.

che PC è?
se mi passi il .exe, lo zippi e lo metti su un server di condivisione file, e mi passi il link te lo provo, anche il mio Pc fa abbastanza schifo ma vediamo
Quello per cui mi prendevate in giro in un altro topic! 😁
Comunque non si fa prima a settare nel main n=UINT32_MAX per poi compilare il codice sul proprio pc?!

il metodo per farlo a mano non me lo ricordo, è macchinoso
con un ciclo che prova dimezzando ogni volta l'intervallo credo che lo fai in 3 istruzioni, basta che eviti il solito overlflow quando inevitabilmente moltiplichi i*i!, sempre che stiamo parlando di radici intere
se invece vuoi proprio replicare il metodo su carta e penna, divertiti 😆
Quando lo implemento proviamo a confrontarlo con il metodo di ricerca che hai proposto! 😂
 
Comunque non si fa prima a settare nel main n=UINT32_MAX per poi compilare il codice sul proprio pc?!
no, a parte il fatto che ho dubbi sul compilatore che ho, serve esattamente lo stesso .exe perché i risultati siano confrontabili
se sul tuo, che io non ricordo cosa sia, lo fai in meno di un minuto, vediamo che esce fuori con un altro
Quando lo implemento proviamo a confrontarlo con il metodo di ricerca che hai proposto!
occhio che quello che ho proposto vale solo per gli itneri, se tu vuoi calcolare la radice decimale non vale più
 
no, a parte il fatto che ho dubbi sul compilatore che ho, serve esattamente lo stesso .exe perché i risultati siano confrontabili
se sul tuo, che io non ricordo cosa sia, lo fai in meno di un minuto, vediamo che esce fuori con un altro
A parte il fatto che non so quanto sia portabile un exe creato in C++, poi mi accontenterei di un confronto alla buona (basta che le ottimizzazioni -O2 o -O3 siano attivate)! 😅

occhio che quello che ho proposto vale solo per gli itneri, se tu vuoi calcolare la radice devimale non vale più
Sì sì lo so, d'altronde quello che serve in relazione alla ricerca dei numeri primi è proprio la radice intera di interi.
 
Pubblicità
Pubblicità
Indietro
Top