CodeGym /Kursy /C# SELF /Zaokrąglanie typów zmiennoprzecinkowych

Zaokrąglanie typów zmiennoprzecinkowych

C# SELF
Poziom 6, Lekcja 4
Dostępny

1. Wprowadzenie

W realnym życiu rzadko się zdarza, żeby wypłacać kasę z dokładnością do stu trylionowych euro, albo mierzyć długość pokoju do setnych milimetra. W programowaniu jest podobnie: wyniki pracy z double albo float często są “zbyt ułamkowe”.

Klasyczny problem:

double result = 10.0 / 3.0;
Console.WriteLine(result); // 3.3333333333333335
Dzielenie zmiennoprzecinkowe — wynik z "ogonkiem"

A jeśli chcemy wyświetlić tę liczbę, na przykład tylko z dwoma miejscami po przecinku (żeby nie “straszyć” użytkownika)? Do tego właśnie jest zaokrąglanie.

2. Podstawowe metody zaokrąglania w C#

C# daje kilka sposobów na zaokrąglenie liczby zmiennoprzecinkowej. I tu zaczyna się zabawa: zaokrąglanie może być różne! Zobaczmy główne opcje i ogarnijmy, czym się różnią.

1) Funkcja Math.Round — klasyczne zaokrąglanie

Najpopularniejszy sposób — metoda Math.Round. Zaokrągla liczbę do najbliższej całkowitej (albo do żądanej liczby miejsc po przecinku).


Math.Round(liczba[, ilosc_miejsc[, MidpointRounding]])
Składnia Math.Round
  • liczba — co zaokrąglamy.
  • ilosc_miejsc — ile zostawić miejsc po przecinku (domyślnie 0, czyli do całkowitej).
  • MidpointRounding — metoda zaokrąglania połówek (o tym zaraz pogadamy).

Przykłady:

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

Wizualizacja:

Wartość wejściowa 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

Typ wyniku:
Wszystkie funkcje w tym wykładzie zwracają typ double, więc jeśli chcesz przypisać wynik zaokrąglenia, możesz dostać błąd:

double result = 10.0 / 5.0;     // Wynik 2.0
int x = Math.Round(x);          // Błąd! Nie można tak po prostu przypisać double do int

Jeśli chcesz przypisać double do zmiennej typu int, musisz jawnie rzutować typ:

double result = 10.0 / 5.0;     // Wynik 2.0
int x = (int) Math.Round(x);    // Wszystko gra!

2) Zaokrąglanie w górę lub w dół

C# obsługuje nie tylko “klasyczne” zaokrąglanie, ale też wymuszone zaokrąglanie w górę albo w dół.

Zaokrąglanie w dół (Math.Floor)

Metoda Math.Floor po prostu “ucina” część ułamkową (w stronę minus nieskończoności). Czyli zawsze zaokrągla w dół!

Console.WriteLine(Math.Floor(2.99));   // 2
Console.WriteLine(Math.Floor(-2.99));  // -3

Zaokrąglanie w górę (Math.Ceiling)

Metoda Math.Ceiling — przeciwieństwo Floor: zawsze zaokrągla w górę (w stronę plus nieskończoności).

Console.WriteLine(Math.Ceiling(2.01));   // 3
Console.WriteLine(Math.Ceiling(-2.01));  // -2

Schemat porównawczy:

Funkcja 2.3 -2.3
Math.Round 2 -2
Math.Floor 2 -3
Math.Ceiling 3 -2

3) Truncate: po prostu uciąć część ułamkową

Metoda Math.Truncate — “spokojny chirurg”. Po prostu usuwa część ułamkową, nie przejmując się matematyczną poprawnością zaokrąglania. Jeśli x jest dodatnie — wynik jak w Floor, jeśli ujemne — jak w Ceiling.

Console.WriteLine(Math.Truncate(2.99));   // 2
Console.WriteLine(Math.Truncate(-2.99));  // -2

3. Jak działa Math.Round dla “połówek”? (MidpointRounding)

