CodeGym /Corsi /C# SELF /Arrotondamento dei tipi floating point

Arrotondamento dei tipi floating point

C# SELF
Livello 6 , Lezione 4
Disponibile

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
Divisione floating point — risultato con la "coda"

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]])
Sintassi di Math.Round
  • 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

IMPORTANTE!

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”.
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION