Wat leer je in dit hoofdstuk?

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

Appelen en peren

Een waarde van het ene type zomaar aan een ander type toekennen mag niet:

int leeftijd = 4.3;  // FOUT: een double past niet zomaar in een int

Je kan geen appelen in peren veranderen zonder magie. In C# heb je drie manieren (afh. wat er moet gebeuren):

Casting

de klassieke manier (ook in C, C++, Java)

Parsing

enkel om een string om te zetten

Convert-bibliotheek

van .NET. The all-in-one solution wanneer je 1 en 2 beheerst.

Casting

Plaats het doeltype tussen haakjes voor de waarde:

double kommagetal = 13.8;
int afgekapt = (int)kommagetal;  // 13

Belangrijk

Een variabele verandert nooit van type. Je zet enkel de waarde om en gebruikt die elders. kommagetal blijft een double.

Belangrijk

Bij casting vindt géén afronding plaats: alles na de komma gaat verloren (truncation). Wil je afronden, gebruik dan Math.Round (zie verder).

Casting: Narrowing en widening

Een kopje giet je zonder morsen in een grote kom. Omgekeerd loopt het over.

Widening: int naar double past altijd, dus geen cast nodig

Narrowing: double naar int verliest data, dus de cast is verplicht

Casting in de praktijk

Voorspel eerst het resultaat:

Widening: geen cast nodig

Code Resultaat
double d = 20; 20.0
long l = 1500; 1500
int code = 'A'; 65

Narrowing: cast verplicht

Code Resultaat
int i = (int)20.4; 20
int j = (int)9.99; 9
char c = (char)66; 'B'
byte b = (byte)groot; 44

groot is een int met waarde 300.

De klassieke valkuil

Wat verschijnt er op het scherm?

int tempGisteren = 20;
int tempVandaag = 25;
double gem = (tempGisteren + tempVandaag) / 2;
Console.WriteLine(gem);
22

Niet 22,5: gem is wel een double, maar de deling gebeurt tussen twee int’s.

Wat C# ziet

double gem = ((double)tempGisteren + (double)tempVandaag) / 2;

De cast moet gebeuren voor de deling.

Parsing en .ToString()

Parsing briefje → eieren

zet een string om naar een ander type

int numVal = int.Parse("-105");

.ToString() eieren → briefje

de omgekeerde weg, naar tekst (dat elk type heeft)

int getal = 5;
string tekst = getal.ToString();

Tip

Niet zeker dat de gebruiker een geldig getal invoert? Dan is int.TryParse() je vriend (zie appendix).

Parse doet zijn best

Parse probeert de tekst te lezen. Lukt dat niet, dan crasht je programma.

Code Resultaat
int.Parse(" 42 ") 42, spaties errond mogen
int.Parse("+7") 7
int.Parse("3 eieren") crash: FormatException
int.Parse("4,5") crash: geen geheel getal
int.Parse("3000000000") crash: OverflowException, te groot voor een int
bool.Parse("ja") crash: enkel true of false

Invoer van de gebruiker verwerken

ReadLine geeft altijd een string

De gebruiker typt 3 en daarna 4. Wat toont dit?

string a = Console.ReadLine();
string b = Console.ReadLine();
Console.WriteLine(a + b);
3 [enter]
4 [enter]
34

Zelfde invoer, ander resultaat.

Geen crash: de + plakt twee briefjes aan elkaar. ReadLine geeft je altijd een briefje, ook als de gebruiker een getal typt.

Waarschuwing

int getal = Console.ReadLine(); mag evenmin: de compiler weigert een string in een int te steken.

Van string naar getal

Uitlezen

met ReadLine

Bewaren

in een string

Parsen

naar het type dat je nodig hebt

De meest gebruikte zin dit semester

double gewicht = double.Parse(Console.ReadLine());

Wat de gebruiker typt komt binnen als string, Parse maakt er een double van.

.Parse

int getal = int.Parse(Console.ReadLine());

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.

Komma of punt?

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

Een punt op een Belgische computer

