Let's write a program that generates a list of random numbers and then calculates the mean and standard deviation for the list of numbers . The form we'll use is shown in Figure 19.15.
Figure 19.15. The form for the exception handler sample program.
The text box associated with the number of values is named txtN and the two text boxes near the bottom of the form are named txtMean and txtSD . The two radio buttons are named rbPopulation and rbSample . The command buttons are named btnCalc and btnExit . I don't really need to get into a statistics lesson here, but the mean is simply the average of a list of numbers and the standard deviation is a measure of the variability of the numbers. The sample standard deviation has one less degree of freedom than does the population standard deviation; hence the adjustment in the Term value in the program code.
The code is presented in Listing 19.3.
Listing 19.3 Code for the Exception Handling Example
Imports System.Math Imports System.Double Public Class Form1 Inherits System.Windows.Forms.Form ' Windows Form Designer generated code Private Sub btnCalc_Click(ByVal sender As System.Object, ByVal e As _ System.EventArgs) Handles btnCalc.Click Dim Values() As Double, Mean As Double, SD As Double Dim N As Long, i As Integer, Which As Integer txtmean.Enabled = False txtSD.Enabled = False N = CLng(txtN.Text) ' How many values to generate ReDim Values(N) ' Set the array size Randomize() ' Seed the random number generator For i = 0 To N - 1 Values(i) = 101.0 * Rnd() ' Make up some values Next If rbPopulation.Checked = True Then Which = 1 ' Do population SD Else Which = 0 ' Do sample SD End If SD = StandardDeviation(Values, Mean, N, Which) If Not IsInfinity(Mean) And Not IsNaN(SD) Then txtmean.Enabled = True txtSD.Enabled = True End If txtSD.Text = Format(SD, "###.#####") txtmean.Text = Format(Mean, "###.#####") End Sub Public Function StandardDeviation(ByVal X() As Double, ByRef Mean As _ Double, ByVal N As Double, ByVal Which As Double) As Double ' Purpose: This function finds the mean and standard deviation ' of a series of data. ' ' Argument list: ' X() an array of doubles that holds the data ' Mean the calculated mean, to be filled in by this function ' N the number of observations ' Which calculate pop. SD (Which = 1) or sample SD (Which = 0) ' ' Return value: ' Double the standard deviation for the data set. If an error ' is detected, SD is set to NaN and Mean is set to ' either positive or negative infinity. ' ' CAUTION: Note that argument Mean is passed by reference Dim i As Long, sum As Double, ss As Double, SD As Double Dim Term As Double Try sum = 0.0 ' Yea, I know...they're set to 0 anyway. ss = 0.0 For i = 0 To N - 1 sum += X(i) ' Do the running sum ss += X(i) * X(i) ' Do sums of squares Next Mean = sum / CDbl(N) ' Calculate the mean If Which = 1 Then ' Population SD Term = CDbl(N) Else ' Sample SD, lose one degree of freedom Term = CDbl(N - 1.0) End If If IsPositiveInfinity(Mean) Or IsNaN(Mean) Then ' See if bogus Throw New DivideByZeroException() End If Catch e As DivideByZeroException ' Here if N = 0 sum = PositiveInfinity Catch e As OverflowException ' Here if really big value sum = PositiveInfinity Catch sum = NegativeInfinity ' Here for everything else Finally SD = Sqrt(((N * ss) - (sum * sum)) / (N * Term)) If IsInfinity(SD) Or IsNaN(SD) Then SD = NaN Mean = PositiveInfinity End If End Try Return SD End Function Private Sub btnExit_Click(ByVal sender As System.Object, ByVal e As _ System.EventArgs) Handles btnExit.Click Me.Dispose() End Sub Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As _ System.EventArgs) Handles MyBase.Load rbSample.Checked = True txtmean.Enabled = False txtSD.Enabled = False End Sub End Class
At the very top of Listing 19.3, you'll see that we have imported two additional namespaces. The Math namespace is needed because we use the square root method of the Math class. I'll explain the need for the Double namespace in a moment.
The btnCalc Click Event
When the user clicks the Calculate button, a number of tasks are initiated. First, we set the Enabled property of the two output text boxes to False . We do this so that we can better inform the user when an exception has occurred.
Next, we set the number of elements requested by the user to N . We then redimension the number of elements in the Values() array to permit that number of values. The careful reader will notice that we end up creating one more element than the user requested because arrays start with 0 and ReDim sets the highest index value permitted, not the number of elements. Although we could have adjusted the ReDim statement accordingly , leaving it as it is doesn't affect what we are trying to accomplish.
After the array is resized, we fill it in with random values that fall between 0 and 100. Next we determine whether the user wants to calculate the sample or population standard deviation. Now that we have things set up properly, we call the StandardDeviation() function.
The StandardDeviation() Function
The StandardDeviation() function is passed in four arguments: the Values() array that holds the values; a variable (cleverly) named Mean to hold the mean; the number of observations used in the calculations ( N ); and which type standard deviation is to be calculated ( Which ). You can see the call to StandardDeviation() in the btnCalc Click event.
The StandardDeviation() Function Argument List
As you'll recall from our discussion in Chapter 5, Visual Basic .NET makes all arguments passed to functions copies of the original data. This means that each argument is automatically given the ByVal keyword when you type in the statement for the function. This poses a minor problem for us because functions can return only a single value. We want our function to do double-duty and return both the mean and standard deviation. Hmmm
Because I want to return the standard deviation and the mean from the function, I elected to have the function return the value for the standard deviation. After all, the name of the function is StandardDeviation() , so it seems likely that should be the returned value. So, how do we return the mean?
Simple. In the function's argument list, we change the default pass by value for Mean to be pass by reference. This is why ByRef is used with the Mean argument in the StandardDeviation() function definition. This means that we're passing the lvalue of Mean from the call to the function in the btnCalc Click event. Because StandardDeviation() is using the lvalue for Mean from the btnCalc Click event, our function has access to the Mean defined in btnCalc . Therefore, we can change btnCalc 's Mean while we're executing the StandardDeviation() function code.
To prove to yourself that this works, set a breakpoint in the StandardDeviation() code on the line just after the Mean is calculated. The line is
If Which = 1 Then ' Population SD
Now run the program. When you reach the breakpoint, place the cursor over the variable Mean in the line above the breakpoint line and note its value. Now scroll to the variable named Mean in the btnCalc Click event. The values will be the same, even though you've suspended program execution in the code for the StandardDeviation() function. This is proof positive that the function is using the lvalue of Mean in btnCalc to affect its value in the StandardDeviation() function.
The Exception Handling Code in StandardDeviation()
Once we are into the StandardDeviation() function code, we set the start of the exception handler using the Try keyword. Then we read the data and perform the calculations for the sum ( sum ), sum of squares ( ss ), and the mean ( Mean ). There's an If test that sets the denominator for the standard deviation calculation depending on the value of Which . Then the mean is calculated.
After the mean is calculated, there's the following If statement block:
If IsPositiveInfinity(Mean) Or IsNaN(Mean) Then ' See if the number is bogus Throw New DivideByZeroException() End If
We do this just in case the user plays mind games and throws in a value of 0 for N in the statement that calculates the mean located immediately above this statement. The If test uses the IsPositiveInfinity( ) and IsNaN() methods to see whether the calculation of Mean threw an exception.
Double Data Type and Numeric Overflow
You're probably saying: "Wait a minute! If N is zero, why doesn't the code throw a divided by zero exception that's handled in the divide by zero Catch block?" Good question.
You've probably seen many examples in other books where that is exactly what happens. However, their code doesn't have our little If test. The reason is because they probably used integer variables for the data. If you perform a divide by zero operation using a Double data type, however, Visual Basic .NET does not throw a divide by zero exception! Instead, it sets the variable to PositiveInfinity , NegativeInfinity , or NaN (Not a Number), depending on the result of the operation performed. These constants are defined in the Double class, which is why we imported its namespace at the top of the program code.
The If test uses the IsPositiveInfinity() and IsNaN() methods to see whether Mean has either of these values after the math operation. If either method returns a logic True, we throw a DivideByZeroException . We throw what?
The Throw Keyword
We can force Visual Basic .NET to generate an exception by using the Throw keyword. In our code, we want to generate a divide by zero exception, so we create a new divide by zero exception with the statement
Throw New DivideByZeroException()
This causes Visual Basic .NET to generate a divide by zero exception. When this happens, program control is immediately transferred to the Catch block that we've written for the divide by zero exception. In this case, the Catch block simply sets sum to PositiveInfinity .
The Finally Statement Block
The code found in the Finally statement block is always executed, even if there is no exception. In our case, the code calculates the standard deviation ( SD ). However, if any exception was generated along the way, the variable sum will have been assigned the value PositiveInfinity or NegativeInfinity , depending on the exception. (Notice that we also have a generic Catch for those exceptions we haven't thought of.)
After SD is calculated, we test its value with the IsInfinity() and IsNaN() methods. If either method is logic True, we reset the values for SD and Mean . We then return SD from the call.
Back in the btnCalc Click event, we test the value returned from the StandardDeviation() function to see whether it is a Nan . We also test Mean to see whether it was set to infinity (notice how we use the unary Not operator in the If test). If either condition is true, the two output text boxes remain disabled, which causes their background colors to be set to gray. If the tests are passed, the text boxes are enabled and the values displayed on the normal white background.
Can you think of any use for the Command window while using this program to learn about exception handling?