Formatowanie tekstu i liczb

Program może liczyć bezbłędnie, a i tak wyglądać źle: 12.5000000001 zamiast 12,50 zł, kolumny rozjeżdżające się w każdym wierszu, data w postaci 04.09.2026 00:00:00. Ta lekcja jest o tym, jak pokazać wynik tak, żeby dało się go czytać — i dlaczego formatowanie nigdy nie zmienia samej liczby.

C# interpolacja CultureInfo 45 min
CEL LEKCJI

Czego się dziś nauczysz

  • Wyświetlisz liczbę z dokładnie dwoma miejscami po przecinku
  • Ustawisz dane w równych kolumnach bez ręcznego dodawania spacji
  • Wyjaśnisz, dlaczego ta sama liczba raz ma przecinek, a raz kropkę
  • Wczytasz liczbę zapisaną i z przecinkiem, i z kropką
  • Wypiszesz datę w dowolnym układzie, jaki jest potrzebny
  • Odróżnisz zaokrąglenie wartości od zaokrąglenia wyświetlania
TEORIA

Trzy sposoby złożenia tekstu z danymi

W C# ten sam napis można zbudować na trzy sposoby. Warto znać wszystkie trzy, bo w cudzych programach spotkasz każdy z nich — ale sam będziesz używał głównie pierwszego.

Program.cs
string imie = "Ala";
int wiek = 17;

// 1. Interpolacja - znak $ przed cudzyslowem
Console.WriteLine($"{imie} ma {wiek} lat.");

// 2. Konkatenacja - sklejanie plusem
Console.WriteLine(imie + " ma " + wiek + " lat.");

// 3. Formatowanie zlozone - miejsca oznaczone numerami
Console.WriteLine(string.Format("{0} ma {1} lat.", imie, wiek));
wynik w konsoli
Ala ma 17 lat.
Ala ma 17 lat.
Ala ma 17 lat.

Trzy różne zapisy, jeden wynik. Różnią się czytelnością:

SposóbZaletaWada
Interpolacja
$"..."
Widać docelowy tekst i miejsca wstawienia w jednym miejscu. Najkrótszy i najmniej podatny na pomyłki zapis. Praktycznie żadna. To domyślny wybór w nowym kodzie.
Konkatenacja
+
Nie wymaga niczego nowego — po prostu dodawanie napisów. Przy dłuższym tekście trudno policzyć cudzysłowy i spacje. Łatwo zgubić spację przed „ma”.
string.Format Wzorzec tekstu można trzymać osobno — np. wczytać go z pliku z tłumaczeniami. Trzeba pilnować zgodności numerów {0}, {1} z kolejnością argumentów. Pomyłka to błąd dopiero przy uruchomieniu.

Skąd te klamry

W interpolacji wszystko między { a } to zwykły kod C#. Może tam być zmienna, ale też całe wyrażenie: $"Suma: {a + b}", $"Polowa: {x / 2}", a nawet wywołanie metody $"Wielkimi: {imie.ToUpper()}". Wynik jest zamieniany na tekst i wstawiany w to miejsce.

Jak wypisać samą klamrę

Skoro klamra ma specjalne znaczenie, to żeby pokazać ją w tekście, trzeba ją podwoić:

Program.cs
int x = 5;
Console.WriteLine($"Zbior {{ {x} }} ma jeden element.");
wynik w konsoli
Zbior { 5 } ma jeden element.
TEORIA

Specyfikatory formatu liczb

Po nazwie zmiennej w klamrze można postawić dwukropek i podać, jak dana wartość ma wyglądać. To najważniejszy zapis w tej lekcji:

składnia
$"{ wartosc : format }"
//         ↑        ↑
//         |        └── jak wyswietlic
//         └── co wyswietlic

Bez spacji

Powyżej spacje są tylko dla czytelności rysunku. W prawdziwym kodzie piszemy $"{cena:F2}" — bez spacji, bo spacja stałaby się częścią formatu.

Program.cs
double cena = 12.5;
double pi = 3.14159265;
int magazyn = 1234567;
double zdawalnosc = 0.235;
int numer = 7;

Console.WriteLine($"F2 : {cena:F2}");
Console.WriteLine($"F0 : {pi:F0}");
Console.WriteLine($"F4 : {pi:F4}");
Console.WriteLine($"N0 : {magazyn:N0}");
Console.WriteLine($"C  : {cena:C}");
Console.WriteLine($"P1 : {zdawalnosc:P1}");
Console.WriteLine($"D3 : {numer:D3}");
Console.WriteLine($"X  : {255:X}");
wynik w konsoli (na polskim systemie)
F2 : 12,50
F0 : 3
F4 : 3,1416
N0 : 1 234 567
C  : 12,50 zł
P1 : 23,5 %
D3 : 007
X  : FF
LiteraZnaczeniePrzykładUwagi
FFixed — stała liczba miejsc po przecinku {12.5:F2}12,50 Najczęściej używany. Cyfra po literze to liczba miejsc dziesiętnych. Brakujące miejsca uzupełnia zerami.
NNumber — jak F, ale z separatorem tysięcy {1234567:N0}1 234 567 Bez podanej cyfry daje dwa miejsca po przecinku: {1234.5:N}1 234,50.
CCurrency — waluta {12.5:C}12,50 zł Symbol waluty bierze się z ustawień systemu, nie z kodu. Na angielskim systemie ta sama linia da $12.50 — patrz sekcja o kulturze.
PPercent — procent {0.235:P1}23,5 % Mnoży przez 100. Podajesz ułamek (0,235), dostajesz procent. Podanie 23,5 dałoby 2 350,0 %.
DDecimal — liczba całkowita z zerami z przodu {7:D3}007 Działa tylko dla typów całkowitych. {7.5:D3} to błąd przy uruchomieniu. Do numerów, godzin, kodów.
XZapis szesnastkowy {255:X}FF Małe x daje małe litery (ff). {255:X4}00FF. Też tylko dla całkowitych.
EZapis wykładniczy (naukowy) {12345.678:E2}1,23E+004 Do bardzo dużych i bardzo małych liczb. W szkolnych zadaniach rzadko potrzebny.

