Gaußsches Fehlerintegral / Quantile der Normalverteilung


Wenn man damit zu tun hat, weiß man, worum es geht. Das Gaußsche Fehlerintegral, leider geschlossen nicht lösbar, wird üblicherweise mit Hilfe von Tabellen bestimmt. Kein Problem für Menschen, die schon vor dem Taschenrechnerzeitalter zur Schule gingen und es gelernt haben, mit tabellierten Funktionen umzugehen, sie schnell und fehlerfrei abzulesen und zu interpolieren. Das waren etwa Logarithmentafeln, Sinustabellen und eben das Gaußsche Fehlerintegral

Fehlerintegral
oder ähnliche Integrale, z.B. das Wahrscheinlichkeitsintegral, die alle das exp(-t2)-Glied enthalten.
Geht es auch einfacher?

Die Tabellen sind doch seinerzeit - ohne Computer - berechnet worden. Können wir das, mit unseren heutigen Hilfsmitteln, nicht genau so gut oder besser?
Das oben gezeigte, bestimmte Integral wird dargestellt durch die unendliche Reihe:

Reihe

Der Rest ist einfach. Nur mal eben unendlich viele Glieder berechnen, aufsummieren, fertig.
Nun ja, die Glieder dieser alternierenden Reihe konvergieren, langsam aber sicher. Bei der Berechnung von Reihen steht eine "Restgliedbetrachtung" an. Die soll verraten, wann man die Reihe abbrechen kann. Bei einer gutartigen Reihe, wie der hier vorliegenden, genügt es, so lange immer höhere Glieder (unten dfkt genannt) zu berücksichtigen, bis deren absoluter Wert eine wählbare Größe (unten Eps) unterschreitet.

Die folgende Funktion setzt diese Idee um. Benötigt wird der n!-Ausdruck, der eigens berechnet wird.

Option Explicit
Const Pi = 3.141592654
Const Eps = 0.0000000001

Function phi(x As Double) As Double
Dim n As Integer, fkt As Double, dfkt As Double
n = 0
fkt = 0
Do
dfkt = (-1) ^ n * x ^ (2 * n + 1) / fak(n) / 2 ^ n / (2 * n + 1)
fkt = fkt + dfkt
n = n + 1
Loop Until Abs(dfkt) < Eps
phi = fkt / Sqr(2 * Pi)
End Function
Function fak(m As Integer) As Double
Dim i%
fak = 1
For i = 1 To m
fak = fak * i
Next
End Function

Für x > 1 konvergiert diese Reihe sehr, sehr langsam. Für größere x-Werte ist eine so große Zahl von Gliedern erforderlich, daß irgendwann Zahlenüberlauf auftreten kann, der sich zuerst bei der Fakultätsberechnung bemerkbat macht. Deshalb ist es sinnvoll, Vorsorge gegen die Eingabe unvernünftig großer x-Werte zu treffen.

Zunächst ermöglichen wir Nutzung der Funktion. Es sind zwei Textfelder für Ein- und Ausgabe vorgesehen. Dazu ein Button Command1, dessen Click-Ereignis die Rechnung auslöst.

Private Sub Command1_Click()
Dim x As Double
x = CDbl(Text1.Text)
Text2.Text = "Phi(x)= " & Format(phi(x), "0.########")
End Sub

Damit wäre das Programm bereits verwendbar, allerdings ohne die oben erwähnte Sicherung gegen Fehlbedienung. Die kommt später.
Aber zuvor:
Was ist mit der Quantile? Der umgekehrten Ablesung also, der Vorgabe einer Prozentschranke und Bestimmung des zugehörigen Vielfachen der Standardabweichung, wofür hier unser x steht? Die könnte man doch gleich mit erschlagen?

Die folgenden Bezeichnungen seien phi(x) = y. Wir suchen diesmal zum vorgegeben y das zugehörige x. Das bedeutet die Lösung der Gleichung: phi(x) - y = 0. Um die zu lösen, suchen wir zunächst eine Beinahe-Lösung ("Vorauswahl") und starten dazu anschließend eine Newtonsche Nullstellensuche. Dies Verfahren bricht ab, wenn die Differenz von phi(x) und y einen vorgegebenen Grenzwert unterschreitet.

Dieser Programmteil könnte dann wie folgt aussehen:

