[C++] Logica con cui si approssima

Steven11
Domanda molto di base, ma ho cominciato da poco a studiare la programmazione e vorrei capire una cosa.

Dall'inizio uno si accorge che il programma deve approssimare e quindi non ci si stupisce se arriva a dire che, dato $alpha$ dall'utente, allora
s*s+c*c-1==0
dove $s=sinalpha$ e $c=cosalpha$, è falsa.

Ora ho dovuto ricorrere per la prima volta alla funzione potenza pow( , ).
Ecco, io non capisco perché se calcolo i vari quadrati
pow(2,2)
...
pow(19,2) va tutto bene, poi da venti in poi mi sbaglia di 1, ad esempio pow(20,2) è 399.
Forse sono ingenuo, ma mi sembra strano che già con numero così piccoli mi inizia a sbagliare.
Capisco la necessaria approssimazione quando hai a che fare con un numero illimitato e devi "limitarlo", ma con interi?
Il codice è a fondo pagina ma non credo possa servire.

Un'ultima cosa: se volessi ottenere la potenza senza usare la funzione addetta, ma usando uno dei cicli? Ci pensavo prima così, pensavo al for ma non saprei che mettere come incremento. Mi fa pensare al caso noto del fattoriale dove poni
fatt=fatt*i e i lo fai incrementare di uno in uno, ponendo fatt=1 all'inizio. Ma con la potenza non si deve incrementare i, deve star fermo. Ora continuo a pensarci, nel frattempo se qualcuno ha qualche osservazione lo ringrazio.
Ciao!

#include<stdlib.h>
#include<stdio.h>
#include<iostream.h>
main( )
{
      float n, k, i;
      printf("\n\nDigita la base della potenza\n\n");
      scanf("%i", &n);
      
      printf("\n\nDigita l'esponente della potenza\n\n");
      scanf("%i", &k);
      float pow(n,k);
      printf("\n\n La potenza risulta %f\n\n", pow(n,k));
      
      system("PAUSE");
      return(0);
      }

Risposte
salvozungri
Sono anch'io alle prime armi con la programmazione in C++ :)

Supponendo che a sia la base, b l'esponente, con $b>=0$, potresti procedere in questo modo:
int a, b;
int c=1;
for(int i=0; i<b; i++)
   c=c*a;


Questa è un' idea ma deve essere perfezionata con le varie condizioni.
per quanto riguarda la funzione pow, non so aiutarti, ho copiato e incollato il codice, ma mi dà due errori :?

apatriarca
Quando si lavora con “numeri reali” in informatica (qualunque sia il metodo usato per rappresentarli - virgola mobile, virgola fissa...) si deve porre molta attenzione a non dimenticarsi che si sta lavorando con approssimazioni e che quindi non valgono molte delle proprietà dei numeri reali. Per esempio non vale la proprietà associativa e non ha senso confrontare due numeri tra di loro. Il modo corretto per confrontare due variabili di questi tipi è quello di aggiungere un qualche epsilon al numero (che dipende in generale dai numeri che si confrontano e dal tipo di applicazione). Per esempio il seguente codice sul mio computer visualizza sullo schermo "Errore sempre inferiore a 1e-12":
#include <cmath>
#include <cstdio>
using namespace std;

const double pi = 3.14159265358979323846;

bool test(double alpha)
{
    double s = sin(alpha);
    double c = cos(alpha);
    
    return fabs((s*s) + (c*c) - 1.0) < 1e-12;
}

int main()
{
    for (int i = 0; i < 361; ++i) {
        double alpha = i * ((2.0 * pi) / 360.0);
        if (!test(alpha)) {
            fputs("Errore maggiore di 1e-12\n\n", stderr);
            return 1;
        }
    }

    puts("Errore sempre inferiore a 1e-12\n");
}

A patto quindi di accettare un certo errore nei numeri ottenuti si può affermare che il punto $(cos(alpha), sin(alpha))$ si trova sulla circonferenza unitaria. Nota che mentre in questo esempio ho deciso un epsilon singolo per ogni numero considerato, in casi più generali potrebbe essere necessario decidere un epsilon variabile a seconda dei valori in ingresso. Non ha insomma senso considerare lo stesso epsilon per confrontare ad esempio l'altezza di due uomini e di due montagne. Se quindi nello stesso codice possono capitare numeri molto alti e molto piccoli è possibile dover ricorrere a epsilon relativi alla dimensione dei numeri in ingresso.

