Numerieke fouten spelen een cruciale rol in de nauwkeurigheid van berekeningen, vooral in toepassingen die afhankelijk zijn van numerieke methoden. Deze fouten kunnen ontstaan door de beperkingen van de computerrepresentatie van getallen en hebben invloed op de resultaten van veel algoritmes. In deze sectie worden enkele voorbeelden van numerieke fouten en manieren om ze te verminderen besproken.

Een veelvoorkomend probleem bij numerieke berekeningen is de precisie van getallen. In veel programmeertalen worden de getallen opgeslagen als zogenaamde "floating-point" getallen, die een eindige hoeveelheid geheugen gebruiken om reële getallen te representeren. Dit kan leiden tot een verlies van precisie. Als voorbeeld wordt een programma gepresenteerd waarbij een waarde 0.1 10.000 keer wordt opgeteld:

c
#include <stdio.h> int main() { double s = 0.0; int i;
for (i = 0; i < 10000; i++) s = s + 0.1;
printf("%f\n", s); return 0; }

Wanneer dit programma wordt uitgevoerd, geeft het als resultaat 1000.000000, terwijl we misschien hadden verwacht dat het exact 1000 zou zijn. Dit komt door de onvolledige representatie van 0.1 in het binaire systeem van de computer.

Een ander voorbeeld van numerieke fouten komt voor bij het aftrekken van twee getallen die erg dicht bij elkaar liggen. Dit fenomeen staat bekend als "cancellation error". Als we bijvoorbeeld de waarden 123.45678 en 123.45655 van elkaar aftrekken, krijgen we het volgende resultaat:

c
#include <stdio.h>
int main() { double a, b; a = 123.45678; b = 123.45655; printf("%f\n", a - b); return 0; }

Het resultaat van deze berekening is 0.000230, wat lijkt te wijzen op een relatief klein verschil, maar het is belangrijk te realiseren dat de werkelijke precisie van deze berekening beperkt is door de representatie van de getallen in het systeem.

Een veel complexer geval van numerieke fouten doet zich voor bij het oplossen van kwadratische vergelijkingen. Bijvoorbeeld, bij de vergelijking:

x2+200000x3=0x^2 + 200000x - 3 = 0

waar de exacte oplossingen x1=200000x_1 = -200000 en x2=0.000015x_2 = 0.000015 zijn. Het uitvoeren van de berekening met een float datatype in plaats van een double resulteert in een incorrecte oplossing voor x2x_2:

c
#include <stdio.h> #include <math.h> int main() { float a, b, c, disc, x1, x2; a = 1.0; b = 200000; c = -3; disc = b * b - 4 * a * c; x1 = (-b - sqrt(disc)) / (2 * a); x2 = (-b + sqrt(disc)) / (2 * a); printf("x1 = %f, x2 = %f\n", x1, x2); return 0; }

Het resultaat van dit programma is:

ini
x1 = -200000.000000, x2 = 0.000000

Hoewel x1x_1 correct is, is x2x_2 niet nauwkeurig genoeg, en dit kan worden toegeschreven aan de cancelatiefout die optreedt bij het aftrekken van grote en kleine getallen. Dit probleem kan worden opgelost door het gebruik van het double datatype, dat een hogere precisie biedt:

c
#include <stdio.h>
#include <math.h> int main() { double a, b, c, disc, x1, x2; a = 1.0; b = 200000; c = -3; disc = b * b - 4 * a * c; x1 = (-b - sqrt(disc)) / (2 * a); x2 = (-b + sqrt(disc)) / (2 * a); printf("x1 = %f, x2 = %f\n", x1, x2); return 0; }

Het resultaat is nu:

ini
x1 = -200000.000015, x2 = 0.000015

Bij numerieke berekeningen is het dus vaak raadzaam om het double datatype te gebruiken, aangezien dit een betere precisie biedt, ondanks dat het meer geheugen vereist. De keuze voor het juiste datatype is essentieel om numerieke fouten te minimaliseren en betrouwbare resultaten te verkrijgen. Dit kan echter niet altijd de oplossing zijn, aangezien voor toepassingen die onbegrensde precisie vereisen, symbolische rekensystemen zoals Mathematica of Maple nuttig kunnen zijn.

Daarnaast speelt de keuze van het algoritme een belangrijke rol in de nauwkeurigheid en efficiëntie van numerieke berekeningen. Twee veelgebruikte methoden voor het vinden van de wortels van een functie f(x)=0f(x) = 0 zijn de bisection method en de Newton-Raphson method.

De bisection method is gebaseerd op de Mean Value Theorem en is gegarandeerd om ten minste één wortel te vinden als de begininterval correct wordt gekozen. Het algoritme halveert herhaaldelijk het interval en zoekt naar het punt waar de functiewaarden van de twee uiteinden van het interval een tegenovergestelde teken hebben. Deze methode is betrouwbaar, maar kan langzamer zijn dan andere benaderingen.

Newton's method daarentegen, convergeert kwadratisch en is sneller voor veel functies. Het vereist echter een goede startwaarde en kan falen als de gekozen waarde te ver van de werkelijke wortel ligt. Het algoritme is gebaseerd op het idee van het vinden van de raaklijn aan de grafiek van de functie en het gebruiken van de snijpunten met de x-as als benadering voor de wortel.

Beide methoden hebben hun voor- en nadelen, en de keuze van welke te gebruiken hangt af van de specifieke situatie en de aard van de functie die moet worden opgelost.

Hoe Los Je Gelijktijdige Vergelijkingen Numeriek op?