Polskie litery w wyniku, choć w kodzie ich nie ma

W kodzie napisaliśmy {cena:C}, a na ekranie pojawiło się z polskim „ł” — bo symbol waluty, nazwy miesięcy i nazwy dni bierze się z ustawień systemu, a nie z twojego pliku. Podobnie separator tysięcy w 1 234 567 to nie zwykła spacja, tylko spacja nierozdzielająca (taka, przy której liczba nigdy nie złamie się na dwie linie). Wygląda identycznie, ale nie da się jej wpisać z klawiatury — i dlatego porównywanie sformatowanego napisu ze zwykłym tekstem prawie zawsze zawodzi.

Zasada praktyczna

Pieniądze i wyniki pomiarów: F2. Duże liczby całkowite: N0. Numery i godziny: D2. To pokrywa jakieś 90% przypadków w programach, które będziesz pisał.

TEORIA

Formaty własne: 0 i #

Gdy gotowe litery nie wystarczą, wzór można narysować samemu za pomocą dwóch znaków. Różnica między nimi jest tylko jedna, ale istotna:

ZnakDziałanie
0 Miejsce obowiązkowe. Jeśli nie ma tu cyfry, zostanie wypisane zero.
# Miejsce opcjonalne. Jeśli nie ma tu cyfry, nie zostanie wypisane nic.
Program.cs
Console.WriteLine($"{5:00}");          // dwa miejsca obowiazkowe
Console.WriteLine($"{3.5:0.00}");      // dwa miejsca po przecinku
Console.WriteLine($"{3.5:0.##}");      // do dwoch, ale bez zbednych zer
Console.WriteLine($"{3.0:0.##}");      // koncowe zera znikaja
Console.WriteLine($"{1234.5:#,##0.00}");// separator tysiecy wlasnorecznie
Console.WriteLine($"{0.5:#.##}");       // UWAGA - patrz nizej
Console.WriteLine($"{0:#}");            // UWAGA - patrz nizej
wynik w konsoli
05
3,50
3,5
3
1 234,50
,5

Dwie pułapki znaku #

