Zie Scherp Scherper - Hoofdstuk 4
Het recept vraagt 250 gram. Jij hebt een maatbeker.
Een programma zit met hetzelfde probleem.
Na dit hoofdstuk kan je:
data omzetten via casting, parsing en de Convert-bibliotheek
het verschil tussen narrowing en widening uitleggen
gebruikersinvoer correct inlezen en omzetten naar een getal
rekenen met System.Math en methoden in elkaar schuiven
bewust afronden (let op bankers rounding) en weten wanneer je decimal nodig hebt
willekeurige getallen genereren met Random
debuggen met breakpoints en je code stap voor stap doorlopen
Een waarde van het ene type zomaar aan een ander type toekennen mag niet:
Je kan geen appelen in peren veranderen zonder magie. In C# heb je drie manieren:
Casting
de klassieke manier (ook in C, C++, Java)
Convert-bibliotheek
van .NET
Parsing
enkel om een string om te zetten
Plaats het doeltype tussen haakjes voor de waarde:
Belangrijk
Een variabele verandert nooit van type. Je zet enkel de waarde om en gebruikt die elders. kommagetal blijft een double.
Narrowing = een type omzetten met verlies aan data (bv. double naar int).
int gooit alles na de komma weg.Dit vereist een expliciete cast
Je zegt zo tegen de compiler: “ik weet dat er data verloren gaat, en ik draag de verantwoordelijkheid”
Casten rondt niet af
(int)20.9 geeft 20
tempGisteren en tempVandaag zijn int (20 en 25):
Dit geeft 22.0, niet 22.5. De expressie bevat enkel int’s, dus de deling is een gehele deling. Casten lost het op:
int, rechts rekent de hele expressie als double.Belangrijk
De plaats van je cast telt: (double)(a + b) / 2 geeft 22.5, maar (double)((a + b) / 2) geeft 22. Daar dwingen de haakjes de gehele deling eerst af.
Een klein type in een groter type past zonder dataverlies, dus geen cast nodig:
Casten mag hier wel ((double)hoofdMeting), maar het hoeft niet.
Een ruimtesonde van
125
miljoen dollar, verloren door een getal zonder eenheid.
Allebei gewoon een getal, allebei perfect gecompileerd, geen enkele foutmelding.
Wat het getal betekent, moeten mensen onder elkaar afspreken.
Tip
Niet zeker dat de gebruiker een geldig getal invoert? Dan is int.TryParse() je vriend (zie appendix).
De gebruiker typt 3 en daarna 4. Wat toont dit?
Geen crash: de + plakt twee strings aan elkaar. C# heeft geen idee dat jij met "3" en "4" wou rekenen.
Waarschuwing
int getal = Console.ReadLine(); mag evenmin: de compiler weigert een string in een int te steken.
Op het briefje staat “3 eieren”. Daar bak je geen omelet mee.
ReadLine geeft je het briefje. Om te rekenen heb je de eieren nodig, en die haal je met Parse.
Uitlezen
met ReadLine
Bewaren
in een string
Parsen
naar het type dat je nodig hebt
Wat de gebruiker typt komt binnen als string, Parse maakt er een double van.
Enkel de Parse-methode verandert mee met het type dat je nodig hebt:
| Gewenst type | Code |
|---|---|
int |
int.Parse(Console.ReadLine()) |
double |
double.Parse(Console.ReadLine()) |
bool |
bool.Parse(Console.ReadLine()) |
char |
char.Parse(Console.ReadLine()) |
string |
Console.ReadLine() (geen Parse nodig) |
Opmerking
De komende hoofdstukken mag je ervan uitgaan dat de gebruiker foutloze invoer geeft.
Steven moet de leeftijd van de gebruiker inlezen. De A.I. gaf hem dit en hij noemt het zelf “robuust”:
“Werkt perfect, ik heb het getest met 25”, zegt hij trots. Wat als de gebruiker vijfentwintig typt, of gewoon op enter duwt?
Opmerking
Dan crasht zijn programma. int.Parse verwacht tekst die echt een geheel getal voorstelt. Steven testte enkel de invoer die hij zelf in gedachten had, en de A.I. waarschuwde hem nergens. Foute invoer opvangen doe je met int.TryParse.
In je code
schrijf je een kommagetal-literal altijd met een punt: 9.81 (onafhankelijk van je taalinstellingen)
Als invoer
hangt het van de landinstellingen af: een Belgisch systeem verwacht 9,81, een Engels 9.81
Waarschuwing
Het verkeerde scheidingsteken geeft geen foutmelding. Op een Belgische computer wordt 9.81 gelezen als 981, want de punt geldt daar als scheiding voor duizendtallen. Je programma rekent vrolijk verder met een gewicht van 981 kilogram.
Vreemde resultaten bij kommagetallen zonder dat er iets crasht? Kijk eerst welk teken je hebt getypt.
Een Amerikaans recept schrijft 1.5, een Vlaams 1,5.
double.Parse leest het zoals je computer ingesteld is.
Naar int is .ToInt32(), naar short is .ToInt16()
Een zwarte doos: je ziet niet of er gecast of geparset wordt
Tip
Convert.ToBoolean geeft voor elk getal True, behalve voor 0 (of 0.0). En een bool naar een int omzetten lukt enkel via Convert, niet via casting.
Alle omzettingen samengevat.
Casting en parsing zijn compact en werken ook in andere talen. Beheers ze dus, ook al is Convert verleidelijk eenvoudig.
Verder o.a. Sin Cos Tan Ceiling Floor Abs Max Min Clamp
Voor Math schrijf je nooit new.
Waarschuwing
Sin, Cos en Tan rekenen in radialen. Math.Sin(90) geeft 0.8939... en niet 1. Reken eerst om: double hoek = graden * Math.PI / 180;
Tip
Typ Math. en IntelliSense toont alles. Cursor op een methode + F1 opent de documentatie.
Cannot implicitly convert type ‘double’ to ‘int’. Bijna alle rekenmethoden van Math geven een double terug, ook al stop je er gehele getallen in. Je lost het op zoals daarnet:
Tip
Uitzonderingen zijn Abs, Max, Min en Clamp: die geven terug wat je erin stopt. Math.Abs(-42) is dus een int.
Het resultaat van een methode mag de parameter van een andere methode zijn. Dat deed je al in double.Parse(Console.ReadLine()). De afstand tussen speler en monster, met Pythagoras:
C# werkt van binnen naar buiten: eerst de twee machten, dan de optelling, en pas dat totaal gaat in Sqrt.
Max / Min
de grootste of de kleinste van twee getallen
Clamp
begrenzen langs twee kanten tegelijk (waarde, ondergrens, bovengrens)
Abs
het minteken verdwijnt, ideaal voor “hoe groot is het verschil?”
Tip
Deze heb je in de praktijk vaker nodig dan alle sinussen samen.
NaN is besmettelijk: NaN + 5 blijft NaN, NaN * 1000 ook.
Controleren doe je met double.IsNaN(resultaat), dat geeft je een bool.
Belangrijk
Oneindig en NaN laten je programma niet crashen. De foute berekening werkt stilletjes door, soms tot honderden regels verder waar je plots een onmogelijk resultaat ziet.
Elk datatype kent z’n eigen grenzen, en de compiler kent ze ook:
Bij kommagetallen bestaat zelfs double.PositiveInfinity en double.NegativeInfinity.
Waarschuwing
Overflow: de teller loopt over zoals de kilometerteller van een oude auto. Geen crash, geen waarschuwing. Verwacht je grote getallen, kies dan meteen een long.
Casting kapt af, ongeacht of er een 1 of een 9 na de komma staat. Bij negatieve getallen zie je het verschil met echt afronden:
Math.Truncate doet exact wat een cast doet, maar geeft je een double terug.
Floor en Ceiling gaan altijd dezelfde kant uit, een cast schuift altijd naar de nul toe.
47 studenten, 20 per bus:
47 / 20.0
2.35
Math.Ceiling
3.0
(int)
3
Overal waar je iets moet vullen (bussen, dozen, pagina’s) mag je niets laten liggen, en daar is Math.Ceiling voor.
Waarschuwing
Vergeet je de cast naar double, dan deel je twee gehele getallen en krijg je 2 nog voor Ceiling iets kan doen. Er staan dan 7 studenten op de parking.
Math.Round rondt standaard af naar het dichtstbijzijnde even getal.
Rond je de reeks 0.5 tot 5.5 allemaal naar boven af, dan tel je er 3 bij. Met bankers rounding kom je exact op de echte som uit.
Waarschuwing
Handig voor bankiers, minder handig wanneer jouw quotering van 4.5 op 10 plots een 4 wordt.
Geef een MidpointRounding mee:
De volgorde van de parameters ligt vast:
eerst het getal
dan het aantal cijfers na de komma
pas als laatste de MidpointRounding
Belangrijk
Convert.ToInt32 doet ook aan bankers rounding, en daar kan je het niet afzetten. Rond dan eerst zelf af: (int)Math.Round(4.5, MidpointRounding.AwayFromZero).
| Waarde | (int) |
Math.Round |
met AwayFromZero |
Convert.ToInt32 |
|---|---|---|---|---|
4.5 |
4 | 4 | 5 | 4 |
5.5 |
5 | 6 | 6 | 6 |
-2.5 |
-2 | -2 | -3 | -2 |
Belangrijk
Kies bewust. Wil je afkappen, gebruik een cast of Math.Truncate. Wil je afronden, gebruik Math.Round met MidpointRounding.AwayFromZero. Doe je niets, dan krijg je bankers rounding.
Bij F2
behoudt de variabele al haar cijfers, enkel de tekst op het scherm is ingekort
Bij Math.Round
gooi je die cijfers echt weg
Tip
Rond pas af op het laatste moment, vlak voor je het resultaat toont. Rond je te vroeg af, dan sleep je die fout doorheen je hele berekening mee.
4.35 * 100
levert 434.99999999999994
(int)
de cast kapt de rest af
434
in plaats van 435
Je klant is een cent kwijt, en bij duizend bestellingen merkt de boekhouding dat.
Tip
Voor alles wat op een kassaticket zou staan kies je decimal. Voor wetenschappelijke berekeningen, coördinaten en snelheden blijft double de juiste keuze.
Maak eenmalig een generator en vraag dan getallen op:
Belangrijk
De ondergrens telt mee, de bovengrens niet. Wil je een getal tot en met X, schrijf dan X + 1 als tweede parameter.
Tip
De new Random()-syntax pluizen we vanaf hoofdstuk 9 uit. Lig er nu nog niet van wakker.
Een dobbelsteen gooide
10
keer na elkaar hetzelfde getal.
In het huidige .NET krijgt elke Random een eigen, willekeurige seed.
Maak toch maar één generator aan. Telkens dezelfde reeks nodig, bv. om je code te testen? Geef dan zelf een seed mee:
Tip
Het hoeft geen 666 te zijn: elk getal is een andere seed.
Met dit ene getal
666
ligt je hele wereld vast.
Er bestaan hele forums vol goeie seeds.
Code die compileert, is enkel grammaticaal correct.
De betekenis kan nog altijd fout zijn:
Perfecte “code”, maar een logische fout (en een stevige explosie). Dat is een bug.
“Meneer, ik heb een bug in Visual Studio gevonden.”
99%
van de keren zit die bug in je eigen code.
Een kok proeft tussendoor, niet pas als het bord op tafel staat.
Een breakpoint doet dat met je programma.
Klik links van een lijn voor een breakpoint (rode bol)
Je programma pauzeert daar
In Locals/Autos zie je de waarde van elke variabele
Je kan ook over een variabele hoveren.
Een breakpoint.
Continue
verder tot het volgende breakpoint of het einde
Step over (het gebogen pijltje)
één lijn verder, dan weer pauzeren
Stop (rode knop)
stoppen om je code aan te passen
Voorspel telkens eerst wat er moet gebeuren: welke waarden, welke output?
Klopt de werkelijkheid niet met je voorspelling, dan heb je waarschijnlijk een bug beet.
Waarschuwing
Zomaar door je code steppen en hopen dat de bug magisch verschijnt, werkt niet. Een programmeur die niet kan debuggen is als een vis die niet kan zwemmen.
Belangrijk
De verboden prompt: “Schrijf de C#-oplossing voor deze opgave.” Dat is als de Tour de France-winnaar de Mont Ventoux voor je laten opfietsen: je geraakt boven, maar leert niets.
Beter zijn prompts die je doen denken en waarmee je in dialoog gaat:
Zoek de fout
laat AI code met ingebouwde fouten maken
Vergelijken
je oplossing met de modeloplossing
Ondervragen
laat je ondervragen over de output van een stukje code
Links doet de A.I. het denkwerk, rechts doe jij het.
Omzetten kan via casting, parsing en Convert: ken het verschil
Narrowing (met dataverlies) vereist een cast, widening gaat vanzelf
ReadLine geeft altijd een string: parse naar het juiste type
Math rekent met kommagetallen en in radialen; oneindig en NaN crashen niet
Kies bewust tussen afkappen en afronden; voor geld gebruik je decimal
Eén Random-generator volstaat; debuggen doe je door te voorspellen
Tip
Volgende halte: beslissingen nemen met if, vergelijkingen en switch.
Kennisclips
Hoe doen andere programmeertalen hetzelfde? En een korte zoek-de-fout, om zo kritisch mogelijk naar code te leren kijken.
C# is streng. JavaScript zet types net automatisch om (coercion):
Die eerste regel doet C# ook: "5" + 3 levert daar eveneens de tekst "53" op. Het verschil zit in de tweede regel: "5" * 3 weigert de C#-compiler.
De naam van het type is er meteen de omzetfunctie:
Wiskunde zit in een aparte module die je eerst moet importeren:
C# heeft een aparte Parse per type, en Math hoort standaard bij de taal.
Deze Python-code rondt twee examencijfers af. Eén van de twee studenten gaat klagen. Waarom?
round() doet in Python ook aan bankers rounding: ligt een getal op de helft, dan gaat het naar het dichtstbijzijnde even getal. Wat Python niet heeft is de MidpointRounding-parameter.
Een handige samenvattende poster (Opgelet: gemaakt m.b.v. ChatGPT Image Gen 2).