Console.WriteLine("Geef het gewicht in kg:");
double gewicht = double.Parse(Console.ReadLine());
Console.WriteLine($"Dubbel zo zwaar: {gewicht * 2} kg");
Geef het gewicht in kg:
9.81 [enter]
Dubbel zo zwaar: 1962 kg

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.

De Convert-bibliotheek

Naast casten en parsen is er nog een derde manier: Convert zet zowat elk type om naar elk ander type.

int g = Convert.ToInt32(2.7);   // 3
double d = Convert.ToDouble(5); // 5.0
bool b = Convert.ToBoolean(1);  // true
int l = Convert.ToInt32("19");  // 19

Naar int is ToInt32, naar short is ToInt16.

Een keukenrobot: je ziet niet of er gecast of geparset wordt. En Convert.ToInt32 rondt af, waar een cast afkapt.

Casting, conversie of parsing?

Alle omzettingen samengevat.

Casting en parsing zijn compact en werken ook in andere talen. Beheers ze dus, ook al is Convert verleidelijk eenvoudig.

Terug in de keuken

Casting
kopje en kom 2,7 eieren

Tussen getaltypes. (int)2.7 geeft 2: hij kapt af.

Parsing en ToString
briefje

Tussen tekst en getal. int.Parse("3") geeft 3, 3.ToString() geeft "3".

Convert
keukenrobot

Van alles naar alles, ook bool. Convert.ToInt32(2.7) geeft 3: hij rondt af.

Berekeningen met System.Math

Wat is een bibliotheek?

Een verzameling kant-en-klare methoden die iemand anders al geschreven heeft:

Console.WriteLine("Hallo");   // bibliotheek Console, ken je al
double w = Math.Sqrt(144);    // bibliotheek Math, nieuw

Math

de bibliotheek

.

een punt

Sqrt(144)

de methode, met haar parameters

Telkens hetzelfde patroon als bij Console.

De Math-bibliotheek

double macht = Math.Pow(getal, 3);   // grondtal, exponent
double wortel = Math.Sqrt(144);      // 12
double omtrek = Math.PI * 2 * straal;

Verder o.a. Sin Cos Tan Ceiling Floor Abs Max Min Clamp

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.

Math geeft kommagetallen terug

int resultaat = Math.Pow(2, 10);  // compileert niet!

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 m.b.v. casting of Convert:

double resultaat = Math.Pow(2, 10);          // 1024
int resultaatAlsInt = (int)Math.Pow(2, 10);  // 1024

Tip

Uitzonderingen zijn Abs, Max, Min en Clamp: die geven terug wat je erin stopt. Math.Abs(-42) is dus een int.

Methoden in elkaar schuiven

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:

double afstand = Math.Sqrt(Math.Pow(dx, 2) + Math.Pow(dy, 2));

C# werkt van binnen naar buiten: eerst de twee machten, dan de optelling, en pas dat totaal gaat in Sqrt.

Waarden begrenzen

levens = Math.Max(levens - schade, 0);        // nooit onder 0
oven = Math.Clamp(oven, 50, 250);             // 300 wordt 250
int verschil = Math.Abs(vandaag - gisteren);  // 5, niet -5

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.

Rekenen kan mislukken

Console.WriteLine(a / b);          // int door 0: DivideByZeroException
Console.WriteLine(x / y);          // double door 0.0: oneindig
Console.WriteLine(Math.Sqrt(-1));  // NaN, Not a Number

NaN is besmettelijk: NaN + 5 blijft NaN, NaN * 1000 ook.

Controleren doe je met double.IsNaN(resultaat), dat geeft je een bool.

Oneindig heeft ook een naam: double.PositiveInfinity en double.NegativeInfinity.

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.

Het bereik in code

Elk datatype kent z’n eigen grenzen, en de compiler kent ze ook:

Console.WriteLine($"int gaat van {int.MinValue} tot {int.MaxValue}");
int gaat van -2147483648 tot 2147483647

En wat als je over die grens gaat?

int groot = int.MaxValue;
Console.WriteLine(groot + 1);  // -2147483648
-2147483648

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.

Over afronden

Afkappen is niet afronden

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:

Console.WriteLine((int)-2.7);        // -2, richting nul
Console.WriteLine(Math.Floor(-2.7)); // -3, richting min oneindig

Math.Truncate doet exact wat een cast doet, maar geeft je een double terug.

Truncate, Floor en Ceiling

Floor en Ceiling gaan altijd dezelfde kant uit, een cast schuift altijd naar de nul toe.

Hoeveel bussen heb je nodig?

47 studenten, 20 per bus:

int aantalBussen = (int)Math.Ceiling(aantalStudenten / (double)perBus); // 3

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.

Bankers rounding

Math.Round(4.5);  // 4
Math.Round(5.5);  // 6

Ligt een getal exact op de helft, dan rondt Math.Round standaard af naar het dichtstbijzijnde even getal.

Waarschuwing

Handig voor bankiers, minder handig wanneer jouw quotering van 4.5 op 10 plots een 4 wordt.

Waarom naar het even getal?

Een bank rondt elke dag duizenden bedragen af en telt ze daarna op. Gaat elke helft naar boven, dan komt er telkens een halve eenheid bij, en die fouten stapelen zich op.

Naar het even getal gaat de ene helft naar boven en de andere naar beneden: de fouten heffen elkaar op.

Afronden zoals je verwacht

Geef een MidpointRounding mee. Treedt enkel in actie als het getal exact op de helft ligt:

Math.Round(4.5, MidpointRounding.AwayFromZero);         // 5
Math.Round(4.12343, 2, MidpointRounding.AwayFromZero);  // 4,12

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).

De drie manieren naast elkaar

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.

Afronden of enkel mooi tonen?

double gemiddelde = 7.0 / 3.0;

Console.WriteLine(gemiddelde);          // 2,3333333333333335
Console.WriteLine($"{gemiddelde:F2}");  // 2,33

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.

Geld: reken niet met double

Console.WriteLine(0.1 + 0.2);     // 0,30000000000000004

double prijs = 4.35;
int centen = (int)(prijs * 100);  // 434 in plaats van 435!

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.

decimal prijs = 4.35M;   // decimal bewaart de cijfers decimaal

Tip

Voor alles wat op een kassaticket zou staan kies je decimal. Voor wetenschappelijke berekeningen, coördinaten en snelheden blijft double de juiste keuze.

Waarom decimal wel klopt

1/3 in het tiendelig stelsel

0,3333333... loopt eindeloos door. Ergens moet je stoppen.

0,1 binair (double)

0,000110011001100... loopt ook eindeloos door. De double kapt af en bewaart een getal net naast 0,1.

4.35M (decimal)

Bewaard als 435, met de komma twee plaatsen naar links. Tiendelig, net als jij: exact.

Console.WriteLine(4.35M * 100);  // 435,00
Console.WriteLine(1M / 3M);      // 0,3333333333333333333333333333

Random getallen genereren

De Random-generator

Zonder toeval lopen je monsters altijd dezelfde weg en krijg je altijd dezelfde wiskundeopgaven.

Maak eenmalig een generator en vraag dan getallen op:

Random generator = new Random();
int getal1 = generator.Next();
int a = generator.Next(0, 11);    // 0 tot en met 10
int b = generator.Next(55, 100);  // 55 tot en met 99

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.

Kommagetallen met NextDouble

NextDouble() geeft een getal tussen 0.0 en 1.0. Schaal en verschuif naar je bereik:

// tussen 5.0 en 12.5
double r = 5.0 + (generator.NextDouble() * 7.5);
Bereik van Random verschuiven en schalen.

Bereik = 12.5 - 5.0 = 7.5, daarna +5 om te verschuiven.

Een vaste seed

Een Random rekent zijn getallen uit, vertrekkend van een startgetal: de seed. Geef je zelf een seed mee tussen de haakjes, dan krijg je bij elke start dezelfde reeks:

Random generator = new Random(666);
Console.WriteLine(generator.Next(1, 7));
Console.WriteLine(generator.Next(1, 7));
Console.WriteLine(generator.Next(1, 7));

1e keer starten:

4
2
2

2e keer starten:

4
2
2

