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.
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
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.
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));
Ala ma 17 lat.
Ala ma 17 lat.
Ala ma 17 lat.
Trzy różne zapisy, jeden wynik. Różnią się czytelnością:
| Sposób | Zaleta | Wada |
|---|---|---|
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ć:
int x = 5;
Console.WriteLine($"Zbior {{ {x} }} ma jeden element.");
Zbior { 5 } ma jeden element.
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:
$"{ 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.
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}");
F2 : 12,50
F0 : 3
F4 : 3,1416
N0 : 1 234 567
C : 12,50 zł
P1 : 23,5 %
D3 : 007
X : FF
| Litera | Znaczenie | Przykład | Uwagi |
|---|---|---|---|
F | Fixed — 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. |
N | Number — 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. |
C | Currency — 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. |
P | Percent — 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 %. |
D | Decimal — 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. |
X | Zapis szesnastkowy | {255:X} → FF |
Małe x daje małe litery (ff). {255:X4} → 00FF. Też tylko dla całkowitych. |
E | Zapis 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ł 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ł.
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:
| Znak | Dział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. |
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
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”.
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:
| Zapis | Znaczenie |
|---|---|
{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.
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}");
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:
Console.WriteLine($"[{"Antoni",-4}]"); // 6 znakow w polu szerokosci 4
[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
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.
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.
| Kultura | Ułamek | Tysiące | Waluta |
|---|---|---|---|
pl-PL (polska) | przecinek 3,14 | spacja 1 000 | 1 000,00 zł |
en-US (amerykańska) | kropka 3.14 | przecinek 1,000 | $1,000.00 |
de-DE (niemiecka) | przecinek 3,14 | kropka 1.000 | 1.000,00 € |
InvariantCulture | kropka 3.14 | przecinek 1,000 | bez 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ć.
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}");
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:
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}");
}
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ę:
| Argument | Znaczenie |
|---|---|
wpis | tekst do zamiany |
NumberStyles.Float | na 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.InvariantCulture | według jakich reguł czytać — tu: ułamek po kropce |
out double cena | gdzie 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.
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ór | Znaczenie | Uwagi |
|---|---|---|
yyyy | rok czterocyfrowy | yy daje dwie cyfry: 26 |
MM | miesiąc dużymi literami | MMMM daje nazwę: września |
dd | dzień miesiąca | dddd daje nazwę dnia tygodnia: piątek |
HH | godzina w zegarze 24-godzinnym | małe hh to zegar 12-godzinny |
mm | minuty małymi literami | najczęstsza pomyłka: MM zamiast mm |
ss | sekundy |
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}");
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:
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.
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:
| Zapis | Znaczenie | Kiedy potrzebny |
|---|---|---|
\n | nowa linia | przejście do kolejnej linii wewnątrz jednego napisu |
\t | tabulator | proste odstępy — ale do kolumn lepsze jest wyrównanie z tej lekcji |
\" | cudzysłów | gdy cudzysłów ma być częścią tekstu |
\\ | odwrotny ukośnik | ścieżki do plików w Windows |
\u00e9 | znak o podanym numerze | rzadko; znaki spoza klawiatury, tu é |
Console.WriteLine("Pierwsza linia\nDruga linia");
Console.WriteLine("Powiedzial: \"Dzien dobry\".");
Console.WriteLine("Sciezka: C:\\Users\\Uczen\\Dokumenty");
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:
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:
string komunikat = """
Program: Kalkulator
Autor: "Anna K."
Sciezka: C:\dane\wyniki.txt
""";
Console.WriteLine(komunikat);
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.
Użyte w tej lekcji metody i właściwości
| Nazwa | Zwraca | Dział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.InvariantCulture | CultureInfo |
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.Today | DateTime |
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”. |
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.
// --- 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}");
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
| Fragment | Co 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.
Na czym się najczęściej potykamy
| Błąd | Co 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 CS0029 — ToString 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
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}");
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.
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}");
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.
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");
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.
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");
}
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.
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}");
}
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').
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}");
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ę.
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}. F2do kwot,N0do dużych liczb,D2do numerów i godzin,Pdo procentów — pamiętając, żePmnoży przez 100.- We własnych wzorach
0to miejsce obowiązkowe,#— opcjonalne. Na pozycji jedności zawsze0. - 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
MMto miesiąc, ammto 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.