{0.5:#.##} daje ,5 — bez zera z przodu, bo # uznał, że tej cyfry „nie ma”. Poprawnie jest {0.5:0.##}0,5.

{0:#} daje pusty napis. Zero to jedyna cyfra, a # jej nie wypisuje. Dlatego na pozycji jedności zawsze stawiaj zero: #,##0.00, nigdy #,###.##.

We wzorze piszemy kropkę, na ekranie pojawia się przecinek

W kodzie zawsze piszemy 0.00 z kropką — to zapis języka C#, taki sam jak w literałach liczbowych. Dopiero przy wypisywaniu kropka zostaje zamieniona na znak używany w danym kraju. Na polskim systemie zobaczysz przecinek. Nie próbuj pisać 0,00 we wzorze — przecinek znaczy tam „separator tysięcy”.

TEORIA

Wyrównanie, czyli równe kolumny

Po nazwie zmiennej można podać przecinek i szerokość pola. Wartość zostanie uzupełniona spacjami do tej szerokości:

ZapisZnaczenie
{x,10}szerokość 10 znaków, wartość dosunięta do prawej (spacje z przodu)
{x,-10}szerokość 10 znaków, wartość dosunięta do lewej (spacje z tyłu)
{x,10:F2}najpierw szerokość, potem dwukropek i format — w tej kolejności

Zasada jest prosta: tekst do lewej, liczby do prawej. Liczby dosunięte do prawej mają jedności pod jednościami, dziesiątki pod dziesiątkami i dzięki temu da się je porównać wzrokiem.

Program.cs
Console.WriteLine($"{"Uczen",-16}{"Ocena",7}{"Procent",10}");
Console.WriteLine(new string('-', 33));

Console.WriteLine($"{"Kowalski A.",-16}{4.5,7:F2}{0.82,10:P1}");
Console.WriteLine($"{"Nowak B.",-16}{3.0,7:F2}{0.61,10:P1}");
Console.WriteLine($"{"Zielinski C.",-16}{5.25,7:F2}{0.94,10:P1}");
wynik w konsoli
Uczen             Ocena   Procent
---------------------------------
Kowalski A.        4,50    82,0 %
Nowak B.           3,00    61,0 %
Zielinski C.       5,25    94,0 %

Cudzysłów w cudzysłowie

$"{"Uczen",-16}" wygląda dziwnie, ale jest poprawne: wewnętrzne "Uczen" to zwykły napis wewnątrz klamry, czyli w miejscu, gdzie piszemy kod. Kompilator się w tym nie gubi. Gdyby ci to przeszkadzało, nagłówek można złożyć zwykłym string.Format albo przypisać do zmiennych.

Szerokość mniejsza niż wartość

Podana szerokość to minimum, nie maksimum. Jeśli wartość jest dłuższa, kolumna po prostu się rozjedzie — nic nie zostanie ucięte:

Program.cs
Console.WriteLine($"[{"Antoni",-4}]");   // 6 znakow w polu szerokosci 4
wynik w konsoli
[Antoni]

Jeśli chcesz mieć pewność, że kolumna nie przekroczy szerokości, musisz sam skrócić tekst — poznamy do tego metodę Substring przy okazji operacji na napisach.

To samo bez interpolacji: PadLeft i PadRight

Program.cs
string nazwa = "Chleb";
double cena = 4.5;

Console.WriteLine(nazwa.PadRight(16) + cena.ToString("F2").PadLeft(8));
// to samo co:
Console.WriteLine($"{nazwa,-16}{cena,8:F2}");

PadRight(16) dokłada spacje z prawej (tekst zostaje po lewej), PadLeft(8) dokłada spacje z lewej. Nazwy bywają mylące: mówią, po której stronie dokładane są spacje, a nie gdzie ląduje tekst.

Można wypełnić czymś innym niż spacja

"5".PadLeft(3, '0') daje "005". To alternatywa dla formatu D3, przydatna wtedy, gdy pracujesz na napisie, a nie na liczbie.

TEORIA

Kultura, czyli skąd się bierze przecinek

W lekcji o konwersjach obiecałem wyjaśnienie, dlaczego double.Parse("3.14") raz działa, a raz wyrzuca błąd. Odpowiedź brzmi: kultura.

Kultura (ang. culture) to zestaw reguł danego kraju: jaki znak oddziela część ułamkową, jaki oddziela tysiące, jak wygląda symbol waluty, w jakiej kolejności zapisuje się datę, jak nazywają się miesiące. .NET pobiera ją z ustawień systemu i stosuje automatycznie przy każdym formatowaniu i przy każdym Parse.

KulturaUłamekTysiąceWaluta
pl-PL (polska)przecinek 3,14spacja 1 0001 000,00 zł
en-US (amerykańska)kropka 3.14przecinek 1,000$1,000.00
de-DE (niemiecka)przecinek 3,14kropka 1.0001.000,00 €
InvariantCulturekropka 3.14przecinek 1,000bez symbolu

Ten sam kod, dwa różne wyniki

Program napisany na polskim systemie wypisze 12,50 zł. Uruchomiony na komputerze kolegi z ustawieniami amerykańskimi wypisze $12.50 — mimo że w kodzie nic się nie zmieniło. To bardzo częsta niespodzianka przy oddawaniu projektu.

Kultura niezmienna

InvariantCulture to sztuczna kultura, która nigdy się nie zmienia — niezależnie od komputera i ustawień. Używa kropki jako separatora ułamka. To jedyny właściwy wybór, gdy liczbę zapisujesz dla maszyny: do pliku, do bazy, do wysłania przez sieć.

Program.cs
using System.Globalization;

double kwota = 1234.5;

// dla czlowieka - kultura systemu
Console.WriteLine($"Na ekran: {kwota:N2}");

// dla maszyny - kultura niezmienna
string doPliku = kwota.ToString("F2", CultureInfo.InvariantCulture);
Console.WriteLine($"Do pliku: {doPliku}");
wynik w konsoli (na polskim systemie)
Na ekran: 1 234,50
Do pliku: 1234.50

Potrzebne using

Klasa CultureInfo mieszka w przestrzeni nazw System.Globalization, której nie ma wśród przestrzeni dołączanych automatycznie. Bez linii using System.Globalization; na górze pliku dostaniesz błąd CS0103: The name 'CultureInfo' does not exist.

Wczytanie liczby z przecinkiem albo kropką

W poprzedniej lekcji obiecałem sposób na przyjęcie obu zapisów. Trik jest prosty: zamieniamy przecinek na kropkę i czytamy zawsze kulturą niezmienną. Wtedy program działa tak samo, cokolwiek wpisze użytkownik i jakiekolwiek ustawienia ma system:

Program.cs
using System.Globalization;

Console.Write("Podaj cene (przecinek lub kropka): ");
string wpis = (Console.ReadLine() ?? "").Replace(',', '.');

bool ok = double.TryParse(wpis,
                          NumberStyles.Float,
                          CultureInfo.InvariantCulture,
                          out double cena);

if (!ok)
{
    Console.WriteLine("To nie jest liczba.");
}
else
{
    Console.WriteLine($"Zapisano cene: {cena:F2}");
}
wynik w konsoli — obie wersje działają tak samo
Podaj cene (przecinek lub kropka): 3.75
Zapisano cene: 3,75

Podaj cene (przecinek lub kropka): 3,75
Zapisano cene: 3,75

Wersja TryParse z czterema argumentami wygląda groźnie, ale każdy z nich ma prostą rolę:

ArgumentZnaczenie
wpistekst do zamiany
NumberStyles.Floatna co pozwalamy w zapisie: spacje po bokach, znak minus, część ułamkowa. Nie pozwalamy na separator tysięcy (NumberStyles.Number by na niego pozwolił).
CultureInfo.InvariantCulturewedług jakich reguł czytać — tu: ułamek po kropce
out double cenagdzie zapisać wynik

Ustawienie kultury dla całego programu

Można też wymusić jedną kulturę na starcie: CultureInfo.CurrentCulture = new CultureInfo("pl-PL");. Wtedy program zachowuje się identycznie na każdym komputerze. Przydatne, gdy nauczyciel sprawdza projekt na maszynie z innymi ustawieniami niż twoja.

TEORIA

Formatowanie dat

Data ma własny zestaw wzorów. Litery są tu skrótami od angielskich nazw i trzeba pamiętać, że wielkość liter ma znaczenie:

WzórZnaczenieUwagi
yyyyrok czterocyfrowyyy daje dwie cyfry: 26
MMmiesiąc dużymi literamiMMMM daje nazwę: września
dddzień miesiącadddd daje nazwę dnia tygodnia: piątek
HHgodzina w zegarze 24-godzinnymmałe hh to zegar 12-godzinny
mmminuty małymi literaminajczęstsza pomyłka: MM zamiast mm
sssekundy 
Program.cs
DateTime data = new DateTime(2026, 9, 4, 14, 5, 0);

Console.WriteLine($"{data:yyyy-MM-dd}");
Console.WriteLine($"{data:dd.MM.yyyy}");
Console.WriteLine($"{data:HH:mm}");
Console.WriteLine($"{data:dd.MM.yyyy HH:mm:ss}");
Console.WriteLine($"{data:dddd, d MMMM yyyy}");
wynik w konsoli (na polskim systemie)
2026-09-04
04.09.2026
14:05
04.09.2026 14:05:00
piątek, 4 września 2026

MM to miesiąc, mm to minuty

Zapis "dd.MM.yyyy HH:MM" wygląda niewinnie i się skompiluje — a wypisze godzinę i… numer miesiąca zamiast minut. Jeśli w wyniku widzisz zaskakujące liczby przy czasie, sprawdź najpierw wielkość liter.

Bieżącą datę i godzinę daje właściwość DateTime.Now, a samą dzisiejszą datę (z godziną 00:00) — DateTime.Today:

Program.cs
Console.WriteLine($"Dzis jest {DateTime.Today:dd.MM.yyyy}");
Console.WriteLine($"Godzina {DateTime.Now:HH:mm}");
Console.WriteLine($"Biezacy rok: {DateTime.Now.Year}");

Data do nazwy pliku

Do nazw plików i zapisu w bazie używaj układu yyyy-MM-dd. Taki zapis sortuje się alfabetycznie tak samo jak chronologicznie, a przy okazji nie zawiera kropek ani ukośników, które w nazwach plików bywają kłopotliwe.

TEORIA

Znaki specjalne w napisach

Niektórych znaków nie da się po prostu wpisać w cudzysłowie — bo albo kończyłyby napis, albo w ogóle nie da się ich wystukać na klawiaturze. Do tego służy odwrotny ukośnik \ i tak zwane sekwencje ucieczki:

ZapisZnaczenieKiedy potrzebny
\nnowa liniaprzejście do kolejnej linii wewnątrz jednego napisu
\ttabulatorproste odstępy — ale do kolumn lepsze jest wyrównanie z tej lekcji
\"cudzysłówgdy cudzysłów ma być częścią tekstu
\\odwrotny ukośnikścieżki do plików w Windows
\u00e9znak o podanym numerzerzadko; znaki spoza klawiatury, tu é
Program.cs
Console.WriteLine("Pierwsza linia\nDruga linia");
Console.WriteLine("Powiedzial: \"Dzien dobry\".");
Console.WriteLine("Sciezka: C:\\Users\\Uczen\\Dokumenty");
wynik w konsoli
Pierwsza linia
Druga linia
Powiedzial: "Dzien dobry".
Sciezka: C:\Users\Uczen\Dokumenty

Napis dosłowny: @

Podwajanie ukośników w ścieżkach jest męczące. Znak @ przed cudzysłowem wyłącza sekwencje ucieczki — wszystko wewnątrz jest brane dosłownie:

Program.cs
string sciezka = @"C:\Users\Uczen\Dokumenty";

string wiersz = @"Pierwsza linia
Druga linia";                       // przelamanie linii tez jest doslowne

string zCena = @"Cena: 10 zl";
Console.WriteLine($@"{sciezka} — {zCena}");   // $ i @ mozna laczyc

Cudzysłów w napisie dosłownym

Skoro \ nic nie znaczy, to \" też nie zadziała. W napisie dosłownym cudzysłów podwajamy: @"Powiedzial ""tak"".".

Napis surowy: trzy cudzysłowy (C# 11)

Nowsza wersja języka daje jeszcze wygodniejszy zapis dla dłuższych tekstów. Napis surowy zaczyna się i kończy trzema cudzysłowami i nie wymaga uciekania niczego:

Program.cs
string komunikat = """
    Program: Kalkulator
    Autor:   "Anna K."
    Sciezka: C:\dane\wyniki.txt
    """;

Console.WriteLine(komunikat);
wynik w konsoli
Program: Kalkulator
Autor:   "Anna K."
Sciezka: C:\dane\wyniki.txt

Skąd zniknęły wcięcia

Wcięcie zamykających trzech cudzysłowów wyznacza „lewy margines” — takie wcięcie zostaje usunięte z każdej linii. Dzięki temu tekst w kodzie może być ładnie wcięty, a na ekranie pojawi się przy lewej krawędzi. Pierwsza i ostatnia linia (te z cudzysłowami) nie trafiają do wyniku.

METODY WBUDOWANE

Użyte w tej lekcji metody i właściwości

NazwaZwracaDziałanie
x.ToString("F2")string Zamienia liczbę na tekst według podanego formatu. Wersja z drugim argumentem — ToString("F2", CultureInfo.InvariantCulture) — pozwala narzucić kulturę.
string.Format(wzor, ...)string Buduje tekst ze wzoru z miejscami {0}, {1}. Obsługuje te same formaty i wyrównania co interpolacja: {0,10:F2}.
s.PadLeft(n)string Zwraca napis uzupełniony spacjami z lewej do długości n. Drugi argument podaje inny znak wypełnienia: PadLeft(3, '0').
s.PadRight(n)string To samo, ale spacje dokładane są z prawej.
s.Replace(a, b)string Zwraca nowy napis z każdym wystąpieniem a zamienionym na b. Oryginał zostaje bez zmian — napisy w C# są niezmienne.
new string('-', 33)string Tworzy napis złożony z 33 myślników. Wygodniejsze i mniej podatne na pomyłki niż wklejenie linii kresek.
double.TryParse(t, styl, kultura, out x)bool Wersja TryParse z jawną kulturą. Zwraca true, gdy udało się odczytać liczbę, a wynik wpisuje do x.
CultureInfo.InvariantCultureCultureInfo Właściwość — stałe, niezależne od systemu reguły zapisu liczb i dat (ułamek po kropce). Do zapisu danych „dla maszyny”.
DateTime.Now / DateTime.TodayDateTime Właściwości — bieżąca data z godziną / bieżąca data z godziną 00:00.
Math.Round(x, n)double Zaokrągla wartość do n miejsc. Uwaga na regułę połówek — patrz „Częste błędy”.
PRZYKŁAD Z OMÓWIENIEM

Zestawienie sprzedaży sklepiku szkolnego

Program wypisuje tabelę z trzema pozycjami: nazwą, ilością, ceną jednostkową, wartością i udziałem pozycji w całości obrotu. Wszystkie kolumny muszą być równe, a kwoty — z dwoma miejscami po przecinku.

Program.cs
// --- dane ---
string nazwa1 = "Zeszyt A5";
int ilosc1 = 4;
double cena1 = 3.20;

string nazwa2 = "Dlugopis";
int ilosc2 = 10;
double cena2 = 1.99;

string nazwa3 = "Blok techniczny";
int ilosc3 = 2;
double cena3 = 6.50;

// --- obliczenia ---
double wartosc1 = ilosc1 * cena1;
double wartosc2 = ilosc2 * cena2;
double wartosc3 = ilosc3 * cena3;
double suma = wartosc1 + wartosc2 + wartosc3;

// --- naglowek ---
Console.WriteLine($"{"Produkt",-18}{"Ilosc",6}{"Cena",9}{"Wartosc",10}{"Udzial",9}");
Console.WriteLine(new string('-', 52));

// --- wiersze ---
Console.WriteLine($"{nazwa1,-18}{ilosc1,6}{cena1,9:F2}{wartosc1,10:F2}{wartosc1 / suma,9:P1}");
Console.WriteLine($"{nazwa2,-18}{ilosc2,6}{cena2,9:F2}{wartosc2,10:F2}{wartosc2 / suma,9:P1}");
Console.WriteLine($"{nazwa3,-18}{ilosc3,6}{cena3,9:F2}{wartosc3,10:F2}{wartosc3 / suma,9:P1}");

// --- podsumowanie ---
Console.WriteLine(new string('-', 52));
Console.WriteLine($"{"RAZEM",-33}{suma,10:F2}");
Console.WriteLine();
Console.WriteLine($"Zestawienie z dnia {DateTime.Today:dd.MM.yyyy}");
wynik w konsoli
Produkt            Ilosc     Cena   Wartosc   Udzial
----------------------------------------------------
Zeszyt A5              4     3,20     12,80   28,0 %
Dlugopis              10     1,99     19,90   43,5 %
Blok techniczny        2     6,50     13,00   28,4 %
----------------------------------------------------
RAZEM                                 45,70

Zestawienie z dnia 04.09.2026

Omówienie linia po linii

FragmentCo robi i dlaczego tak
{"Produkt",-18} Nagłówek dosunięty do lewej w polu 18 znaków. Ta sama szerokość co w wierszach niżej — to jest cały sekret równych kolumn: nagłówek i dane muszą mieć identyczne szerokości.
{"Ilosc",6} Nagłówek liczbowej kolumny dosuwamy do prawej, tak jak liczby pod nim. Gdyby był po lewej, nie stałby nad danymi.
new string('-', 52) Linia oddzielająca. 52 to suma szerokości kolumn: 18 + 6 + 9 + 10 + 9. Gdybyś zmienił którąś szerokość, tu też trzeba poprawić.
{cena1,9:F2} Najpierw wyrównanie (9), potem dwukropek i format (F2). Odwrotna kolejność to błąd kompilacji.
{wartosc1 / suma,9:P1} W klamrze może być całe wyrażenie. Dzielimy wartość przez sumę i dostajemy ułamek (0,28), a format P1 sam mnoży go przez 100 i dokleja znak procenta.
{"RAZEM",-33}{suma,10:F2} 33 = 18 + 6 + 9, czyli szerokość trzech pierwszych kolumn razem. Dzięki temu suma trafia dokładnie pod kolumnę „Wartosc”, choć nie wypisujemy pustych pól.
DateTime.Today:dd.MM.yyyy Sama data, bez godziny 00:00:00, którą wypisałby domyślny format.

Udziały sumują się do 99,9%, a nie do 100%

28,0 + 43,5 + 28,4 = 99,9. To nie jest błąd programu — każdy udział został osobno zaokrąglony do jednego miejsca i drobne reszty się nie skompensowały. Tak działa każde zaokrąglanie i tak samo zachowują się arkusze kalkulacyjne. W poważnym raporcie dopisuje się przy tabeli uwagę o zaokrągleniach.

Trzy pozycje to trzy komplety zmiennych — i to boli

Widzisz, że dla każdego produktu trzeba było napisać po cztery zmienne i osobną linię. Przy dziesięciu produktach byłoby to nie do utrzymania. W bloku o tablicach i pętlach zapiszemy to samo w kilkunastu liniach, niezależnie od liczby pozycji.

CZĘSTE BŁĘDY

Na czym się najczęściej potykamy

BłądCo się dzieje i jak poprawić
$"{cena:F2}" bez znaku $ Zapis "{cena:F2}" to zwykły napis — na ekranie pojawi się dosłownie {cena:F2}. Kompilator nie zgłosi błędu, bo taki napis jest poprawny. Jeśli w wyniku widzisz klamry, brakuje $.
{cena:F2,10} Odwrotna kolejność. Wyrównanie idzie przed dwukropkiem: {cena,10:F2}.
{procent:P1} przy wartości 23,5 Format P mnoży przez 100, więc dostaniesz 2 350,0 %. Podawaj ułamek: 0.235. Jeśli masz już procenty, użyj {procent:F1} i dopisz znak sam.
{7.5:D3} Format D działa tylko na typach całkowitych — dla double program zatrzyma się przy uruchomieniu z komunikatem FormatException. Do liczb ułamkowych: F, N lub format własny.
{0:#}, {0.5:#.##} Znak # pomija nieistotne cyfry, więc wyjdzie pusty napis i ,5. Na pozycji jedności zawsze stawiaj 0.
"HH:MM" Duże MM to miesiąc. Minuty to małe mm. Poprawnie: "HH:mm".
cena = cena.ToString("F2"); Błąd CS0029ToString zwraca napis, a cena jest liczbą. Formatowanie tworzy tekst do wyświetlenia; sama zmienna zostaje bez zmian i dalej ma pełną dokładność.
zaokrąglanie przez formatowanie {2.345:F2} nie zmienia zmiennej — kolejne obliczenia nadal używają pełnej wartości. Jeśli wynik ma być naprawdę zaokrąglony (np. kwota do zapłaty), użyj Math.Round.
Math.Round(2.5) daje 2 To nie pomyłka biblioteki. Domyślnie .NET zaokrągla połówki „do parzystej”: 2,5 → 2, a 3,5 → 4. Ta reguła zmniejsza błąd przy tysiącach dodawań. Jeśli chcesz szkolnego „w górę”, napisz Math.Round(2.5, MidpointRounding.AwayFromZero) → 3.
brak using System.Globalization; Błąd CS0103: The name 'CultureInfo' does not exist in the current context. Ta przestrzeń nazw nie jest dołączana automatycznie.
ręczne spacje zamiast wyrównania $"{nazwa} {cena}" wygląda dobrze przy jednym wierszu i rozjeżdża się przy drugim, bo nazwy mają różne długości. Do kolumn zawsze używaj {x,szerokosc}.
ZADANIA

Zadania

ZAD 1Wizytówka★☆☆

Wypisz trzy wiersze: „Imie i nazwisko”, „Klasa”, „Szkola” wraz z wartościami. Etykiety z dwukropkiem mają zajmować równe 18 znaków, żeby wartości zaczynały się w jednej kolumnie.

pokaż rozwiązanie
string imie = "Anna Kowalska";
string klasa = "3 TI";
string szkola = "ZSMI Lebork";

Console.WriteLine($"{"Imie i nazwisko:",-18}{imie}");
Console.WriteLine($"{"Klasa:",-18}{klasa}");
Console.WriteLine($"{"Szkola:",-18}{szkola}");
wynik w konsoli
Imie i nazwisko:  Anna Kowalska
Klasa:            3 TI
Szkola:           ZSMI Lebork

Dwukropek jest częścią etykiety, a nie dopisywany osobno — inaczej trzeba by go liczyć do szerokości pola i łatwo o pomyłkę o jeden znak.

ZAD 2Cennik★☆☆

Wypisz cennik czterech pozycji: nazwa dosunięta do lewej w polu 20 znaków, cena dosunięta do prawej w polu 8 znaków, zawsze z dwoma miejscami po przecinku. Nad tabelą nagłówek, pod nim linia z myślników o odpowiedniej długości.

pokaż rozwiązanie
Console.WriteLine($"{"Pozycja",-20}{"Cena",8}");
Console.WriteLine(new string('-', 28));

Console.WriteLine($"{"Kawa",-20}{12.00,8:F2}");
Console.WriteLine($"{"Herbata",-20}{8.50,8:F2}");
Console.WriteLine($"{"Sok pomaranczowy",-20}{9.99,8:F2}");
Console.WriteLine($"{"Woda",-20}{5.00,8:F2}");
wynik w konsoli
Pozycja                 Cena
----------------------------
Kawa                   12,00
Herbata                 8,50
Sok pomaranczowy        9,99
Woda                    5,00

Długość linii to 20 + 8 = 28. Zwróć uwagę, że 12.00 i 5.00 w kodzie mają zbędne zera — dla kompilatora to po prostu 12 i 5, ale format F2 i tak dopisze końcówkę ,00 na ekranie.

ZAD 3Rabat★★☆

Cena towaru to 249,99 zł, rabat wynosi 15%. Wypisz cenę początkową, wysokość rabatu, procent rabatu i kwotę do zapłaty. Wszystkie kwoty z dwoma miejscami po przecinku, procent formatem P0.

pokaż rozwiązanie
const double StopaRabatu = 0.15;

double cena = 249.99;
double rabat = cena * StopaRabatu;
double doZaplaty = cena - rabat;

Console.WriteLine($"{"Cena katalogowa:",-20}{cena,10:F2} zl");
Console.WriteLine($"{"Rabat:",-20}{rabat,10:F2} zl   (stopa {StopaRabatu:P0})");
Console.WriteLine(new string('-', 33));
Console.WriteLine($"{"DO ZAPLATY:",-20}{doZaplaty,10:F2} zl");
wynik w konsoli
Cena katalogowa:        249,99 zl
Rabat:                   37,50 zl   (stopa 15 %)
---------------------------------
DO ZAPLATY:             212,49 zl

Rabat wynosi dokładnie 37,4985 zł — na ekranie widać 37,50, bo F2 zaokrągla wyświetlanie. Kwota do zapłaty liczona jest jednak z pełnej wartości: 249,99 − 37,4985 = 212,4915 → 212,49. Gdybyśmy odjęli zaokrąglone 37,50, wyszłoby 212,49 — tym razem tak samo, ale przy innych liczbach różnica jednego grosza jest bardzo realna. Zaokrąglaj dopiero na końcu, przy wyświetlaniu.

Stopa rabatu jest stałą, bo to wartość, która nie zmienia się w trakcie działania programu, a nazwa StopaRabatu mówi więcej niż samo 0.15 wklejone w obliczenie.

ZAD 4Przecinek albo kropka★★☆

Program pyta o cenę jednostkową i ilość sztuk. Cenę użytkownik może wpisać z przecinkiem albo z kropką — oba zapisy mają działać. Wypisz wartość zamówienia.

pokaż rozwiązanie
using System.Globalization;

Console.Write("Cena jednostkowa (np. 3,75): ");
string wpis = (Console.ReadLine() ?? "").Replace(',', '.');
bool cenaOk = double.TryParse(wpis, NumberStyles.Float,
                              CultureInfo.InvariantCulture, out double cena);

Console.Write("Ilosc sztuk: ");
bool iloscOk = int.TryParse(Console.ReadLine(), out int ilosc);

if (!cenaOk || cena <= 0)
{
    Console.WriteLine("Cena musi byc liczba dodatnia.");
}
else if (!iloscOk || ilosc <= 0)
{
    Console.WriteLine("Ilosc musi byc liczba calkowita dodatnia.");
}
else
{
    double wartosc = cena * ilosc;
    Console.WriteLine($"{ilosc} szt. x {cena:F2} zl = {wartosc:F2} zl");
}
wynik w konsoli
Cena jednostkowa (np. 3,75): 3.75
Ilosc sztuk: 2
2 szt. x 3,75 zl = 7,50 zl

Replace(',', '.') działa na napisie, zanim jeszcze powstanie liczba — dlatego kolejność jest ważna: najpierw zamiana znaku, potem TryParse. Zauważ, że wpisana kropka wraca na ekran jako przecinek: wczytujemy kulturą niezmienną, ale wyświetlamy kulturą systemu.

Ilość jest liczbą całkowitą, więc czytamy ją zwykłym int.TryParse — tam kropka ani przecinek nie mają nic do roboty.

ZAD 5Stoper★★☆

Wczytaj liczbę sekund i wypisz ją w postaci gg:mm:ss — zawsze z dwiema cyframi w każdym polu. Dla 3725 sekund program ma wypisać 01:02:05.

pokaż rozwiązanie
Console.Write("Podaj liczbe sekund: ");
bool ok = int.TryParse(Console.ReadLine(), out int razem);

if (!ok || razem < 0)
{
    Console.WriteLine("Podaj nieujemna liczbe calkowita.");
}
else
{
    int godziny = razem / 3600;
    int minuty = razem % 3600 / 60;
    int sekundy = razem % 60;

    Console.WriteLine($"{godziny:D2}:{minuty:D2}:{sekundy:D2}");
}
wynik w konsoli
Podaj liczbe sekund: 3725
01:02:05

Tu wracają dwa narzędzia z lekcji o operatorach: dzielenie całkowite (razem / 3600 obcina resztę, więc od razu daje pełne godziny) i modulo (razem % 60 daje sekundy, które nie złożyły się w pełną minutę). Zapis razem % 3600 / 60 czyta się „reszta z godzin, podzielona na minuty” — oba operatory mają ten sam priorytet, więc działają od lewej.

Format D2 dokłada zero z przodu. Alternatywa dla napisów: godziny.ToString().PadLeft(2, '0').

ZAD 6Tabela temperatur★★★

Wypisz tabelę przeliczeń dla temperatur −10, 0, 10, 20, 30 i 37 °C: stopnie Celsjusza z jednym miejscem po przecinku, Fahrenheita z jednym, kelwiny z dwoma. Wzory: F = C · 9/5 + 32, K = C + 273,15. Kolumny mają szerokości 8, 10 i 10 znaków.

pokaż rozwiązanie
Console.WriteLine($"{"C",8}{"F",10}{"K",10}");
Console.WriteLine(new string('=', 28));

double c1 = -10, c2 = 0, c3 = 10, c4 = 20, c5 = 30, c6 = 37;

Console.WriteLine($"{c1,8:F1}{c1 * 9 / 5 + 32,10:F1}{c1 + 273.15,10:F2}");
Console.WriteLine($"{c2,8:F1}{c2 * 9 / 5 + 32,10:F1}{c2 + 273.15,10:F2}");
Console.WriteLine($"{c3,8:F1}{c3 * 9 / 5 + 32,10:F1}{c3 + 273.15,10:F2}");
Console.WriteLine($"{c4,8:F1}{c4 * 9 / 5 + 32,10:F1}{c4 + 273.15,10:F2}");
Console.WriteLine($"{c5,8:F1}{c5 * 9 / 5 + 32,10:F1}{c5 + 273.15,10:F2}");
Console.WriteLine($"{c6,8:F1}{c6 * 9 / 5 + 32,10:F1}{c6 + 273.15,10:F2}");
wynik w konsoli
       C         F         K
============================
   -10,0      14,0    263,15
     0,0      32,0    273,15
    10,0      50,0    283,15
    20,0      68,0    293,15
    30,0      86,0    303,15
    37,0      98,6    310,15

Dlaczego zmienne są typu double, a nie int? Gdyby c3 było typu int, wyrażenie c3 * 9 / 5 policzyłoby się na liczbach całkowitych: 10 · 9 = 90, potem 90 / 5 = 18 — akurat dobrze. Ale dla 37: 333 / 5 = 66 zamiast 66,6, i wynik byłby zaniżony. Przy przeliczeniach zawsze pilnuj, żeby przynajmniej jedna wartość w dzieleniu była ułamkowa.

Kolejność c * 9 / 5 jest bezpieczniejsza niż c * (9 / 5) — to drugie na liczbach całkowitych dałoby c * 1. Wrócimy do tego przy tablicach, gdzie sześć niemal identycznych linii zamieni się w jedną pętlę.

PODSUMOWANIE

Co trzeba zapamiętać

  • Interpolacja $"..." to domyślny sposób składania tekstu z danymi.
  • Format podajemy po dwukropku: {cena:F2}. Wyrównanie po przecinku: {cena,10}. Razem — najpierw szerokość, potem format: {cena,10:F2}.
  • F2 do kwot, N0 do dużych liczb, D2 do numerów i godzin, P do procentów — pamiętając, że P mnoży przez 100.
  • We własnych wzorach 0 to miejsce obowiązkowe, # — opcjonalne. Na pozycji jedności zawsze 0.
  • Równe kolumny to identyczne szerokości w nagłówku i w wierszach: tekst do lewej (-n), liczby do prawej (n).
  • Przecinek czy kropka zależy od kultury systemu. Do zapisu dla maszyny używamy CultureInfo.InvariantCulture.
  • W formatach dat MM to miesiąc, a mm to minuty.
  • @"..." wyłącza sekwencje ucieczki, """...""" pozwala wpisać dłuższy tekst dosłownie.
  • Formatowanie zmienia tylko wygląd, nie wartość. Do faktycznego zaokrąglenia służy Math.Round, a zaokrąglać należy jak najpóźniej.

Koniec bloku 2

Umiesz już wczytać dane, przeliczyć je i pokazać w czytelnej postaci. Ale twoje programy wciąż biegną od góry do dołu i nie potrafią podjąć decyzji ani niczego powtórzyć. Od następnej lekcji to się zmienia — zaczynamy sterowanie przebiegiem programu.