DOMANDA Numero primo

  • Autore discussione Autore discussione MPG
  • Data d'inizio Data d'inizio
Pubblicità
Ti ringrazio Fabio93 purtroppo alcune funzioni tipo unsigned non le ho fatte ancora, comunque mi segno il tuo listato e ti ringrazio ancora.
Il bool flag = true iniziale sotto i due unsigned specifica che deve essere tutto vero delle due righe sopra ?
Quando lo riprendi piu' sotto :
while (d <= sqrt(n) && flag == true) {
poni la condizione che quanto detto sopra sia sempre vero?
Scusami ma cerco di capire meglio possibile.
 
Ultima modifica:
Procediamo con ordine: "unsigned int" non è una funzione, ma un tipo di dato che può rappresentare solo numeri interi positivi, mentre "int" può rappresentare sia i positivi che i negativi. Se la variabile da usare deve contenere solo valori positivi, specialmente molto grandi, può essere utile usare il tipo unsigned perché così puoi rappresentare più numeri a parità di bit. Infatti con n bit puoi rappresentare 2^n valori, se solo positivi vanno da 0 a 2^n - 1, se sono sia negativi che positivi, da -2^(n-1) a +2^(n-1) - 1. Per cui ad esempio con 32 bit il tipo unsigned int può rappresentare i valori da 0 a 2^32 - 1, mentre il tipo int i valori da -2^31 a +2^31 - 1. Ti faccio un esempio più facile: con 5 bit hai 2^5 = 32 valori rappresentabili, da 0 a 31 (=2^5 - 1) oppure da -16 a 15 (cioè da -2^4 a +2^4 - 1). Il numero di valori rappresentabili è lo stesso, ma cambia il range.
Il bool flag = true iniziale sotto i due unsigned specifica che deve essere tutto vero delle due righe sopra ?
No! Il flag serve ad uscire dal ciclo quando un numero è divisibile per il divisore corrente (quindi sai che non è primo perché i numeri oprimi sono divisibili solo per 1 e per se stessi). Se non ci fosse, il ciclo continuerebbe fino a d<=sqrt(n), anche quando ciò fosse inutile. È una semplice variabile, tutto qui.
 
Non riesco bene a capire questa parte scusami:
"else { // se e' un numero dispari > 1
while (d <= sqrt(n) && flag == true) {
if (n % d == 0) {
flag = false;
"
in pratica il flag = true messo li' e poi il flag =false.
Per quest'ultimo comprendo che se n%d==0 ovvimanete il numero non è primo, non capisco bene le posizioni del flag..
Esempio :
if (flag == true) {
cout << n << " e' un numero primo\n";
La condizione flag true cosa richiama?
Abbiamo solo accennato a questa funzione bool flag, mi paicerebbe capire meglio ..
 
Ultima modifica:
Se il numero inserito è dispari (e maggiore di 3, nel commento ho sbagliato a scrivere 1) allora il programma lo divide per 3 (valore iniziale di d): se la divisione dà resto 0, vuol dire che il numero non è primo e il flag viene impostato a false per uscire dal ciclo (che altrimenti continuerebbe inutilmente). Se il numero non è divisibile per 3, potrebbe essere primo, e allora il programma prova a dividerlo per 5, e così via fino a che il divisore raggiunge la radice quadrata del numero. Come vedi il flag viene impostato a false solo se si verifica la condizione
Codice:
n % d == 0
ovvero se n è divisibile per d.
Codice:
while (d <= sqrt(n) && flag == true)
significa che il ciclo viene iterato se il divisore d è <= alla radice di n e se il flag ha valore true. && è l'AND logico.
 
IN pratica questa parte finale"
if (flag == true) {
cout << n << " e' un numero primo\n";
} else {
cout << n << " non e' un numero primo\n";
}
}
return 0;
"
mi dice se la divisione non da' resto 0 prima, seguendo la parte " while (d <= sqrt(n) && flag == true) " si va avanti con d +=2, ho compreso bene?
 
A quel punto il ciclo è già finito. Quelle istruzioni sono fuori dal ciclo, come puoi vedere dalle parentesi graffe.
Esempio di ciclo while, il blocco è delimitato dalle graffe
Codice:
while(condizione) {
    <istruzioni>
} // fine ciclo
 
Stavo un attimo ripensando al flag, se qui mettevi:
"while (d <= sqrt(n) && flag )"
senza mettere il true non è lo stesso perchè richiami come true già messo in alto , mentre dopo lo metti = false per farlo uscire fuori dal ciclo?

Per esempio qui che ho trovato su internet (c'era proceed al post di dove trovi in tre punti scritto flag che io ho messo) dove c'è while mette "while (flag)"
INoltre dove tu hai scritto: " if (n % d == 0) {
flag = false;"
Se mettevo "
{
cout<<"Il numero "<<numero<<" non e' primo\n";
break;

}
Non è lo stesso senza mettere il flag e si blocca il ciclo?

Codice:
#include <iostream>
#include <iostream>
using namespace std;
int main ()
{

    bool flag = true;
    int itemCnt=0;
    double price, totalPrice=0;
    cout<<"Program To Calculate The Average and Total Price Of Items \n\n";

    while (flag)
    {
        cout<<"Enter price of item: ";
        cin>>price;
        totalPrice += price;
        itemCnt++;
        cout<<"Anymore item? Enter 1 for yes OR 0 for no: ";
        cin>>flag;
    }

    cout<<"\n\nYou have "<<itemCnt<< " Items";
    cout<<"\nThe Average Price Of The Items Is "<<totalPrice/itemCnt;
    cout<<"\nThe Total Price of the Items Is "<<totalPrice;
    cout<<"\nEnd of Program";
    return 0;
}
[CODE]
 
Ultima modifica:
Stai confrontando mele e arance però. La forma forse è simile, ma sono frutti diversi.
Il tuo scopo a quanto ho visto da qualche post precedente non è fare una media di valori.
 
Ovvio che questo listato che ho posto è diverso, sto cercando di capire bene l'impostazione del flag facendo un contronto nel listato sotto in cui è stato richiamato solo il flag nel while (flag) senza metterlo =true che era stato posto in alto.
In pratica se ho posto sopra bool flag= true
si puo' successivamente richiamare in un while tipo "while (flag) " (significando true) senza mettere "while (flag=true)"?
 
Si, C/C++ ed altri linguaggi valutano 0=false e diverso da 0 = true. Specificare nel caso di una variabile boolean può essere anche superfluo, ma se coinvolge numeri personalmente lo rendo esplicito. Comunque ai fini del risultato funzionano allo stesso modo.

Edit: giusto per precisare nel tuo caso il flag mi sembra anche evitabile.
 
Ultima modifica:
Per quanto riguarda il primo quesito, sì, è possibile scrivere while(condizione) anziché while(condizione == true), è una questione di scelte stilistiche.
Se mettevo "
{
cout<<"Il numero "<<numero<<" non e' primo\n";
break;

}
Non è lo stesso senza mettere il flag e si blocca il ciclo?

Anche qui si può fare come dici però l'uso di istruzioni come break o goto viola i principi della programmazione strutturata (non so se hai mai sentito nominare il celebre pamphlet "Goto statement considered harmful" di Dijkstra) quindi è preferibile evitarle, per una questione di chiarezza del codice e più facile manutenzione. L'unico caso in cui va usata break è nel costrutto switch.
giusto per precisare nel tuo caso il flag mi sembra anche evitabile.
Ho messo il flag per uscire dal ciclo quando si è certi che un numero non è primo. Un esempio: n = 995; il divisore va da 3 a 31 (radice quadrata intera di 995). Alla prima iterazione divido 995 per 3 e ottengo resto 2 (non è divisibile, potrebbe essere primo), proseguo e divido 995 per 5: a questo punto ottengo resto 0 e so per certo che 995 non è un primo, quindi esco dal ciclo (grazie al flag) altrimenti continuerei inutilmente a provare tutti i divisori fino a 31. Comunque se hai una soluzione migliore sono felice di avere la tua opinione perché sono uno studente e ogni occasione di apprendere cose nuove è ben accetta, inoltre questi argomenti mi interessano particolarmente.
 
il flag nella condizione del while non serve: il ciclo si interrompe quando il valore del divisore supera la radice quadrata intera del numero da controllare.
Prova a riscrivere il programma usando questo algoritmo:
  1. ogni intero n<5 (quindi giustamente anche i numeri negativi) è primo solo quando n=2 oppure n=3 (naturalmente devi mettere == per il confronto)
  2. altrimenti controlla se n è pari (cioè divisibile per 2) e stampa "NON primo" se lo è (oppure ritorna false)
  3. altrimenti: calcola la radice quadrata di n e arrotondala all'intero più vicino (l'arrotondamento ci deve essere per evitare il caso che ci sia una radice del tipo x.9999 che altrimenti verrebbe troncata a x invece che a x+1; in altri termini se il risultato della radice è qualcosa come 126.9999 l'arrotondamento deve essere 127 (se dimentichi di arrotondare sarebbe 126). L'intero arrotondato è il limite superiore dei divisori da controllare. Infine imposta come primo divisore il numero 3. ed esegui il ciclo al punto 4.
  4. ciclo: fin quando (cioè while) il divisore è <= al limite superiore dividi n per il divisore; se il resto è zero stampa "NON primo" (oppure ritorna false) altrimenti incrementa il divisore di 2; nota che dal ciclo si può uscire se trovi un divisore mettendo in un blocco la stampa "NON primo" e l'istruzione break, invece se scrivi una funzione (cosa che ti consiglio di fare) il return false esce direttamente dalla funzione;
  5. uscito dal ciclo controlla (if) se il divisore è > del limite superiore e, se lo è, stampa "PRIMO!" (oppure ritorna true).
La parte critica, anche se può sembrare strano, è il calcolo della radice quadrata; in C++ si fa con sqrt che accetta in input float, double e long double quindi n deve essere passato a sqrt dopo la conversione in floating point. Suggerisco di usare il tipo int per n e la conversione di n in double perché gli int in C++ generalmente sono al massimo a 32 bit, per cui il massimo int>0 è 2^31-1, ed il calcolo della radice quadrata con argomento double non perde cifre utili (la funzione round arrotonda all'intero più vicino). Se preferisci usare gli unsigned devi prima controllare che sia sufficiente passare un double a sqrt, altrimenti serve long double (in ogni caso dipende dal compilatore, bisogna vedere quanti bit usa per la rappresentazione dei vari tipi in virgola mobile). Ai fini dell'esercizio ritengo che int sia più che sufficiente, inoltre in teoria se controlli un intero negativo il risultato è false.
 
Ultima modifica:
Per quanto riguarda il primo quesito, sì, è possibile scrivere while(condizione) anziché while(condizione == true), è una questione di scelte stilistiche.


Anche qui si può fare come dici però l'uso di istruzioni come break o goto viola i principi della programmazione strutturata (non so se hai mai sentito nominare il celebre pamphlet "Goto statement considered harmful" di Dijkstra) quindi è preferibile evitarle, per una questione di chiarezza del codice e più facile manutenzione. L'unico caso in cui va usata break è nel costrutto switch.

Ho messo il flag per uscire dal ciclo quando si è certi che un numero non è primo. Un esempio: n = 995; il divisore va da 3 a 31 (radice quadrata intera di 995). Alla prima iterazione divido 995 per 3 e ottengo resto 2 (non è divisibile, potrebbe essere primo), proseguo e divido 995 per 5: a questo punto ottengo resto 0 e so per certo che 995 non è un primo, quindi esco dal ciclo (grazie al flag) altrimenti continuerei inutilmente a provare tutti i divisori fino a 31. Comunque se hai una soluzione migliore sono felice di avere la tua opinione perché sono uno studente e ogni occasione di apprendere cose nuove è ben accetta, inoltre questi argomenti mi interessano particolarmente.
Questa sera penso di avere un po' di tempo libero, così mostro altre possibili soluzioni.
 
Ho scritto due funzioni di esempio:

Codice:
// primi.h

#ifndef  PRIMI
#define  PRIMI

class Primi
{
    public:
        bool eratostene(int n);
        bool isPrime(int n);

};

#endif

Codice:
#include <iostream>
#include <cmath>
#include "primi.h"


bool Primi::eratostene(int n)
{
    if(n == 1 || n == 0 || !(n & 1))
        return false;
    else if(n == 2)
        return true;
 
    int *sieve = new int[n+1];
    memset(sieve, 1, sizeof(sieve)*(n+1));
 
    int sqrt_n = sqrt(n) + 1;
 
    for(register int i = 3; i < sqrt_n; i+=2)
    {    
        if(sieve[i] & 1)
        {
            for(int j = i << 1; j <= n; j += i)
            {
                sieve[j] ^= sieve[j];
            }
        }
    }
 
    return sieve[n] & 1;
}

bool Primi::isPrime(int n)
{
    if(n == 1 || n == 0 || !(n & 1))
        return false;
    else if(n == 2)
        return true;
 
    int sqrt_n = sqrt(n);
    register int i = 3;
 
    for(; (n%i) && i <= sqrt_n; i+=2);
    
    return n == i || i > sqrt_n;
}

Nel primo caso, si tratta di una versione modificata del Crivello di Eratostene (esistono anche metodi migliori), che spiegato in due parole consiste nell'eliminare tutti i divisori di un dato numero. Per fare ciò ho settato l'array con N+1 valori a 1, e a partire dal numero 3 elimino (settando a 0) tutti i suoi multipli, così per 5 e così via. Quando accederò quindi all'elemento in posizione n saprò se è uguale a 1 (quindi primo) oppure uguale a 0 (quindi non primo).

Nel secondo caso l'algoritmo è simile al tuo @fabio93 (ed è il motivo per il quale dicevo che si può evitare il flag; ma esisteranno altri modi sicuramene per giungere a tal fine). L'interruzione del ciclo avviene se la divisione da resto 0 (quindi n è divisibile per i) oppure se i è maggiore della radice quadrata di n. Considerando il valore di i quindi sei in grado di dire se il numero è primo o no.

Ho usato qualche piccolo "trick" che ad uno alle prime armi può creare qualche perplessità, quindi preferisco chiarire. Ciò che fa !(n & 1) è l'and bit a bit con il numero 1, e poi ne inverte logicamente il risultato. E' true quando il numero è pari.
Quindi quando scrivo (sieve & 1), è true se il valore è dispari.
L'ultimo, sieve[j] ^= sieve[j], è/era usato a più basso livello con Assembly (ma tutt'ora utilizzato - a loro discrezione - dai compilatori) e serve ad azzerare il contenuto della variabile. :)
 
@DispatchCode ti ringrazio moltissimo! Trovo il tuo codice molto elegante, anche se mi ci è voluto un po' per capirlo. Conoscevo le operazioni bit a bit ma non avevo mai sentito l'esigenza di utilizzarle. Se ho ben capito:
!(n & 1) è uguale a (n % 2 == 0)
int j = i << 1;
equivale a int j = i * 2; perché fare uno shift a sinistra di 1 bit equivale a moltiplicare per 2
(sieve & 1) equivale a (sieve % 2 != 0) o anche (sieve % 2)
sieve[j] ^= sieve[j];
equivale a sieve[j] = 0; perché fa lo xor bit a bit tra due cose uguali
giusto?
C'è qualche vantaggio prestazionale nelle forme a sinistra rispetto a quelle a destra?
Non ho capito invece perché usi lo specificatore di classe di memoria register (cioè, immagino che sia per dire al compilatore di mettere la variabile in un registro anziché in ram), ma mi sfugge l'utilità. Inoltre mi piacerebbe sapere perché su alcuni libri dicono che è deprecato. Grazie ancora, trovo molto interessanti queste discussioni.
 
Ultima modifica:
Pubblicità
Pubblicità
Indietro
Top