Handig om je code te testen. Elk ander getal dan 666 is een andere seed.

Seeds in Balatro en Minecraft

Met dit ene getal

666

ligt je hele wereld vast. (het hoeft niet 666 te zijn, elk getal is een andere seed)

Zelfde seed, zelfde reeks

Jouw wereld op een andere pc

Er bestaan hele forums vol goeie seeds.

Debuggen

Logica vs syntax

Code die compileert, is enkel grammaticaal correct.

De betekenis kan nog altijd fout zijn:

Neem natrium
Neem water
Voeg beide samen

Perfecte “code”, maar een logische fout (en een stevige explosie). Dat is een bug.

Een bug in Visual Studio?

“Meneer, ik heb een bug in Visual Studio gevonden.”

99%

van de keren zit die bug in je eigen code.

Breakpoints

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.

Door je code steppen

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.

Conclusie

Omzetten kan via casting, parsing en Convert: ken het verschil

keukenrobot

Narrowing (met dataverlies) vereist een cast, widening gaat vanzelf

kopje en kom 2,7 eieren

ReadLine geeft altijd een string: parse naar het juiste type

briefje

Math rekent met kommagetallen en in radialen; oneindig en NaN crashen niet

oven

Kies bewust tussen afkappen en afronden; voor geld gebruik je decimal

weegschaal

Eén Random-generator volstaat; debuggen doe je door te voorspellen

proeven onderweg

Tip

Volgende halte: beslissingen nemen met if, vergelijkingen en switch.

Meer weten

Kennisclips

Alle kennisclips van H4: 19 korte clips over casten, invoer omzetten, Math, afronden, geld, Random en debuggen

Oefeningen H4 · Quizlet flashcards

Zie verder

Hoe doen andere programmeertalen hetzelfde? En een korte zoek-de-fout, om zo kritisch mogelijk naar code te leren kijken.

Zie verder: type-coercion

C# is streng. JavaScript zet types net automatisch om (coercion):

console.log("5" + 3);  // "53"  (de 3 wordt een string)
console.log("5" * 3);  // 15    (de "5" wordt een getal)
53
15

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.

Zie verder: omzetten in Python

De naam van het type is er meteen de omzetfunctie:

gewicht = float(input("Geef je gewicht: "))

Wiskunde zit in een aparte module die je eerst moet importeren:

import math

wortel = math.sqrt(144)   # 12.0

C# heeft een aparte Parse per type, en Math hoort standaard bij de taal.

Zie verder: zoek de fout

Deze Python-code rondt twee examencijfers af. Eén van de twee studenten gaat klagen. Waarom?

cijfer1 = 4.5
cijfer2 = 5.5

print(round(cijfer1))  # 4
print(round(cijfer2))  # 6

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.

Samenvattende poster

Alles uit dit hoofdstuk op één A4.

Download de poster als pdf

Mars Climate Orbiter

1999: veel te laag de atmosfeer in

Een ruimtesonde van

125

miljoen dollar, verloren door een getal zonder eenheid.

Pound-force of newton?

Allebei gewoon een getal, allebei perfect gecompileerd, geen enkele foutmelding.

Wat een datatype niet weet

Wat het getal betekent, moeten mensen onder elkaar afspreken.

Stagiair Steven

Steven moet de leeftijd van de gebruiker inlezen. De A.I. gaf hem dit en hij noemt het zelf “robuust”:

Console.WriteLine("Geef je leeftijd:");
int leeftijd = int.Parse(Console.ReadLine());
Console.WriteLine($"Volgend jaar word je {leeftijd + 1}.");
Geef je leeftijd:
25 [enter]
Volgend jaar word je 26.

“Werkt perfect, ik heb het getest met 25”, zegt hij trots. Wat als de gebruiker vijfentwintig typt, of gewoon op enter duwt?

Steeds dezelfde getallen?

Een dobbelsteen gooide

10

keer na elkaar hetzelfde getal.

Dezelfde code, twee versies van .NET

De klok als seed

Het oude .NET Framework

In het huidige .NET krijgt elke Random een eigen, willekeurige seed.