In veel technische en wetenschappelijke toepassingen is het noodzakelijk om stelsels van gelijktijdige lineaire vergelijkingen op te lossen. Dit is een fundamenteel onderwerp binnen de numerieke wiskunde, vooral wanneer discretisatie of linearisatie van problemen leidt tot dergelijke systemen. Voor relatief kleine systemen, bijvoorbeeld met twee of drie vergelijkingen, kan men gebruik maken van Cramer's regel, een algebraïsche methode die gebruik maakt van determinanten. Voor grotere systemen is echter een andere aanpak nodig, aangezien Cramer's regel niet efficiënt is voor systemen met duizenden of miljoenen vergelijkingen. In deze gevallen moeten gespecialiseerde methoden worden toegepast.

Een stelsel van gelijktijdige lineaire vergelijkingen kan worden uitgedrukt in matrix-vector vorm als Ax = c, waarbij A een matrix is van coëfficiënten, x de onbekenden bevat en c de rechterleden van de vergelijkingen. Voor een stelsel van n vergelijkingen ziet dit eruit als:

(a11a12a1na21a22a2nan1an2ann)(x1x2xn)=(c1c2cn)\begin{pmatrix} a_{11} & a_{12} & \cdots & a_{1n} \\ a_{21} & a_{22} & \cdots & a_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{n1} & a_{n2} & \cdots & a_{nn} \end{pmatrix} \begin{pmatrix} x_1 \\ x_2 \\ \vdots \\ x_n \end{pmatrix} = \begin{pmatrix} c_1 \\ c_2 \\ \vdots \\ c_n
\end{pmatrix}

Het oplossen van dit stelsel is essentieel voor veel praktische toepassingen, zoals het simuleren van fysieke systemen of het oplossen van economische modellen.

Cramer's regel biedt een oplossing voor systemen van twee of drie vergelijkingen door het gebruik van determinanten. Voor een systeem met twee vergelijkingen, zoals:

a11x1+a12x2=c1,a21x1+a22x2=c2,\begin{aligned} a_{11}x_1 + a_{12}x_2 &= c_1, \\ a_{21}x_1 + a_{22}x_2 &= c_2,
\end{aligned}

wordt de oplossing voor x₁ en x₂ als volgt uitgedrukt:

x1=det(A1)det(A),x2=det(A2)det(A)x_1 = \frac{\text{det}(A_1)}{\text{det}(A)}, \quad x_2 = \frac{\text{det}(A_2)}{\text{det}(A)}

waarbij A₁ en A₂ matrices zijn waarin de kolommen van de originele matrix A worden vervangen door de vector c. Hoewel deze methode voor kleine systemen relatief eenvoudig te implementeren is, heeft ze een significante beperking in de praktische toepasbaarheid. De complexiteit van het berekenen van determinanten neemt exponentieel toe naarmate de grootte van de matrix toeneemt. Voor bijvoorbeeld een 4×4-matrix vereist het berekenen van de determinant al honderden vermenigvuldigingen, en voor een matrix van 10×10, kan het aantal vermenigvuldigingen oplopen tot honderden miljoenen. Dit maakt Cramer's regel onpraktisch voor grote systemen.

In de praktijk worden methoden zoals de Gauss-Jordan eliminatie vaak gebruikt om stelsels van gelijktijdige vergelijkingen op te lossen. Deze methode is gebaseerd op het principe van lineariteit en maakt gebruik van een reeks elementaire rijoperaties om een matrix te transformeren naar de zogenaamde gereduceerde rij-echelonvorm. Het doel van de Gauss-Jordan eliminatie is om de matrix in een vorm te brengen waarin de onbekenden direct kunnen worden afgelezen. Dit wordt gedaan door rijen van de matrix te manipuleren zodat de hoofddiagonaal elementen van 1 bevat en alle andere elementen in die kolommen 0 zijn. Het proces kan geïllustreerd worden met het voorbeeld van de volgende stelsel van drie vergelijkingen:

2xy+z=2,x+3y+3z=3,2x+y+4z=1.\begin{aligned}
2x - y + z &= 2, \\ -x + 3y + 3z &= 3, \\ 2x + y + 4z &= 1. \end{aligned}

Door gebruik te maken van rijoperaties wordt het systeem omgezet naar een matrix in de volgende vorm:

(100270103100121)\begin{pmatrix}
1 & 0 & 0 & | & 27 \\ 0 & 1 & 0 & | & 31 \\ 0 & 0 & 1 & | & -21 \end{pmatrix}

Dit geeft de oplossingen x = 27, y = 31 en z = -21. Het aantal bewerkingen voor de Gauss-Jordan eliminatie is veel lager dan voor Cramer's regel en is doorgaans van de orde O(n³), wat betekent dat het aanzienlijk efficiënter is voor grotere systemen.

Hoewel de Gauss-Jordan eliminatie nuttig is voor kleinere stelsels, is het niet altijd de meest efficiënte keuze voor grotere systemen, waarvoor de LU-decompositie of andere geavanceerdere technieken vaak sneller en praktischer zijn.

Naast de theoretische onderbouwing van deze methoden, is het essentieel voor de lezer om te begrijpen dat numerieke technieken voor het oplossen van gelijktijdige vergelijkingen vaak worden toegepast in systemen die in de praktijk worden gevonden. Dit betekent dat men te maken krijgt met grote hoeveelheden data en de noodzaak om algoritmen te optimaliseren voor de uitvoering op moderne hardware, zoals parallelle verwerking. Daarnaast moeten numerieke methoden altijd worden geëvalueerd op hun stabiliteit en nauwkeurigheid, vooral wanneer de betrokken getallen grote schommelingen vertonen of wanneer er sprake is van een singulier systeem. Het is belangrijk te realiseren dat geen enkel numeriek algoritme perfect is en dat er altijd trade-offs bestaan tussen nauwkeurigheid, snelheid en rekenkracht.