Wyobraź sobie: masz 2.5. Trzeba zaokrąglić — ale gdzie? W sumie “po równo” między 2 a 3. W matematyce są dwa popularne podejścia:

  • Zaokrąglanie w górę (“do najbliższej większej”)
  • Zaokrąglanie do najbliższej parzystej (Banker's rounding)

C# domyślnie używa drugiego podejścia — zaokrąglania do najbliższej parzystej (“banker's rounding”, czyli “zaokrąglanie w stronę parzystej”).

Console.WriteLine(Math.Round(2.5)); // 2
Console.WriteLine(Math.Round(3.5)); // 4

Dlaczego? Bo z dwóch równo oddalonych całkowitych wygrywa parzysta. To po to, żeby przy dużej liczbie zaokrągleń nie było systematycznego błędu w górę albo w dół. To mega ważne np. przy liczeniu dużych sum kasy w banku (stąd nazwa).

Jeśli chcesz zawsze zaokrąglać w górę, możesz jawnie wskazać tryb:

// Zaokrąglić zawsze "od zera"
Console.WriteLine(Math.Round(2.5, 0, MidpointRounding.AwayFromZero)); // 3
Console.WriteLine(Math.Round(-2.5, 0, MidpointRounding.AwayFromZero)); // -3

Tabela trybów MidpointRounding:

Wartość wejściowa Round (Domyślnie) AwayFromZero ToZero / ToEven
2.5 2 3 2
3.5 4 4 4
-2.5 -2 -3 -2

4. Zaokrąglanie do żądanej liczby miejsc: nie zapominaj o "ósemkach" i "trójkach"!

Czasem trzeba zaokrąglić nie do całkowitej, a np. do dwóch miejsc po przecinku (np. do wyświetlania kasy albo procentów).

double price = 149.9999;
double roundedPrice = Math.Round(price, 2);
Console.WriteLine(roundedPrice); // 150

A jeśli chcemy po prostu “uciąć” nadmiarowe miejsca, ale nie zaokrąglać? Na przykład z 123.4567 zrobić 123.45.

Można to zrobić małym trikiem:

double num = 123.4567;
double result = Math.Floor(num * 100) / 100;
Console.WriteLine(result); // 123.45

Ręcznie “przesuwamy” przecinek, ucinamy część ułamkową i cofamy przecinek.

5. Formatowanie wyniku wyjścia vs zaokrąglanie

WAŻNE!

Formatowanie wyjścia ({x:F2}) — to nie zawsze "prawdziwe" zaokrąglanie, a tylko “maska”, jak liczba wygląda na ekranie. W pamięci zostaje długi “ogon” double. Jeśli chcesz naprawdę zaokrąglić wartość i ją zapisać, użyj Math.Round.

Przykład, gdzie różnica może być kluczowa:

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

// Ale jeśli tylko wyświetlisz bez zaokrąglania, tylko z formatem...
Console.WriteLine($"{value:F2}"); // 2.56, ale w pamięci — 2.555

6. Typowe błędy i niuanse

  • Nie używaj zaokrąglania do dokładnych obliczeń finansowych z double! Do kasy w .NET jest specjalny typ decimal — on “nie szaleje” z przecinkiem (więcej — w jednym z kolejnych wykładów).
  • Smutne “wiszące ósemki”: przez binarne przechowywanie nie wszystkie liczby zaokrąglają się “logicznie” na oko. Na przykład 0.1 + 0.2 może wcale nie być 0.3.
  • Nie myl Math.Floor i Math.Round! Pierwszy zawsze w dół, drugi — jak matematyka każe.
  • Nieograniczone narastanie błędu: jeśli często zaokrąglasz wynik obliczeń po drodze (np. w pętli), końcowa wartość może niespodziewanie “odpłynąć”. Zwykle lepiej zaokrąglać tylko końcowy wynik, a nie wszystkie pośrednie kroki.

7. Finałowa tabela: która metoda co daje?

Wartość wejściowa 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. Ciekawostki

  • Kiedyś NASA straciła sondę kosmiczną przez nieścisłe zaokrąglanie współrzędnych i różnicę między jednostkami metrycznymi/imperialnymi. Morał: zaokrąglaj z głową!
  • Banki już dawno przeszły na “banker's rounding”, żeby nie tracić groszy — kiedyś po “staremu” wszyscy zaokrąglali w górę i klienci tracili ułamki centa przy każdym przelewie.
  • W C# jest nawet metoda Math.Sign, która zwraca znak liczby — czasem przydatne przed zaokrąglaniem, żeby samemu wybrać, gdzie “ucinać”.
2
Zadanie
C# SELF, poziom 6, lekcja 4
Niedostępne
Zaokrąglanie do określonej liczby miejsc po przecinku
Zaokrąglanie do określonej liczby miejsc po przecinku
2
Zadanie
C# SELF, poziom 6, lekcja 4
Niedostępne
Zastosowanie różnych metod zaokrąglania
Zastosowanie różnych metod zaokrąglania
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION