Funzioni matematiche
Math.h e limits.h
Il linguaggio C++ mette a disposizione la libreria <math.h> per svolgere numerose funzioni matematiche.
#include <iostream>
#include <math.h>
using namespace std;
int main()
{
cout << pow(2,3) << endl; // 2 ^ 3
cout << sqrt(2) << endl; // radice quadrata
cout << cbrt(2) << endl; // radice cubica
cout << sin(3.14159) << endl;
cout << cos(3.14159) << endl;
cout << tan(3.14159) << endl;
cout << exp(2) << endl;
cout << log(10) << endl;
cout << ceil(2.3) << endl; // più piccolo intero maggiore (per eccesso)
cout << floor(2.3) << endl; // più grande intero minore (per difetto)
cout << round(2.3) << endl; // intero più vicino (per difetto o eccesso)
cout << abs(-2) << endl; // valore assoluto
return 0;
}
E’ possibile usare anche costanti matematiche, aggiungendo una direttiva define come la seguente:
#define _USE_MATH_DEFINES
#include <math.h>
#include <iostream>
using namespace std;
int main()
{
cout << M_E << endl; // numero di Eulero
cout << M_PI << endl; // pi greco
cout << M_PI_2 << endl; // pi greco / 2
cout << M_SQRT2) << endl; // radice di 2
return 0;
}
La libreria <limits.h> invece ci da il valore massimo per tipo di dato.
#include <limits.h>
#include <iostream>
using namespace std;
int main()
{
cout << INT_MAX << endl; // massimo intero
cout << INT_MIN << endl; // minimo intero
cout << LONG_MAX << endl; // massimo intero
cout << LONG_MIN << endl; // minimo intero
return 0;
}
Casting
Il casting consiste in una operazione che consente di trasformare un tipo di dato in un altro.
int x = 3;
float y = (float)x;
Il casting è solitamente implicito quindi non è necessario trasformare un tipo di dato in un altro. Una eccezione è con il tipo di dato char:
char c = "A";
cout << c << endl; // A
cout << (int)c << endl; // 65 è il codice di A
Operatori unari
In C++ sono presenti alcuni operatori che permettono di semplificare alcune operazioni semplici di calcolo. Qui l’operatore di postincremento:
int c = 2;
c++; // c viene incrementato di 1
c--; // c viene decrementato di 1
Esiste anche un operatore di preincremento:
int c = 2;
++c; // c viene incrementato di 1
--c; // c viene decrementato di 1
Che differenza c’è? Lo vediamo qui:
int a, b;
int c = 2;
a = ++c; // prima c viene incrementato di 1 e poi viene assegnato => a=2, c=2
b = c++; // prima c viene assegnato e poi viene incrementato di 1 => b=1, c=2
Numeri casuali
E’ possibile generare numeri casuali con un computer?
La risposta breve è: no.
Un computer è per sua natura un sistema deterministico, ovvero è un sistema che dispone di uno stato (la sua memoria) e può ricevere degli input (tramite le periferiche). Se eseguiamo un programma per computer quello che il computer svolge è partire da uno stato iniziale (cioè una valorizzazione iniziale delle sue variabili) ricevere un eventuale input, ed eseguire una elaborazione logico matematica che permette di memorizzare delle variabili da un valore ad un altro, ed infine mostrare un eventuale output e memorizzare un nuovo stato (cioè il nuovo valore delle variabili). Si dice che è deterministico perché viene garantito che a partire dallo stesso stato iniziale e dallo stesso input, si raggiungerà sempre lo stesso stato finale e lo stesso output, qualsiasi sia il computer su cui viene eseguito il programma. Questa garanzia di funzionamento consente infatti la ripetibilità delle operazioni, ovvero che se un programma funziona in modo corretto sul computer con determinati dati, allora viene garantito che continuerà a farlo per sempre.
Ad esempio, prendiamo questo programma:
#include <iostream>
using namespace std;
int main()
{
int i = 3;
cin >> j;
int i = i+j;
cout << j;
return 0;
}
E’ un programma che come si può vedere si comporterà allo stesso identico modo purché l’utente inserisca lo stesso valore di j. Ad esempio con j=2 otterremo sempre i=5.
Una cosa che è possibile, sfruttando una proprietà dei numeri, è quella di creare una sequenza pseudocasuale di numeri, che a partire da un numero base (detto seme) calcola il numero successivo della sequenza.
#include <iostream>
using namespace std;
int main()
{
int a = 73;
int m = 89;
int next = 1;
for (int i=0; i<100; i++) {
next = (next * a) % m;
cout << next << " ";
}
return 0;
}
Tuttavia questa sequenza viene generata sempre nello stesso ordine perché il numero di partenza è sempre lo stesso e sempre le stesse sono le operazioni eseguite. Il sistema funziona in questo modo:
- si moltiplica un numero per un numero a, e poi calcolando il resto della divisione con un numero m (scelto opportunamente, cioè non un divisore di a), si ottiene un nuovo numero.
- se si rieffettua l’operazione si genera un nuovo numero, diverso dal precedente.
- se si ripete ciclicamente l’operazione si genera una sequenza di numeri lunga m che però appaiono scorrelati tra loro (non c’è un modo semplice per ricostruire a ed m)
- al ciclo m+1 si rigenera lo stesso numero e viene rigenerata di nuovo la stessa sequenza e così all’infinito.
Quindi se si parte da un determinato seme, si genererà sempre la stessa sequenza, che per questo non va considerata casuale.
Vediamo come svolgere tutto questo in C/C++.
Come generare numeri casuali in C++
La libreria <cstdlib> del linguaggio C++ include alcune funzioni per la generazione di numeri “random” (cioè “casuali”). La funzione rand() non è altro che una versione un po’ più evoluta della funzione sopra descritta.
Rand() funziona nel seguente modo:
#include <cstdlib>
#include <iostream>
using namespace std;
int main()
{
int i = rand();
cout << i << endl;
return 0;
}
Cerchiamo di capire cosa significa:
- Rand() emette un numero “pseudocasuale” tra 0 e una numero massimo (che dipende dal sistema ed è una costante che sii chiama RAND_MAX). Il suo funzionamento è analogo a quello visto nell’esempio sopra.
- Se vogliamo invece generare un valore tra 0 e N (es. tra 0 e 100) possiamo calcolarlo tramite questa semplice operazione:
int i = rand() % N;
Questo perchè il resto della divisione intera di un qualsiasi numero per B è un numero compreso tra 0 e N-1. Ad esempio 3362721 % 100 fa 21, oppure 27264142 % 100 fa 42 (provare con qualsiasi numero a caso e con qualsiasi N).
Tuttavia rimane il problema che ogni volta che si esegue il programma vengono generati sempre gli stessi numeri, perchè come detto sopra l’algoritmo che genera i numeri casuali parte sempre dallo stesso numero iniziale.
Vediamo un esempio in cui genero 10 numeri tra 1 e 100 per capirlo meglio:
#include <cstdlib>
#include <iostream>
using namespace std;
int main()
{
for (int j=1; j<10; j++) {
int i = rand() % 100 + 1;
cout << i << ", ";
}
return 0;
}
Se eseguo questo codice otterrò sempre la stessa sequenza, ad esempio esce sempre questa: 84, 87, 78, 16, 94, 36, 87, 93, 50
Per risolvere questo problema e quindi generare ogni volta una nuova sequenza casuale, dobbiamo trovare un meccanismo per generare ad ogni avvio un numero casuale che useremo per inizializzare rand() con un numero sempre diverso ogni volta. Per farlo usiamo l’unico elemento di stato di un computer che pur non essendo casuale, è imprevedibile e cambia continuamente, ovvero il tempo.
I computer hanno infatti un orologio interno che calcola ad ogni istante un valore chiaamato timestamp, che indica il tempo in millisecondi passato da una data fissa valida per tutti i computer, ovvero l’1/1/1970 00:00 (anno convenzionale della nascita di UNIX).
La funzione che inizializza rand() si chiama srand(), a cui passeremo il valore della funzione di sistema time(NULL) che emette il timestamp corrente. Siccome varia ad ogni avvio, varierà anche la sequenza generata.
Proviamo quindi a generare di nuovo di valori casuali:
#include <cstdlib>
#include <iostream>
using namespace std;
int main()
{
srand(time(NULL));
for (int j=1; j<10; j++) {
int i = rand() % 100 + 1;
cout << i << ", ";
}
return 0;
}
Questa volta otterremo una sequenza differente ad ogni esecuzione e quindi quella “casualità” che cercavamo.
Sia ben chiaro però che è un artificio, nemmeno questa è vera casualità. Nulla impedisce di resettare l’orologio di sistema e quindi avviare un programma ad un istante specifico e quindi generare anche in due esecuzioni differenti lo stesso timestamp e quindi la stessa sequenza. Ma resta una situazione molto particolare, e quindi possiamo con buona approssimazione dire che il sistema genera dei valori “a caso”.