De ce contează?
Imaginează-ți o riglă pe care sunt marcate doar milimetrii. Cu ea poți măsura 3 mm sau 4 mm, dar nu poți arăta exact „a treia parte dintr-un milimetru" — pui creionul cât de aproape poți și accepți o mică eroare. Calculatorul face exact asta cu numerele cu virgulă: nu le ține perfect, ci le aproximează cât de bine îi permit biții lui.
Ce este un tip real
Un tip real (sau în virgulă mobilă) stochează numere cu parte fracționară: 3.14, 0.5, -2.718. Spre deosebire de întregi, aici valoarea e aproape mereu o aproximare, nu o copie exactă.
Motivul: calculatorul lucrează în baza 2, iar multe fracții simple în baza 10 sunt periodice în baza 2. La fel cum 1/3 = 0,3333… nu se termină în baza 10, 0,1 nu se termină în baza 2. Tipul real păstrează doar primele câteva zeci de cifre binare și taie restul — de acolo vine eroarea.
| Tip | Octeți | Cifre semnificative | Folosire |
|---|---|---|---|
float | 4 | ~7 | rar la concursuri |
double | 8 | ~15–16 | alegerea standard |
long double | 12–16 | ~18–19 | când ai nevoie de precizie extra |
La concursuri folosește aproape mereu double. float are doar ~7 cifre sigure, ceea ce duce des la răspunsuri greșite la testele cu numere mari sau cu multe operații acumulate.
Câte cifre sunt de încredere
„Cifre semnificative" înseamnă câte cifre de la început poți crede înainte ca aproximarea să strice restul. Iată cifrele lui π:
3 | 1 | 4 | 1 | 5 | 9 | 2 | 6 | 5 | 3 | 5 | 8 | 9 | 7 | 9 |
Dincolo de zona sigură, cifrele afișate pot fi „gunoi" matematic — există în memorie, dar nu mai reprezintă valoarea reală.
Capcana comparației cu ==
Pentru că valorile sunt aproximări, două calcule care „ar trebui" să dea același rezultat pot diferi cu o eroare minusculă:
#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;
int main() {
double a = 0.1 + 0.2;
double b = 0.3;
cout << (a == b) << "\n"; // 0 (fals!)
cout << setprecision(17) << a << "\n"; // 0.30000000000000004
// comparatie corecta: "suficient de aproape"
double eps = 1e-9;
cout << (fabs(a - b) < eps) << "\n"; // 1 (adevarat)
return 0;
}Regula: nu compara niciodată două reale cu ==. Verifică dacă diferența lor absolută e sub un prag mic (epsilon).
Afișarea cu precizie controlată
Implicit, cout afișează doar câteva cifre. Pentru rezultate cu zecimale fixe folosești fixed și setprecision:
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
double x = 2.0 / 3.0;
cout << x << "\n"; // 0.666667 (implicit)
cout << fixed << setprecision(4) << x; // 0.6667 (4 zecimale)
return 0;
}Complexitate
Operațiile pe reale costă tot O(1), dar sunt în general mai lente decât cele pe întregi, pentru că procesorul face mai multă muncă pentru virgula mobilă. Concluzie practică: dacă o problemă se poate rezolva cu întregi, preferă întregii — sunt și mai rapizi, și exacți.
Greșeala 1 — compari cu ==: if (a == b) pe reale eșuează din cauza erorilor de reprezentare. Folosește fabs(a - b) < 1e-9.
Greșeala 2 — folosești float în loc de double: cele ~7 cifre ale lui float se epuizează repede. La concurs, double e alegerea sigură.
Greșeala 3 — împărțire întreagă fără cast: double r = 5 / 2; dă 2.0, nu 2.5, pentru că 5 / 2 se calculează între întregi. Scrie 5.0 / 2 sau (double)5 / 2.
Greșeala 4 — afișare fără setprecision: dacă problema cere un anumit număr de zecimale și nu setezi fixed << setprecision(k), rezultatul corect poate fi marcat greșit.
Recapitulare
- Tipurile reale stochează aproximări, nu valori exacte, pentru că multe fracții nu au reprezentare finită în baza 2.
- Folosește
double(≈ 15–16 cifre sigure), nufloat, și nu compara niciodată cu==— verificăfabs(a - b) < epsilon. - Pentru afișare controlată folosești
fixed << setprecision(k); dacă poți rezolva cu întregi, evită cu totul realele.