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.
 
Pubblicità
Pubblicità
Indietro
Top