y = CDbl(Text2.Text)
'Vorauswahl
x = 0
Do
x = x + 0.1
Loop Until phi(x) > y
'Newton-Nullstellensuche
Do
dphi = (phi(x) - phi(x - 0.000001)) / 0.000001
xn = x - (phi(x) - y) / dphi
x = xn
Loop Until Abs(phi(x) - y) < 0.00000001
Text1.Text = "x = " & Format(x, "0.########")

Die Einbindung dieses Programmfragmentes ist insofern schon begonnen, als die Eingabe in dem Fenster entgegengenommen wird, in dem bei der vorhergehenden Rechnung das Resultat angezeigt wurde. Wir prüfen die Fenster auf einen Eintrag, werten dies als Anzeige der Rechenrichtung, rechnen und geben in das leere Fenster aus. 

Wie das so aussehen könnte? Es sind vier mögliche Fälle denkbar, zwei unerwünschte: Kein Eintrag, zwei Einträge und die zwei möglichen Berechnungsfälle.

Private Sub Command1_Click()
Dim x As Double, y As Double, dphi As Double, xn As Double
If Text1.Text <> "" And Text2.Text <> "" Then
MsgBox "Erbitte neue Eingabe!", 16, "ACHTUNG"
Exit Sub
End If
If Text1.Text <> "" Then
x = CDbl(Text1.Text)
Text2.Text = "Phi(x)= " & Format(phi(x), "0.########")
ElseIf Text2.Text <> "" Then
y = CDbl(Text2.Text)
'Vorauswahl
x = 0
Do
x = x + 0.1
>Loop Until phi(x) > y
'Newton-Nullstellensuche
Do
dphi = (phi(x) - phi(x - 0.000001)) / 0.000001
xn = x - (phi(x) - y) / dphi
x = xn
Loop Until Abs(phi(x) - y) < 0.00000001
Text1.Text = "x= " & Format(x, "0.########")
Else
MsgBox "Eingabe erforderlich!", 16, " A C H T U N G"
End If
End Sub

Die Eingabe numerisch sinnloser Startwerte wird allerdings noch nicht unterbunden. Außerdem, muß der "Rechne"-Button wirkich sein? ...
Wenn wir die Textfelder ohnehin  gegen Fehleingaben (z.B. Text) und gegen die Eingabe numerisch unzulässiger Werte überwachen, können wir die Rechnung auch mit der Werteeingabe verknüpfen. Die Technik dazu wird beim Programmbeispiel zum Währungsrechner erläutert.
Private Sub Text1_GotFocus()
Text1.Text = ""
Text2.Text = ""
End Sub
Private Sub Text2_GotFocus()
Text1.Text = ""
Text2.Text = "0,"
Text2.SelStart = 3
End Sub
Private Sub Text1_KeyPress(KeyAscii As Integer)
KeyAscii = PrüfeZeichen1(KeyAscii, Text1.Text)
End Sub
Private Sub Text2_KeyPress(KeyAscii As Integer)
KeyAscii = PrüfeZeichen2(KeyAscii, Text2.Text)
End Sub
Function PrüfeZeichen1(Prüfling As Integer, vorhTxt As String) As Integer
Dim erlaubt As String
If Len(vorhTxt) = 0 Then erlaubt = "01234"
If Len(vorhTxt) = 1 Then erlaubt = "," & Chr(8)
If Len(vorhTxt)> 1 Then erlaubt = "0123456789" & Chr(8)
If InStr(erlaubt, Chr(Prüfling)) = 0 Then
PrüfeZeichen1 = 0
Exit Function 'Prüfling war unzulässig!
End If
PrüfeZeichen1 = Prüfling 'Prüfling ist zulässig!
End Function
Function PrüfeZeichen2(Prüfling As Integer, vorhTxt As String) As Integer
Dim erlaubt As String
If Len(vorhTxt) = 2 Then erlaubt = "01234"
If Len(vorhTxt)> 2 Then erlaubt = "0123456789" & Chr(8)
If InStr(erlaubt, Chr(Prüfling)) = 0 Then
PrüfeZeichen2 = 0
Exit Function 'Prüfling war unzulässig!
End If
PrüfeZeichen2 = Prüfling 'Prüfling ist zulässig!
End Function

Zum Schluß ein Blick auf das Programmfensterchen nach einer Berechnung; es ist - fast - bediensicher. Wie man es überlisten kann, wird nicht verraten.

Fehlerintegral
zum Anfang

© R. Hirte * 2003