1. Introduzione
Nella vita reale è raro che ti paghino soldi con la precisione di cento trilionesimi di euro, o che misuri la lunghezza di una stanza fino ai centesimi di millimetro. Anche nella programmazione: i risultati con double o float spesso sono “troppo frazionari”.
Ecco un classico problema:
double result = 10.0 / 3.0;
Console.WriteLine(result); // 3.3333333333333335
E se vogliamo stampare questo numero, tipo solo con due cifre decimali (per non “spaventare” l’utente)? Ecco perché serve arrotondare.
2. I principali metodi di arrotondamento in C#
C# ti dà diversi modi per arrotondare un numero floating point. E qui viene il bello: non tutti gli arrotondamenti sono uguali! Vediamo le opzioni principali e capiamo le differenze.
1) Funzione Math.Round — arrotondamento classico
Il modo più usato è il metodo Math.Round. Arrotonda il numero al più vicino intero (o al numero di cifre decimali che vuoi).
Math.Round(numero[, cifre_decimali[, MidpointRounding]])
- numero — quello che vuoi arrotondare.
- cifre_decimali — quante cifre dopo la virgola vuoi lasciare (default 0, cioè intero).
- MidpointRounding — il metodo per arrotondare i “mezzi” (ne parliamo tra poco).
Esempi:
double x = 2.71828;
Console.WriteLine(Math.Round(x)); // 3
Console.WriteLine(Math.Round(x, 2)); // 2.72
Console.WriteLine(Math.Round(x, 3)); // 2.718
Visualizzazione:
| Valore in ingresso | Math.Round(x) | Math.Round(x, 2) |
|---|---|---|
| 2.3 | 2 | 2.3 |
| 2.5 | 2 | 2.5 |
| 2.7 | 3 | 2.7 |
| 2.71828 | 3 | 2.72 |
Tipo di risultato:
Tutte le funzioni di questa lezione restituiscono double, quindi se vuoi assegnare il risultato arrotondato potresti beccarti un errore:
double result = 10.0 / 5.0; // Risultato 2.0
int x = Math.Round(x); // Errore! Non puoi assegnare direttamente un double a una variabile int
Se vuoi mettere un double in una variabile int, devi fare il cast esplicito:
double result = 10.0 / 5.0; // Risultato 2.0
int x = (int) Math.Round(x); // Così funziona!
2) Arrotondare verso l’alto o verso il basso
C# supporta non solo l’arrotondamento “classico”, ma anche quello forzato verso l’alto o verso il basso.
Arrotondare verso il basso (Math.Floor)
Il metodo Math.Floor semplicemente “taglia” la parte decimale (verso meno infinito). Arrotonda sempre verso il basso!
Console.WriteLine(Math.Floor(2.99)); // 2
Console.WriteLine(Math.Floor(-2.99)); // -3
Arrotondare verso l’alto (Math.Ceiling)
Il metodo Math.Ceiling è l’opposto di Floor: arrotonda sempre verso l’alto (verso più infinito).
Console.WriteLine(Math.Ceiling(2.01)); // 3
Console.WriteLine(Math.Ceiling(-2.01)); // -2
Schema comparativo:
| Funzione | 2.3 | -2.3 |
|---|---|---|
| Math.Round | 2 | -2 |
| Math.Floor | 2 | -3 |
| Math.Ceiling | 3 | -2 |
3) Truncate: semplicemente taglia la parte decimale
Il metodo Math.Truncate è tipo un “chirurgo tranquillo”. Taglia la parte decimale senza preoccuparsi della correttezza matematica dell’arrotondamento. Se x è positivo — risultato come Floor, se negativo — come Ceiling.
Console.WriteLine(Math.Truncate(2.99)); // 2
Console.WriteLine(Math.Truncate(-2.99)); // -2
3. Come funziona Math.Round per i “mezzi”? (MidpointRounding)
Immagina: hai 2.5. Dove arrotondi? È proprio a metà tra 2 e 3. In matematica ci sono due approcci famosi:
- Arrotondare verso l’alto (“al più vicino superiore”)
- Arrotondare al più vicino pari (Banker's rounding)
C# di default usa il secondo — arrotondare al più vicino pari (“banker's rounding”, cioè “verso il pari”).
Console.WriteLine(Math.Round(2.5)); // 2
Console.WriteLine(Math.Round(3.5)); // 4
Perché? Perché tra due interi equidistanti vince quello pari. Così, se arrotondi tanti numeri, non hai errori sistematici sempre verso l’alto o il basso. È fondamentale, tipo quando si calcolano grandi somme di soldi in banca (da qui il nome).
Se vuoi arrotondare sempre verso l’alto, puoi specificare il modo che preferisci:
// Arrotonda sempre "lontano da zero"
Console.WriteLine(Math.Round(2.5, 0, MidpointRounding.AwayFromZero)); // 3
Console.WriteLine(Math.Round(-2.5, 0, MidpointRounding.AwayFromZero)); // -3
Tabella dei modi MidpointRounding:
| Valore in ingresso | Round (Default) | AwayFromZero | ToZero / ToEven |
|---|---|---|---|
| 2.5 | 2 | 3 | 2 |
| 3.5 | 4 | 4 | 4 |
| -2.5 | -2 | -3 | -2 |
4. Arrotondare a un certo numero di cifre: occhio alle “otto” e alle “tre”!
A volte vuoi arrotondare non all’intero, ma tipo a due cifre decimali (per stampare soldi o percentuali).
double price = 149.9999;
double roundedPrice = Math.Round(price, 2);
Console.WriteLine(roundedPrice); // 150
E se vuoi solo “tagliare” le cifre in più, senza arrotondare? Tipo da 123.4567 a 123.45.
Si può fare con un piccolo trucco:
double num = 123.4567;
double result = Math.Floor(num * 100) / 100;
Console.WriteLine(result); // 123.45
Spostiamo la virgola a mano, tagliamo la parte decimale, e la rimettiamo a posto.
5. Formatting dell’output vs arrotondamento
Il formatting dell’output ({x:F2}) — non è sempre un "vero" arrotondamento, ma solo una “maschera” di come appare il numero a schermo. In memoria resta comunque un double lungo e “peloso”. Se vuoi davvero arrotondare e salvare il valore, usa Math.Round.
Ecco un esempio dove la differenza conta:
double value = 2.555;
Console.WriteLine($"{value:F2}"); // 2.56
Console.WriteLine(Math.Round(value, 2)); // 2.56
double stored = Math.Round(value, 2);
Console.WriteLine(stored); // 2.56
// Ma se stampi solo con il formato, senza arrotondare...
Console.WriteLine($"{value:F2}"); // 2.56, ma in memoria — 2.555
6. Errori tipici e dettagli
- Non usare l’arrotondamento per calcoli finanziari precisi con double! Per i soldi in .NET c’è il tipo decimal — non “sbarella” con la virgola mobile (ne parliamo meglio in una prossima lezione).
- Le famose “otto appese”: per via della rappresentazione binaria non tutti i numeri si arrotondano “come sembra”. Tipo 0.1 + 0.2 potrebbe non essere proprio 0.3.
- Non confondere Math.Floor e Math.Round! Il primo sempre verso il basso, il secondo — come dice la matematica.
- Accumulo incontrollato di errori: se arrotondi spesso durante i calcoli (tipo dentro un ciclo), il risultato finale può “scappare” in modo strano. Di solito meglio arrotondare solo il risultato finale, non ogni step.
7. Tabella finale: cosa fa ogni metodo?
| Valore in ingresso | Math.Round(x) | Math.Floor(x) | Math.Ceiling(x) | Math.Truncate(x) |
|---|---|---|---|---|
| 3.2 | 3 | 3 | 4 | 3 |
| 3.5 | 4 | 3 | 4 | 3 |
| -3.2 | -3 | -4 | -3 | -3 |
| -3.5 | -4 | -4 | -3 | -3 |
8. Curiosità divertenti
- Una volta la NASA ha perso una sonda spaziale per arrotondamenti poco rigorosi delle coordinate e differenze tra unità metriche/imperiali. Morale: arrotonda con la testa!
- Le banche usano da tempo il “banker's rounding” per non perdere i centesimi — una volta si arrotondava sempre verso l’alto, e i clienti perdevano frazioni di centesimo a ogni trasferimento.
- In C# c’è anche il metodo Math.Sign, che restituisce il segno di un numero — a volte utile prima di arrotondare, per scegliere tu dove “tagliare”.
GO TO FULL VERSION