Két féle szám típust ismertünk meg:
int
- egész számok (integer)float
- tizedes tört számok (floating point = lebegőpontos)
Szám típusú változók létrehozása
Azzal jelezzük, hogy egy szám float típusú, hogy a végére egy f betűt teszünk. (Úgynevezett: f suffix)
int i = 1; // Int típusú változó deklarálása és definiálása
float f = 3.5f; // Float típusú változó deklarálása és definiálása
// Programozásban mindig tizedes pontot használunk (nem vesszőt)
var i2 = 5; // Ez így int típusú változó lesz
var f2 = 5f; // Ez így float típusú változó lesz
var d1 = 5.0; // Ez így se nem float, de nem int, hanem double típusú,
// amivel itt nem foglalkozunk.
float f3 = 5; // Ha kiírjuk a típust, akkor egészből is lehet float.
float f4 = 4.0; // Ez így HIBÁS! float változó <- Double érték
int i3 = 4f; // Ez így HIBÁS! int változó <- Float érték
Alapműveletek számokon
Nézzük, meg milyen matematikai műveleteket tudunk rajtuk elvégezni!
Alap műveletek int
-eken:
int a = 1, b = 2, c = -3;
int summa = 4 + a; // összeadás
int difference = b - a; // kivonás
int product = c * 2; // szorzás
// Int osztás később...
Alapműveletek float-okon:
float a = 1.3f, b = -5.1f, c = 3f;
float summa = 4.9f + a; // összeadás
float difference = b - a;// kivonás
float product = c * 2f; // szorzás
float quotient = c / b; // hányados
int
-ek osztása esetén a kimenet lehet tört szám, ebben az esetben a tizedes rész levágásra kerül, csak az egész rész marad.
int a = 1, b = 2, c = -3;
int quotient1 = a / b; // 1.5 -> 1
int quotient2 = b - c; // -0.6˙ -> 0
int quotient2 = c / a; // -3
Mint látható változót és konstans számokat vegyítve használhatunk.
Operátorok
Az egyszerű műveleteket idegen szóval operáció-knak is nevezhetjük. (Gyakorlatban nem gyakran)
A műveletek elvégzésére használt karakter az operátor. (Ezt a fogalmat már szoktuk használni)
Példák operátorokra: +
-
*
/
Egy műveletnek lehetnek bemenetei és van egy kimente: A bemeneteket operandus-oknak nevezzük, a kimenetet visszatérés-nek.
Egy műveletre tekinthetünk úgy mint egy gép, ami vár megadott számú és típusú bemeneteket és ha megkapta őket, akkor legyárt valamilyen kimenetet:
Az, hogy a dobozon belül mi történik, például, hogy hogyan állítja elő a számítógép az összeget, az jelen pillanatban nem érdekel minket, csak a bemenetek, a kimenet és a köztük lévő logika.
Majd látni fogjuk, hogy nem minden esetben azonos a bemenetek és kimenetek típusa.
Ha egy műveletben az operandusok sorrendje nem számít, azt kommutatív műveletnek nevezzük. Ekkor az operandusok felcserélhetőek. Kommutatív művelet például az összeadás és a szorzás. (3+5) egyenlő (5+3)-mal. Nem kommutatív művelet példáául a kivonás vagy az osztás, hiszen (3-5) nem egyezik meg (5-3)-tel.
Alapműveletek Int és Float közt
Ha műveleteket végzünk egy int
és egy float
változón, a kimenet mindig float
típusú.
int i = 10;
float f = 2.5f;
var summa = i + f ; // float típusú. Értéke: 12.5f
var difference = i - f; // float típusú. Értéke: 7.5f
var product = i * f; // float típusú. Értéke: 25f
var quotient = i / f; // float típusú. Értéke: 4f
Dobozokkal kifejezve:
Matek vs. Programozás
Vegyük az következő programkódot példának:
Ha hasonló leírást látnánk egy matematikafüzetben joggal feltételeznénk, hogy a szerző a legalapvetőbb műveleteket sem érti. A leírtak szimplán hibásak lennének.
Ezzel szemben programozásban ez a kódrészlet teljességgel szabályos és értelmes.
int a, b, c;
a = 1;
b = 2;
c = a + b;
c = 7;
Matematikában:
Programozásban:
Igaz állítások egy sorozatát adjuk meg.
Végrehajtandó műveletek egy sorozatát adjuk meg.
Ha megnézzük a példa utolsó sorát, ott nem kijelentjük, hogy c értéke 7, hanem c-nek a 7-es értéket adjuk bármi is volt előtte.
int a = 1, b = 2, c;
c = a + b;
Console.WriteLine(c); // Kiírja: 3
c = 7;
Console.WriteLine(c); // Kiírja: 7
// Unityben így írnánk ki: Debug.Log(c);
A negálás művelet
Nem minden operátornak van 2 bemenete. Egy számot tudunk negálni ebben az esetben csak egy bemeneti érték van.
int i = 6;
float f = -77.7f;
var iNeg = -i; // Típus: int Érték: -6
var fNeg = -f; // Típus: float Érték: 77.7f
A modulo művelet
A jól ismert összeadáson, kivonáson, szorzáson, osztáson és negáláson kívül még létezik egy alapművelet, amire szintén minden programozási nyelvben egy operátorral elvégezhető.
Ez a modulo művelet és a %
operátort használjuk rá C#-ban. Ehhez két megjegyzés:
- Ez a művelet a magyar matematikai szakirodalomban kongruencia néven szerepel, én azonban a modulo kifejezést fogom továbbra is használni, mert az informatikában az az elterjedtebb.
- (A C# nyelv készítői által választott operátor a modulo műveletre a százalék karakter:
%
. Ennek ellenére semmi köze nincs a modulonak a százalék matematikai fogalmához. Egyszerűen csak ez a karakter volt szabad.
Úgy a legegyszerűbb a modulo műveletet megérteni, hogy visszagondolunk arra az időszakra, mikor általános iskolában az osztást tanultuk, de a törtek fogalmával még nem voltunk tisztában.
Ekkor ha osztanunk kellet, így végeztük el:
14 osztva 4-gyal egyenlő 3-mal, maradék a 2
23 osztva 5-tel egyenlő 4-gyel, maradék a 3
Ez a bizonyos maradék lesz a modulo művelet eredménye.
// 14 osztva 4-gyal egyenlő 3-mal, maradék a 2
int q1 = 14 / 4; // Eredmény: 3
int m1 = 14 % 4; // Eredmény: 2
// 23 osztva 5-tel egyenlő 5-tel, maradék a 3
int q2 = 23 / 5; // Eredmény: 4
int m2 = 23 % 5; // Eredmény: 3
Habár a float
számoknál a számítógép nem csapja le a tizedes pont/vessző utáni részt, a modulo művelet ugyanúgy értelmezett flaot
-okon is.
float q2 = 63.5f / 10f; // Eredmény: 6.35f
float m2 = 63.5f % 10f; // Eredmény: 3.5f
Kifejezések
Kifejezésnek nevezünk bármit, aminek az eredménye egy meghatározott típusú adat.
Egy változónak például egy ilyen kifejezést tudunk értékül adni.
Például egy kifejezés lehet egy konstans érték: int a = 2;
int b = 3;
int c = 42;
,
egy másik változó: int i = a;
int j = i;
,
vagy bármiféle összetett művelet eredménye: int y = -2 * 23 - 5;
int y = i + 3 * 4 - a / 2;
.
Ha valaminek egy konkrét típusa és értéke van az mind kifejezés.
Zárójelek, műveleti sorrend
A matematikai jelöléstanban létezik a már jól megszokott műveleti sorrend. Egy bizonyos művelet lehet azonos, magasabb- vagy alacsonyabb rendű, mint egy másik művelet. Például:
A szorzás magasabb rendű művelet, mint az összeadás.
Az összeadás és a kivonás azonos rendű művelet.
Mit is jelent ez?
Ha zárójelek nélkül írunk le egy kifejezést, akkor mindig a magasabb rendűt hajtjuk először végre.
Azonos rendű műveletek esetén pedig azt, ami az olvasásban előrébb (ballra) van.
Ez a C#-ban is ugyanígy működik:
float f1 = 2.5f + 2 / 8 * 10 - 4;
// Ez lépésről lépésre lebontva így hajtódna végre:
float f2 = 2 / 8; // 0.25
float f3 = f2 * 10; // 2.5
float f4 = 12 + f3; // 5
float f5 = f4 - 4; // 1
// Azaz zárójelezve:
float f6 = (2.5f + ((2 / 8) * 10)) - 4; // 1
Ahogy azt megszokhattuk a matematikában, a C#-ban is használhatunk zárójeleket a műveleti sorrend módosítására.
// Ha a fenti kifejezést igy írnánk le, akkor az teljesen más eredményt adna:
float g1 = 2.5f + ((2 / 8) * (10 - 4)); // 4
// Ez lépésről lépésre lebontva így hajtódna végre:
float g2 = 2 / 8; // 0.25
float g3 = 10 - 4; // 6
float g4 = g2 * g3; // 1.5
float g5 = 2.5f + g4; // 4
Olvashatóság kedvéért gyakran azonban célszerű a többszörösen összetett kifejezéseket több sorra bontani.
Egyszerűsítések
C#-ban létezik néhány egyszerűsített kifejezési forma gyakori feladatokra.
Ha egy változónak olyan értéket adunk, ami egy művelet eredménye, amiben a változó előző értékét használjuk első paraméterként, akkor annak leírása a következő módon egyszerűsíthető.
float f = 3;
// A következő 2 művelet teljesen ekvivalens:
f = f + 6; // f értékéhez hozzáadunk 6-ot
f += 6; // f értékéhez hozzáadunk 6-ot
// A következő 2 művelet teljesen ekvivalens:
f = f - 3; // f értékéből kivonunk 3-at
f -= 3; // f értékéből kivonunk 3-at
// A következő 2 művelet teljesen ekvivalens:
f = f * 8; // f értékét szorozzuk 8-cal
f *= 8; // f értékét szorozzuk 8-cal
// A következő 2 művelet teljesen ekvivalens:
f = f / -2; // f értékét osztjuk 2-vel
f /= -2; // f értékét osztjuk 2-vel
Ha pontosan 1-gyel akarunk növelni vagy csökkenteni egy szám típusú változó értékén, még egyszerűbb dolgunk van:
int i = 5;
// A következő 4 művelet teljesen ekvivalens:
i = i + 1; // i értékét növeljük 1-gyel
i += 1; // i értékét növeljük 1-gyel
i++; // i értékét növeljük 1-gyel
++i; // i értékét növeljük 1-gyel
// A következő 4 művelet teljesen ekvivalens:
i = i - 1; // i értékét csökkentjük 1-gyel
i -= 1; // i értékét csökkentjük 1-gyel
i--; // i értékét csökkentjük 1-gyel
--i; // i értékét csökkentjük 1-gyel
A kasztolás
Egy változó nem változtathatja meg típusát menet közben. (A C# statikusan típusos nyelv)
Egy értéket viszont sok esetben át tudunk alakítani egyik típusból egy másikba. Ezt kasztolástnak vagy casting-nak nevezzük.
Ez az átalakítás lehetséges például az int
és a float
típusok közt is.
Nézzük meg a következő példakódot:
float f = 2.5;
int i = 3;
float f2 = i; // Implicit kasztolás f2 érték: 3f
int i2 = (int) f; // Explicit kasztolás i2 érték: 2
Látható, hogy két különböző módja is van a kasztolásnak:
- Implicit kasztolás: Az átalakítás automatikusan végbemegy.
- Explicit kasztolás: Külön jeleznünk kell, hogy végre akarom hajtani az átalakítást.
Pl.: int
→ float
A ( )
operátorral végezzük és a zárójelen belül ki kell írni milyen típusba alakítunk:
Pl.: flaot
→ int
A float
-ból int
-be történő kasztolás úgy történik, hogy elhagyjuk a törtrészt.
Figyeljük meg, hogy az int
-ből float
-tá alakításnál nem történik adatvesztés (fogjuk rá), míg float
-ból int-be történő átalakításnál igen, hiszen a tizedespont utáni részt lecsapjuk.
Ezért van az, hogy float
-ból int
-be alakításnál jeleznünk kell, hogy szándékosan végezzük a műveletet: Explicit nyilatkoznunk kell róla, hogy tudjuk, mit csinálunk.
Ez azért van így, hogy nehezebben kövessünk el hibát és lőjük magunkat lábon vele.
// Ezért is lehetséges, hogy egy int értéket adunk egy flaot változónak.
// A háttérben egy láthatatlan implicit cast-olás történik.
float f = 5;
// Ez így viszont hibás:
int i = 4.5f; // 🛑 Fordítási idejű error-t kapunk
Int, Long, Float, Double
Korábban megismerkedtünk az int
és a float
típusokkal. Ám a C#-ban egyéb primitív azaz beépített szám típusok is vannak ezek általában vagy az int
-hez hasonlóan egész számot vagy a float
-hoz hasonlóan tört számot tartalmaznak. Mért van akkor mégis szükség rájuk? A legnagyobb különbség az, hogy hány bit-et használnak fel egy szám ábrázolására.
Minél több bites egy egész szám típusú adat, annál magasabb számokat tud tartalmazni a pozitív tartományban, a negatív tartományban pedig annál alacsonyabbakat.
Ez így van a lebegőpontos számokra is, de emellett a több bites lebegőpontos törtek még nagyobb pontossággal is tudják tárolni számokat, mint a kevesebb bites verzióik.
Emellett még lehet egy szám előjeles vagy előjel nélküli is. Az előjel nélküli számok nem vehetnek fel negatív értéket, csak pozitívat.
Egész
(Előjeles) | Egész
(Előjel nélküli) | Lebegőpontos
(Bináris) | Lebegőpontos
(Decimális) | |
1 byte (8 bit) | sbyte | byte | - | - |
2 byte(16 bit) | short | ushort | - | - |
4 byte (32 bit) | int | uint | float | - |
8 byte (64 bit) | long | ulong | double | - |
16 byte (128 bit) | - | - | - | decimal |
Jelenleg nem kell nagyon foglalkoznunk egyéb szám típusokkal az int
-en és a float
-on kívül, de érdemes fejben tartani a létezésüket.
(A binárisan és decimálisan tárolt lebegőpontos számok közti különbségekkel most nem nagyon foglalkozunk. Egyelőre legyen elég annyi, hogy a decimális formában történő tárolás valamivel pazarlóbb a memóriával, de bizonyos feladatokra alkalmasabb.)
A későbbiekben lesz pár matematikai függvény, amik csak double
-okon működnek.
Bővebb információk a különböző szám típusokról: Adatmennyiség: Bitek és bájtok