Dzielenie przez zero w C# — double, nieskończoność i NaN

Statek ma 120 litrów paliwa. Komputer pokładowy liczy, jak daleko na tym zajedzie, i odpowiada: nieskończenie daleko. Program się nie wywrócił, nie było błędu ani ostrzeżenia. A to jeszcze nie najgorsze, co potrafi ta liczba — jest wartość, przy której alarm „mało paliwa” nie włączy się nawet przy trzech litrach w zbiorniku.
Ten wpis to wersja tekstowa odcinka Program .NET Developer #3 na YouTube. Kod jest ten sam co w filmie, a każdy wynik uruchomiłem na .NET 10. Jeśli w twoim programie coś się dzieli — średnia, procent, zasięg, cena za sztukę — to dotyczy także ciebie. Zakładam, że znasz zmienne i typy w C# i instrukcję if; resztę wyjaśniam po drodze.
Dzielenie przez zero w double: zasięg ∞ km
Komputer pokładowy statku Zorza dzieli paliwo przez spalanie. Czujnik spalania jeszcze nic nie zmierzył, więc na razie jest tam zero:
double paliwo = 120; // litry w zbiorniku
double spalanie = 0; // litry na kilometr
double zasieg = paliwo / spalanie;
Console.WriteLine($"Paliwo: {paliwo} l");
Console.WriteLine($"Zasięg: {zasieg} km");Słowo double przed nazwą zmiennej to jej typ: liczba, która może mieć część po przecinku, na przykład 0,05. Ukośnik / to dzielenie. Wynik:
Paliwo: 120 l
Zasięg: ∞ kmDzielenie przez zero, którego „nie wolno” robić, przeszło bez słowa. C# wpisał do zmiennej nieskończoność i program poszedł dalej. Tak działa typ double: ma kilka specjalnych wartości, które nie są zwykłymi liczbami, a jedna z nich to właśnie nieskończoność.
Zasada: dzielenie
doubleprzez zero nie jest błędem. Daje nieskończoność i program liczy dalej.
Te same liczby w int: DivideByZeroException
Zmieńmy jedno słowo — zamiast double piszemy int, czyli liczbę całkowitą:
int paliwo = 120;
int spalanie = 0;
int zasieg = paliwo / spalanie;
Console.WriteLine($"Paliwo: {paliwo} l");
Console.WriteLine($"Zasięg: {zasieg} km");Unhandled exception. System.DivideByZeroException: Attempted to divide by zero.
at Program.<Main>$(String[] args) in …\02-Int.cs:line 10To jest wyjątek — sygnał, że program trafił na coś, czego nie umie zrobić. DivideByZeroException znaczy „wyjątek dzielenia przez zero”. Nikt go nie obsłużył, więc program się zatrzymał. Nie wypisał nawet paliwa, ale powiedział dokładnie, w której linijce był problem. Jak taki wyjątek złapać, zamiast pozwolić programowi się wywrócić, opisuje wpis o obsłudze błędów: try, catch, finally.
Skąd ta różnica? Liczby double zapisuje się według normy IEEE 754, która przewiduje specjalne wartości: plus i minus nieskończoność oraz NaN. Typ int takich wartości nie ma — dla wyniku dzielenia przez zero nie ma czego wpisać, więc zostaje tylko zatrzymać program. Tak samo zachowuje się decimal (sprawdziłem: 120 / 0 w decimal też kończy się DivideByZeroException).
| Typ | 120 / 0 | Co się dzieje dalej |
|---|---|---|
int | DivideByZeroException | program staje i pokazuje linijkę |
decimal | DivideByZeroException | program staje i pokazuje linijkę |
double | ∞ | program liczy dalej, po cichu |
Zasada: program, który się wywraca, jest uczciwy. Program, który liczy dalej ze złą wartością, może cię okłamać.
Pułapka #1: 0 / 0 to NaN — alarm, który milczy
Prawdziwy komputer pokładowy sam liczy spalanie: dzieli litry spalone od startu przez przebyte kilometry. Wyobraź sobie, że właśnie się zrestartował. Liczniki są wyzerowane, a w zbiorniku zostały trzy litry. Niżej jest alarm: jeżeli (if) zasięg jest mniejszy niż 50 km, wracamy do bazy; w przeciwnym razie (else) lecimy dalej. Więcej o tej konstrukcji — we wpisie o instrukcjach warunkowych if i else.
double paliwo = 3; // litry w zbiorniku
double zuzyte = 0; // litry spalone od restartu komputera
double przebyte = 0; // kilometry od restartu komputera
double spalanie = zuzyte / przebyte;
double zasieg = paliwo / spalanie;
Console.WriteLine($"Spalanie: {spalanie} l/km");
Console.WriteLine($"Zasięg: {zasieg} km");
if (zasieg < 50)
{
Console.WriteLine("ALARM: mało paliwa! Wracaj do bazy.");
}
else
{
Console.WriteLine("Paliwa wystarczy. Lecimy dalej.");
}Spalanie: NaN l/km
Zasięg: NaN km
Paliwa wystarczy. Lecimy dalej.NaN to skrót od *Not a Number* — „to nie jest liczba”. Zero podzielone przez zero w double nie daje nieskończoności, tylko właśnie NaN. Zasięg też wyszedł NaN, bo każde działanie z NaN daje znowu NaN. A ostatnia linijka mówi: paliwa wystarczy. Przy trzech litrach. Alarm był w kodzie i się nie włączył.
Pułapka #2: NaN przegrywa każde porównanie — nawet z samym sobą
Żeby zobaczyć dlaczego, bierzemy NaN i zadajemy mu cztery pytania:
double zasieg = 0.0 / 0.0;
Console.WriteLine($"zasieg < 50: {zasieg < 50}");
Console.WriteLine($"zasieg >= 50: {zasieg >= 50}");
Console.WriteLine($"zasieg == double.NaN: {zasieg == double.NaN}");
Console.WriteLine($"double.IsNaN(zasieg): {double.IsNaN(zasieg)}");Zanim przewiniesz dalej, odpowiedz sam:
04-NaN.cs — cztery pytania do NaN
Zmienna zasieg ma wartość 0.0 / 0.0, czyli NaN. Co wypisze każda linijka?
False, ostatnie True. Porównanie z NaN zawsze przegrywa — nawet z samym NaN. Prawdę mówi tylko double.IsNaN.zasieg < 50: False
zasieg >= 50: False
zasieg == double.NaN: False
double.IsNaN(zasieg): TrueMniejszy niż 50? Fałsz. Większy albo równy 50? Też fałsz. Równy NaN? Fałsz. Porównania <, >, <=, >= i == z NaN zawsze dają false — nawet porównanie NaN z NaN (jedynie != daje true). Dlatego nasz if dostał odpowiedź „nie, zasięg nie jest mniejszy niż 50” i poszedł do else. Kompilator przy zasieg == double.NaN nie ostrzega, więc ten błąd łatwo przepuścić. Prawdę mówi tylko metoda double.IsNaN. Wszystkie operatory porównania znajdziesz we wpisie o operatorach w C#.
Zasada: NaN nie jest ani mniejszy, ani większy, ani równy niczemu. Nigdy nie sprawdzaj go znakiem
==— tylko metodądouble.IsNaN.
Poprawka: double.IsFinite przed porównaniem
Zanim porównamy zasięg z pięćdziesiątką, sprawdzamy, czy to w ogóle zwykła liczba:
double paliwo = 3; // litry w zbiorniku
double zuzyte = 0; // litry spalone od restartu komputera
double przebyte = 0; // kilometry od restartu komputera
double spalanie = zuzyte / przebyte;
double zasieg = paliwo / spalanie;
if (!double.IsFinite(zasieg))
{
Console.WriteLine("Zasięg: brak danych. Wracaj do bazy.");
}
else if (zasieg < 50)
{
Console.WriteLine("ALARM: mało paliwa! Wracaj do bazy.");
}
else
{
Console.WriteLine($"Zasięg: {zasieg} km. Lecimy dalej.");
}Zasięg: brak danych. Wracaj do bazy.IsFinite znaczy „czy skończona”: zwraca true dla zwykłej liczby, a false dla nieskończoności i dla NaN. Wykrzyknik przed nią to zaprzeczenie — „jeżeli zasięg nie jest zwykłą liczbą”. Wtedy mówimy wprost: brak danych, wracaj do bazy. Ta sama poprawka łapie też nieskończoność z pierwszego przykładu (sprawdziłem: przy spalaniu 0 l/km wersja finalna też wypisuje „brak danych”).
To nie jedyny sposób. Trzy podejścia z ich kosztami:
| Podejście | Plusy | Minusy |
|---|---|---|
sprawdzić dzielnik przed dzieleniem (if (przebyte == 0)) | błąd nie powstaje wcale | trzeba pamiętać przy każdym dzieleniu; nie łapie NaN, który przyszedł z innego miejsca |
double.IsFinite(wynik) przed porównaniem | jedno sprawdzenie łapie ∞ i NaN, skądkolwiek przyszły | wynik już jest zepsuty — sprawdzasz skutek, nie przyczynę |
liczyć w int albo decimal | dzielenie przez zero kończy się wyjątkiem, którego nie da się przeoczyć | tracisz część ułamkową (int) albo zakres i szybkość (decimal); wyjątek trzeba obsłużyć |
Zasada: gdy program nie wie, ile wynosi wynik, ma zachować się ostrożnie — „brak danych” to też odpowiedź, i to lepsza niż „lecimy dalej”.
Sprawdź sam: komputer pokładowy przed i po poprawce
Wybierz stan liczników i wersję kodu. Wszystkie dziesięć wyników uruchomiłem na .NET 10.
Komputer pokładowy Zorzy
1 · stan liczników (paliwo / spalone / przebyte)
2 · wersja kodu
Ściągawka na code review
| Działanie | Wynik | Porównanie z liczbą |
|---|---|---|
int / 0, decimal / 0 | DivideByZeroException | — |
double (≠ 0) / 0 | ∞ | ∞ < 50 → False |
double 0 / 0 | NaN | każde <, >=, == → False |
| cokolwiek z NaN | NaN | jw. |
double.IsFinite(x) | False dla ∞ i NaN | — |
Przeglądając kod, szukaj dzielenia zmiennych double, których mianownik może być zerem (liczniki, sumy, „przebyte od restartu”), porównań == double.NaN i warunków, których gałąź else jest „bezpieczna” tylko pod warunkiem, że liczba jest prawdziwa. Kiedy wynik i tak wyjdzie dziwny, przejdź program krok po kroku debugerem — NaN widać w podglądzie zmiennych od pierwszej linijki, w której powstał.
Zadanie: osłony 0 z 0
Nowy statek, generator osłon jeszcze niezamontowany. Program liczy procent osłon:
double oslony = 0;
double maxOslony = 0;
double procent = oslony / maxOslony * 100;
Console.WriteLine($"Osłony: {procent}%");
if (procent < 20)
{
Console.WriteLine("UWAGA: osłony słabe!");
}- Zanim uruchomisz program, zapisz, co wypisze.
- Popraw go tak, żeby uczciwie mówił „brak danych”.
- Celowo zepsuj: zmień
doublenainti przeczytaj komunikat.
Pytanie dodatkowe do komentarza pod filmem: co wypisze -120.0 / 0? Najpierw w głowie, dopiero potem w kodzie.
Podsumowanie
- **
intidecimalprzez zero → wyjątekDivideByZeroException.** Program staje i pokazuje linijkę. - **
doubleprzez zero →∞, a0 / 0→NaN.** Bez błędu, bez ostrzeżenia — program liczy dalej. - Każde działanie z NaN daje NaN, a każde porównanie z NaN (
<,>=,==) tofalse— dlategoifpo cichu przechodzi doelse. - **Sprawdzaj wynik metodą
double.IsFinite** (albo dzielnik przed dzieleniem), zanim podejmiesz na jego podstawie decyzję.
Dzielenie przez zero wraca też w zadaniu rekrutacyjnym „Kalkulator w C#” — tam celowo zamieniamy je na wyjątek.
🎥 Wersja wideo: ten sam przykład krok po kroku — Program .NET Developer #3 — Zasięg: ∞ km
🔴 Zorza leci dalej — na żywo: „Bitwa statków”, gra w C# od zera, w której czat dowodzi statkiem — transmisja 2.11.2026 o 19:00, potem nagranie pod tym samym linkiem
🎓 Skąd ten temat: kurs C# Podstawy Programowania — lekcje o typach wbudowanych, operatorach i metodach typów takich jak double. Kurs to poziom 1 Programu .NET Developer.
📘 Darmowa roadmapa .NET Developera: dev-hobby.pl/lista-vip
Zobacz
- Zmienne i typy danych w C# — czym różnią się
int,doubleidecimal - Operatory w C# — arytmetyczne i logiczne — dzielenie, porównania i
! - Konwersje typów w C# — co się dzieje, gdy zamieniasz
doublenaint - Obsługa błędów w C# — try, catch, finally — jak złapać
DivideByZeroException
🚀 Co dalej?
Zobacz to w praktyce na wideo i pobierz darmową roadmapę, żeby ułożyć naukę w spójną ścieżkę do pierwszej pracy.
- 🗺️ Pobierz darmową roadmapę Junior .NET Developer — 12 poziomów od podstaw C# do pierwszej pracy: dev-hobby.pl
- 🎬 Subskrybuj kanał YouTube — nowe filmy co tydzień.
Zamień wiedzę w umiejętności
Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.
Pobieram roadmapę →