In quasi 10 anni che programmo in C non ho mai usato la funzione pow in un mio programmo perché ho sempre avuto a che fare con esponenti piccoli o radici per cui esistono altri metodi più specifici ed efficienti per farlo. Per quelli piccoli scrivo semplicemente il prodotto, per la radice uso sqrt.
Sul mio computer pow(20.0f, 2) restituisce comunque 400. In particolare ho provato, usando i double, a calcolare il quadrato di tutti i numeri fino ad un milione e tutti i numeri sono stati calcolati senza errore. Nel caso dei cubi invece il massimo errore è stato di 64, ma in questo caso i numeri non erano rappresentabili con un double. Credo che il tuo problema possa essere o nella scelta di una qualche opzione del compilatore che diminuisce la precisione dei calcoli in virgola mobile a favore di velocità oppure in un bug della tua libreria standard (credo che sia più probabile l'altra possibilità). Nota comunque che pow non calcola la potenza di un intero ma sempre e solo di un numero a virgola mobile che lo approssima.

Per quanto riguarda il tuo codice è in un certo senso datato. Il modo corretto di includere iostream è infatti quello di scrivere:
#include <iostream>

e per le librerie del C è consigliato (è ancora possibile il metodo da te usato per compatibilità con il C) il modo seguente:
#include <cstdlib>
#include <cstdio>

Se includi le librerie in questo modo tutte le funzioni sono contenute nel namespace std e per essere usate come fai attualmente devi inserire la riga
using namespace std;

Perché includi sia stdio che iostream? Hanno le stesse funzionalità... Siccome utilizzi esclusivamente le funzioni di stdio allora non c'è alcun motivo di includere anche iostream. Manca l'inclusione di math.h (o cmath)

Dovresti inoltre scrivere il tipo di ritorno della funzione main perché in C++ è richiesto (mentre in C è opzionale). Al contrario return 0 alla fine in C++ è opzionale.

La linea
float pow(n,k);

non è corretta. In ogni caso non ne capisco lo scopo e quindi eliminala.

system("PAUSE");

non è necessario e probabilmente è anche una cattiva abitudine quella di metterlo ma verrebbe un lungo discorso e non ho tempo di farlo.

Il codice corretto
#include <cstdlib>
#include <cstdio>
#include <cmath>
using namespace std;

int main()
{
      float n, k;
      
      printf("Digita la base della potenza: ");
      scanf("%f", &n);
     
      printf("Digita l'esponente della potenza: ");
      scanf("%f", &k);

      printf("La potenza risulta %f\n\n", pow(n,k));
}

mi restituisce il risultato corretto per i numeri da te segnalati. Quindi è un problema del tuo computer e/o compilatore.

Per quanto riguarda il discorso di calcolare la potenza con un ciclo dai un occhiata per esempio a questa pagina di wikipedia per un metodo un po' più avanzato di quello proposto da Mathematico: http://en.wikipedia.org/wiki/Exponentiation_by_squaring

Steven11
Ciao a entrambi.

Grazie per l'idea Mathematico, non mi era venuta in mente.
Semplice e fattibile :wink:

@apatriarca: grazie per l'esauriente risposta. Le funzioni comunque sono una cosa che tratterò tra un po'.
Non escludo che il mio compilatore abbia qualche bug, infatti mi sa dando anche altre noie.
Perché includi sia stdio che iostream? Hanno le stesse funzionalità...

Non lo sapevo. Brutalmente avevo preferito abbondare... :D
La linea
float pow(n,k);
non è corretta. In ogni caso non ne capisco lo scopo e quindi eliminala.

Ok, ci sono.
Quanto al sysyem("PAUSE"), non mettendolo non riesco a visualizzare il risultato.
Se non sbaglio la professoressa ci disse che serve per far rimanere visibile lo stampato. Comunque non è una questione prioritaria, quindi se non hai tempo rimandiamo ad un altro momento.

Piuttosto, il problema della potenza l'ho introdotto per risolvere un problema più generale: inseriti $n,k$ calcolare
$1^k+2^k+...+n^k$
Io ho scritto il codice, mi compila ma al momento di girarmi si blocca: dice il classico messaggio "si è verificato un errore, l'applicazione verrà chiusa". E non è la prima volta.
Il messaggio che mi appare è questo: http://img9.imageshack.us/img9/5425/immagineez.jpg
Già che mi compila è qualcosa: io comunque posto il codice, perché non sono affatto sicuro di aver fatto bene. Ho paura che i cicli for andavano messi uno dentro l'altro come sentii dire, ma ancora sono troppo maldestro...
Se ci sono osservazioni che qualcuno vuole fare, leggo volentieri.
In breve: fa la richiesta di $n$ e $k$, poi col primo for mi salva ad $h$ il valore di $i^k$.
Col secondo for somo mano mano i vari $h$ facendo scorrere $i$.
Grazie, e a presto!
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
main( )
{
      int n,k,c,i;
      printf("\n\nDigita n per calcolare 1^k+...+n^k\n\nn="); 
      scanf("%i",&n);

      printf("\n\nDigita k\n\nk=");
      scanf("%i",k);
      
      int h=1;
      for (c=0; c<k; c++) h=h*i;
      
      int sum=0;
      for (i=1; i<n+1; i++) sum=sum+h;
      
      printf("\nsum=%i\n\n",sum);
      system("PAUSE");
      return(0);

apatriarca
Dev-C++ è in beta da credo 6-7 anni e ci sono diversi problemi mai risolti (come il supporto al sistema operativo Windows Vista che ancora non esisteva...). Ti consiglierei quindi di passare ad altro. Nel mondo open-source alcune possibili soluzioni potrebbero essere Code::Blocks e Eclipse CDT (deriva dall'IDE per Java). Non ho però grandi esperienza con questi IDE e quindi non ti so dire molto. Io uso Visual Studio, l'IDE della Microsoft. Puoi scaricare la versione Express gratuitamente e se non sbaglio (se sei studente) anche le versioni standard e professional (ma non ricordo il sito).

system("PAUSE");

La funzione system serve per eseguire comandi usando la shell. Viene cioè bloccata l'esecuzione del programma ed eseguito il comando passato come argomento a system. Si tratta del modo più semplice per far partire l'esecuzione di un altro programma all'interno della tua applicazione. Il problema di questa linea risiede principalmente nel fatto che "PAUSE" non è un comando portabile. Su linux ad esempio non esiste. Inoltre stai richiamando un nuovo programma solo per bloccare l'esecuzione del programma, quando sarebbe sufficiente fare qualcosa come il seguente (ci sono probabilmente modi migliori ma è il primo che mi è venuto in mente):
puts("Premere invio per terminare... ");
scanf("%*");

Un altro aspetto negativo del suo uso è che il problema da te riscontrato (la chiusura della finestra prima di vedere il risultato) è dovuto più che altro al modo in cui il tuo IDE apre la finestra. Su Visual Studio ad esempio, facendo partire il programma senza usare il debug, viene inserita in automatico la chiamata a PAUSE, mentre in Eclipse l'output del tuo programma viene visualizzato in una finestra interna dell'IDE (almeno così era per Java se non ricordo male). Inoltre quando il programma viene eseguito all'interno di una console (come avviene per qualsiasi applicazione reale) allora questo problema di chiusura del programma non si verifica e si può quindi semplicemente ignorare.

Ma veniamo al tuo problema. Supponiamo di calcolare la potenza usando un ciclo. Hai allora bisogno di un ciclo esterno che calcola la somma e di uno interno per calcolare la potenza:
unsigned sum = 0u;
for (unsigned i = 1u; i <= n; ++i) {
    unsigned ik = 1u;
    for (unsigned j = 0u; j < k; ++j) ik *= i; // calcolo la potenza k-esima
        
    sum += ik;
}

Puoi anche fare la stessa cosa con la funzione pow. Siccome un double ha 52bit di mantissa (se non ricordo male) tutti i numeri interi a 32 bit sono rappresentabili. L'errore usando pow non dovrebbe quindi essere molto grande (ma non ho mai testato). L'uso di questa funzione ti permette di semplificare un po' il codice usando un solo ciclo:
unsigned sum = 0u;
for (unsigned i = 1u; i <= n; ++i) {
    sum += pow((double) i, k);
}

Steven11
Ciao apatriarca, scusa il ritardo della risposta.
Per devc++ ora vedo, e se non cambia atteggiamento penso che lo siluro.

Due domande specifiche sul codice: all'inizio ero rimasto perplesso per l'uso di "unsigend", che non mi risulta mi sia stato spiegato in classe (né l'ho letto nella dispensa. Poi l'ho tolto sostituendolo con "int" (dichiarando prima) e funziona uguale.

Soprattutto, ho notato che i numeri ormali li indicavi con una "u" vicina: 1u e 0u.
Non l'ho capita proprio, soprattutto perché ho provato a levarli lasciando i numeri da soli e mi gira tutto bene ugualmente.
Insomma, il programma l'ho modificato così e gira:
  sum = 0;
    for ( i = 1; i <= n; i++) 
    {
    ik = 1;
    for ( j = 0; j < k; j++) ik = ik*i; // calcolo la potenza k-esima
       
    sum = sum+ ik;
} 


Ok anche per le altre questioni (sysytemPAUSE etc.).

Grazie tante per la disponibilità!
A presto.

apatriarca
Ci sono molti tipi interi in C++ e posso essere divisi principalmente in due gruppi: con segno e senza segno. Se prendi un qualsiasi tipo signed come short, int, long o long long e aggiungi la parola chiave unsigned davanti ottieni un tipo senza segno della stessa dimensione). unsigned da solo equivale a unsigned int. La u dopo il numero serve solo ad indicare il tipo. La u significa unsigned, una L (puoi usare il masiuscolo o il minuscolo ma nel caso di long è meglio usare il maiuscolo che il minuscolo si confonde con un 1) long... Puoi anche non metterli. Il risultato è normalmente lo stesso e serve principalmente per forzare il tipo di una espressione.

Steven11
Ciao. :wink:

"apatriarca":
Ci sono molti tipi interi in C++ e posso essere divisi principalmente in due gruppi: con segno e senza segno. Se prendi un qualsiasi tipo signed come short, int, long o long long e aggiungi la parola chiave unsigned davanti ottieni un tipo senza segno della stessa dimensione). unsigned da solo equivale a unsigned int.

Quindi aspetta, significa che me lo mette lui positivo?
Oppure che il range di valori contemplati non è (-n,+n) ma (0,2n)?

"apatriarca":
La u dopo il numero serve solo ad indicare il tipo. La u significa unsigned, una L (puoi usare il masiuscolo o il minuscolo ma nel caso di long è meglio usare il maiuscolo che il minuscolo si confonde con un 1) long... Puoi anche non metterli.

Aspetta, quindi 1u significa 1 unsigned, quindi gli stai dicendo che 1 non deve avere segno? :?
"apatriarca":
Il risultato è normalmente lo stesso e serve principalmente per forzare il tipo di una espressione.

Ok, per imporre il tipo quindi. Ma è davvero necessario, cioè ci sono possibilità di fraintendimento o è solo un "vezzo"? :-)

Scusa se insisto, ma vorrei capire bene.
Grazie ancora, a presto!

apatriarca
È solo un vezzo quello di mettere la u, è probabilmente totalmente inutile. L'unico caso, che mi viene in mente, in cui ha senso è quando si ha a che fare con l'overload di funzioni. Per esempio la funzione pow è definita su float, double e long double. Se chiami la funzione nel seguente modo
pow(2.4, 3);
Vengono considerati double. Se per qualche motivo ritieni di non aver bisogno di usare la precisione dei double (nota comunque che a meno di impostare delle opzioni particolari o usare SSE e simili tutte le operazioni vengono comunque fatte in long double e poi il risultato viene arrotondato), puoi scrivere ad esempio
pow(2.4f, 3);
e viene chiamata la funzione pow che ha come argomenti dei float.

Un tipo unsigned può contenere solo valori positivi e quindi il range di valori possibili è $[0, 1, ... (2^b - 1)]$. Un tipo signed invece può contenere sia valori positivi che negativi e quindi il range di valori possibili è normalmente (rappresentazione a complemento a 2) $[(-2^{b-1}), (-2^{b-1} + 1), ... , (2^{b-1} - 1)]$ (nota che l'intervallo non è simmetrico...).

Rispondi
Per rispondere a questa discussione devi prima effettuare il login.