Chapter 6. Numbers and Math

Introduction

Visual Basic is now completely on a par with C# and other languages in its scientific, engineering, and financial number-crunching capabilities. This chapter demonstrates how easy it is to develop very fast and powerful, yet easy-to-read code for advanced number-crunching applications. Some of the recipes will appeal to almost all developers, such as those demonstrating rounding, the new unsigned integers, and the new Decimal numbers that are suitable for the most demanding financial calculations. Other recipes will appeal to the many scientist and engineer types searching for 21st century updates for FORTRAN, programmable calculators, and Excel.

6.1. Using Compact Operator Notation

Problem

You want to write compact, efficient code using the latest syntax available for assignment operators.

Solution

Sample code folder: Chapter 06CompactOperators

Visual Basic 2005 now lets you use the same compact assignment notation for some math operations that has been used in the C and C# languages for many years.

There are several compact assignment operators, and they all work the same way. The variable to the left of the operator is used both as a source value and as a destination for the results of the operation. The operators are listed in Table 6-1.

Table 6-1. Compact assignment operators

Operator

Description

^=

Exponentiation

*=

Multiplication

/=

Division

=

Integer division

+=

Addition

-=

Subtraction

<<=

Shift left

>>=

Shift right

&=

Comparison

Discussion

Consider the following program statement, which increments the variable count:

	count = count + 1

The variable count is repeated twice in this simple line of code, once to retrieve its value and once to assign the results of adding 1 to the value. The new, more efficient compact assignment syntax uses the variable’s name just once:

	count += 1

The compact assignment operator += causes the variable to be used both as the source of the value to be operated on and as the destination for the result.

The following sample code demonstrates all of the operators listed in Table 6-1:

	Dim result As New System.Text.StringBuilder
	
	Dim testDouble As Double = Math.PI
	result.Append("Double ").AppendLine(testDouble)
	testDouble += Math.PI
	result.Append("+= ").AppendLine(testDouble)
	testDouble *= Math.PI
	result.Append("*= ").AppendLine(testDouble)
	testDouble -= Math.PI
	result.Append("-= ").AppendLine(testDouble)
	testDouble /= Math.PI
	result.Append("/= ").AppendLine(testDouble)
	testDouble ^= Math.PI
	result.Append("^= ").AppendLine(testDouble)
	result.AppendLine()
	
	Dim testInteger As Integer = 17
	result.Append("Integer ").AppendLine(testInteger)
	testInteger = 2
	result.Append("= 2 … ").AppendLine(testInteger)
	testInteger += 1
	result.Append("+= 1 … ").AppendLine(testInteger)
	testInteger <<= 1
	result.Append("<<= 1 … ").AppendLine(testInteger)
	testInteger >>= 3
	result.Append(">>= 3 … ").AppendLine(testInteger)
	result.AppendLine()

	Dim testString As String = "Abcdef"
	result.Append("String ").AppendLine(testString)
	testString &= "ghi"
	result.Append("&= ghi … ").AppendLine(testString)
	testString += "jkl"
	result.Append("+= jkl … ").AppendLine(testString)
	
	MsgBox(result.ToString())

Figure 6-1 shows the results displayed by this block of code. While many of the operators work on double-precision variables, some work only on integers of various sizes, and the concatenation operator works only on strings.

The compact assignment operators in action
Figure 6-1. The compact assignment operators in action

Although the += (addition) operator is overloaded to operate on either numerical variables or strings, your code will be clearer if you use the addition operator only for mathematical operations. For string concatenation, use the &= operator instead. This rule can also help you avoid hidden errors when working with numbers formatted as strings. For instance, consider the following code, which updates an Integer value with numbers stored in strings:

	Dim numberFromStrings As Integer
	numberFromStrings = "4"
	numberFromStrings += "3"
	MsgBox(numberFromStrings)

When you run this code, it displays “7” in the message box. This works because Visual Basic is “helping you out,” automatically converting the strings to Integer values before performing the assignment or addition. If you replace the += operator in that code with the &= operator, the code behaves differently:

	Dim numberFromStrings As Integer
	numberFromStrings = "4"
	numberFromStrings &= "3"
	MsgBox(numberFromStrings)

This time, the message box displays “43,” the concatenation of the two strings. Some of the documentation for the += and &= operators claims that the two are functionally equivalent when working with strings, but this example shows that care should be exercised when using them in mixed string/number situations.

See Also

Search for “operator procedures” in Visual Studio Help for more information.

6.2. Choosing Integers of the Right Size and Type for the Job

Problem

You want to use the right- sized integer variable for the job at hand.

Solution

Sample code folder: Chapter 06UsingIntegers

Visual Basic 2005 now has signed and unsigned integer variable types that range in size from 8 bits to 64 bits (1 byte to 8 bytes). Using the right size and type of integer can save memory, generate more efficient code, and provide ranges of integer values suitable to a variety of needs.

Discussion

Visual Basic 2005 is the first version of Visual Basic to support signed byte values and unsigned integer values in a variety of sizes. Here’s a list of all the integer types now supported:

Byte

Eight-bit (1-byte) values ranging from 0 to 255. Equivalent to System.Byte.

SByte

A signed type that is 8 bits (1 byte) in size and holds values ranging from −128 to +127. Equivalent to System.SByte.

Short

Sixteen-bit (2-byte) values ranging from −32,768 to +32,767. Equivalent to System.Int16.

UInt16

An unsigned type that is 16 bits (2 bytes) in size and holds values ranging from 0 to 65,535. Equivalent to System.UInt16.

Integer

Thirty-two-bit (4-byte) values ranging from −2,147,483,648 to +2,147,483,647. Equivalent to System.Int32.

UInteger

An unsigned type that is 32 bits (4 bytes) in size and holds values ranging from 0 to 4,294,967,295. Equivalent to System.UInt32.

Long

Sixty-four-bit (8-byte) values ranging from −9,223,372,036,854,775,808 to +9,223,372,036,854,775,807 (−9 to +9 quintillion). Equivalent to System.Int64.

ULong

An unsigned type that is 64 bits (8 bytes) in size and holds values ranging from 0 to 18,446,744,073,709,551,615 (18 quintillion). Equivalent to System.UInt64.

The following code demonstrates each of these integer types by displaying the largest possible value for each:

	Dim result As New System.Text.StringBuilder()
	result.AppendLine("MaxValue…")
	result.AppendLine()

	Dim maxByte As Byte = Byte.MaxValue
	Dim maxSByte As SByte = SByte.MaxValue
	Dim maxShort As Short = Short.MaxValue
	Dim maxUShort As UShort = UShort.MaxValue
	Dim maxInteger As Integer = Integer.MaxValue
	Dim maxUInteger As UInteger = UInteger.MaxValue
	Dim maxLong As Long = Long.MaxValue
	Dim maxULong As ULong = ULong.MaxValue
	
	result.Append("Byte ").AppendLine(maxByte)
	result.Append("SByte ").AppendLine(maxSByte)
	result.Append("Short ").AppendLine(maxShort)
	result.Append("UShort = ").AppendLine(maxUShort)
	result.Append("Integer = ").AppendLine(maxInteger)
	result.Append("UInteger = ").AppendLine(maxUInteger)
	result.Append("Long = ").AppendLine(maxLong)
	result.Append("ULong = ").AppendLine(maxULong)
	
	MsgBox(result.ToString())

For all unsigned variable types, the minimum possible value is zero. For all signed types, to find the minimum value add one to the maximum value, and change the sign. For example, the maximum value for signed bytes is 127, and the minimum value is −128. As shown above, the MaxValue property of each integer type provides a straightforward way to access the largest possible value. Similarly, you can get the smallest possible value by accessing each type’s MinValue property.

Figure 6-2 shows the maximum values for each type of integer, as displayed by the message box in the example code.

Maximum values for the various integer variable types
Figure 6-2. Maximum values for the various integer variable types

One other variable type is worth considering for extremely large integer values. Although not true integers, Decimal variables can hold integer values up to 79,228,162,514,264,337,593,543,950,335 (79 octillion). The rule for determining the minimum value for a Decimal-type variable is slightly different than for the true integer types: in this case, just reverse the sign of the maximum value, don’t add 1. The MinValue for Decimal variables is thus −79,228,162,514,264,337,593,543,950,335.

Decimal values are signed 128-bit (16-byte) numbers, and they may have a decimal point. If you appropriately round off or truncate values, the Decimal type can accurately hold extremely large integer values. However, even on 64-bit machines, this data type can slow down calculations somewhat because the processor must perform calculations using multiple steps to process each value.

6.3. Using Unsigned Integers

Problem

You want to work with nonnegative integers while minimizing the memory requirements of variables in your code.

Solution

Use the smallest unsigned integer variable types that will hold the desired range of nonnegative values.

Discussion

As mentioned in the previous recipe, the unsigned integer variable types provide many new options for working with nonnegative integers in Visual Basic 2005. The following code provides a specific example to help clarify the concept:

	Dim testUShort As UShort
	Do Until (testUShort > CUShort(33000))
	   testUShort += CUShort(1)
	Loop
	MsgBox("UShort result: " & testUShort.ToString())

The standard Visual Basic Short variable type holds signed integers in the range −32,768 to +32,767 and uses only two bytes of memory. If the previous code used signed integers, an exception would be generated during the looping because values up to 33,001 are not allowed. The unsigned testUShort integer stores values up to 65,535, so the program runs successfully, and the variable still requires only two bytes of memory. Figure 6-3 shows a two-byte unsigned variable displaying a number too big for a standard signed two-byte integer.

Unsigned integer variables can hold bigger numbers than signed integers, in the same amount of memory
Figure 6-3. Unsigned integer variables can hold bigger numbers than signed integers, in the same amount of memory

See Also

Search for “UInteger” in Visual Studio Help for more information.

6.4. Swapping Two Integers Without Using a Third

Problem

You want to swap the values of two integer variables without creating a third.

Solution

Sample code folder: Chapter 06IntegerSwap

Use the exclusive-or bit manipulation function to do the trick.

Discussion

Nowadays efforts to save the space of a single variable in memory seem kind of silly, but this recipe nevertheless demonstrates an interesting technique for swapping two numbers without creating a third variable. More importantly, it demonstrates how bit-manipulation functions can be quite useful in Visual Basic 2005. Here’s the sample code:

	Dim result As String
	Dim firstValue As Integer
	Dim secondValue As Integer

	' ----- Set the initial test values.
	firstValue = 17
	secondValue = 123
	result = String.Format("Before swap: {0}, {1}", _
	   firstValue, secondValue)
	result &= vbNewLine

	' ----- Swap the values at the bit level.
	firstValue = firstValue Xor secondValue
	secondValue = firstValue Xor secondValue
	firstValue = firstValue Xor secondValue
	result &= String.Format("After swap: {0}, {1}", _
	   firstValue, secondValue)

	MsgBox(result)

The above code loads values into integers firstValue and secondValue, then swaps their values by applying three successive Xor operators on them. The Xor operator combines the two integers on a bit-by-bit basis, resulting in a 1 bit whenever the original bits are different and a 0 when they are the same. Once these three Xor operations have been performed, the original contents of the two integers will have migrated to the opposite locations in memory. Figure 6-4 shows the results displayed by the sample code.

Swapping two integers using Xor
Figure 6-4. Swapping two integers using Xor

See Also

Search for “Xor operator” in Visual Studio Help for more information.

6.5. Using Single- and Double-Precision Variables

Problem

You want to use floating-point numbers but aren’t sure if you should use Singles or Doubles.

Solution

Sample code folder: Chapter 06SingleDouble

Choose the most appropriate variable type based on the range and precision of numbers it can hold and on its memory footprint.

Discussion

To help you understand the capabilities of Single and Double variables, the following sample code uses several useful properties and functions to display information about them:

	Dim result As New System.Text.StringBuilder
	Dim maxSingle As Single = Single.MaxValue
	Dim maxDouble As Double = Double.MaxValue
	Dim sizeOfSingle As Integer = _
	    Runtime.InteropServices.Marshal.SizeOf(maxSingle.GetType)
	Dim sizeOfDouble As Integer = _
	    Runtime.InteropServices.Marshal.SizeOf(maxDouble.GetType)

	result.Append("Memory size of a Single (bytes): ")
	result.AppendLine(sizeOfSingle)
	result.Append("Maximum value of a Single: ")
	result.AppendLine(maxSingle)
	result.AppendLine()
	
	result.Append("Memory size of a Double (bytes): ")
	result.AppendLine(sizeOfDouble)
	result.Append("Maximum value of a Double: ")
	result.AppendLine(maxDouble)

	MsgBox(result.ToString())

The MaxValue constant provided by each type provides the largest possible value for variables of that type. The Marshal.SizeOf() function returns the unmanaged size, in bytes, of any class, which in this case is the class returned by the GetType() method of our Single and Double variables. Figure 6-5 shows the results.

If you’re working with large arrays of numbers and memory issues are of concern, the Single type might be appropriate. If you need greater precision, and using twice the memory per occurrence is not a problem, Doubles might work best.

Singles and Doubles require a different amount of memory and hold different-sized numbers
Figure 6-5. Singles and Doubles require a different amount of memory and hold different-sized numbers

Many mathematical functions, such as those provided by the Math class, operate on Doubles only. Generally this is not a problem, as conversion between Single and Double types in memory is efficient. On the other hand, the GDI+ Graphics object operates on Single values, so it’s best to work with these where possible when creating graphics. For example, many of the graphics functions and methods accept PointF objects passed as parameters, and a PointF is comprised of a pair of Single numbers, X and Y.

See Also

The “PointF” topic in Visual Studio Help describes how Singles are used for many graphics methods.

The “Math Class” subject lists many useful functions that operate on Doubles.

6.6. Using Decimal Variables for Maximum Precision

Problem

You want to manipulate numbers with many significant digits of accuracy.

Solution

Sample code folder: Chapter 06SingleDouble

The Decimal number type holds numbers with up to 29 digits of accuracy and is well suited to tasks in which rounding errors are to be kept to a minimum, as in financial calculations.

Discussion

For really big numbers where you want many digits of accuracy, the Decimal number type is ideal. Numbers of this type are stored in 16 bytes (128 bits) of memory each, with up to 29 significant digits. These numbers can be positive or negative, and a decimal point can be included anywhere within the number. The following code demonstrates Decimal variables in action:

	Dim result As New System.Text.StringBuilder
	Dim maxDecimal As Decimal = Decimal.MaxValue
	Dim sizeOfDecimal As Integer = _
	   Runtime.InteropServices.Marshal.SizeOf(maxDecimal.GetType)

	result.Append("Memory size of a Decimal (bytes): ")
	result.AppendLine(sizeOfDecimal)
	result.Append("Maximum value of a Decimal: ")
	result.AppendLine(maxDecimal)
	result.Append("Divided by one million: ")
	result.AppendLine(maxDecimal / 1000000D)
	result.Append("1D / 3D: ")
	result.AppendLine(1D / 3D)
	
	MsgBox(result.ToString())

Figure 6-6 shows the display created by this code. The Marshal.SizeOf() function determines the number of bytes of memory the Decimal variable uses, and the MaxValue constant gets the largest possible numerical value it can hold. To demonstrate how the decimal point can be anywhere in the number, the maximum value is divided by one million. The decimal point shifts six digits in from the right as a result. To demonstrate that the math operators are overloaded to accurately take advantage of the Decimal’s full precision, the quantity 1/3 is calculated and displayed in the last line of the message box. An uppercase “D” is appended to the constants 1 and 3 in the code to tell the compiler that they are Decimal values.

Using the Decimal number type
Figure 6-6. Using the Decimal number type

See Also

See “Decimal data type” in Visual Studio Help for more information.

6.7. Converting Between Number Types

Problem

You want to explicitly convert numeric variables and calculation results between the various number types.

Solution

Sample code folder: Chapter 06ConvertNumber

It’s always a good idea to make sure your project’s Option Explicit and Option Strict settings are on, but this often forces you to apply explicit conversions when working with more than one type of numeric variable. The solution is to apply one of the many standalone conversion functions provided by Visual Basic or to use one of the many methods of the Convert object.

Discussion

The following code sample demonstrates a simple conversion of Double numeric values to Byte values, using both the standalone CByte() function and the Convert.ToByte() method. Some people prefer to use the Convert object exclusively, which may be easier to remember because all the conversion methods have names beginning with “To”. Others prefer the standalone conversion functions, because many of these have been around in previous versions of Visual Basic for some time now. We look at both approaches here:

	Dim result As New System.Text.StringBuilder
	Dim b1 As Byte = CByte(3.1416) + CByte(314.16 / 2)
	Dim b2 As Byte = Convert.ToByte(3.1416) + _
	   Convert.ToByte(314.16 / 2)

	result.AppendLine("Example conversions to Byte…")
	result.AppendLine()
	
	result.AppendLine("Dim b1 As Byte = CByte(3.1416) + " & _
	   "CByte(314.16 / 2)")
	result.Append("b1 = ")
	result.AppendLine(b1.ToString)
	result.AppendLine()

	result.Append("Dim b2 As Byte = Convert.ToByte(3.1416) + ")
	result.AppendLine("Convert.ToByte(314.16 / 2)")
	result.Append("b2 = ")
	result.AppendLine(b2.ToString)
	result.AppendLine()

	result.AppendLine("Numeric Conversions…")
	result.AppendLine()
	result.AppendLine("CByte(expression)")
	result.AppendLine("CSByte(expression)")
	result.AppendLine("CShort(expression)")
	result.AppendLine("CUShort(expression)")
	result.AppendLine("CInt(expression)")
	result.AppendLine("CUInt(expression)")
	result.AppendLine("CLng(expression)")
	result.AppendLine("CULng(expression)")
	result.AppendLine("CSng(expression)")
	result.AppendLine("CDbl(expression)")
	result.AppendLine("CDec(expression)")

	MsgBox(result.ToString())

The Double value 314.16 will not convert to a Byte because it is out of range for byte values. Attempting this conversion causes an exception. However, dividing this value by 2 results in a Double value that does convert. The point is that the decimal digits don’t cause a problem when converting to a Byte (they are simply rounded to the nearest byte value), but the number must be in the range 0 to 255 to allow the conversion.

Figure 6-7 shows the results of the above demonstration code in action. A sample conversion is shown using both techniques, and a list of the standalone conversion functions is displayed for easy review.

Different ways of converting between number types
Figure 6-7. Different ways of converting between number types

The signed byte and unsigned integer data types are new with this latest version of Visual Basic, and so are the functions to convert values to them.

See Also

See “conversion functions” in Visual Studio Help for more information on these functions.

6.8. Rounding Numbers Accurately

Problem

You need to round off double-precision numbers in a standard, accurate way.

Solution

Sample code folder: Chapter 06Rounding

Use the Math.Round() function to round numbers to the desired precision.

Discussion

The Math.Round() function is overloaded to accept several different sets of parameters. If you pass just a Double or Decimal number to it, the number is rounded to the nearest whole number. By passing a second parameter, you control the number of digits after the decimal point where the rounding is to occur. For example, the following code rounds off the value of pi (π) using zero through five as the number of digits for the rounding:

	Dim outputFormat As String = _
	   "Rounding value: {0} Results: {1}"
	Dim oneTry As String
	Dim result As New System.Text.StringBuilder
	Dim piRounded As Double
	Dim digits As Integer
	
	For digits = 0 To 5
	   piRounded = Math.Round(Math.PI, digits)
	   oneTry = String.Format(outputFormat, digits, piRounded)
	   result.AppendLine(oneTry)
	Next digits

	MsgBox(result.ToString())

Figure 6-8 shows the results of these rounding actions.

Using the Math.Round( ) function to round numbers accurately
Figure 6-8. Using the Math.Round( ) function to round numbers accurately

A third optional parameter lets you fine-tune the way a number is rounded when the number is exactly halfway between two values at the point where the number is to be rounded. The choices are to have the number rounded to an even digit, or away from zero. The default is to round to an even digit.

See Also

See “Math.Round” in Visual Studio Help for more information.

6.9. Declaring Loop Counters Within Loops

Problem

You want to create a variable to hold the loop counter in a For…Next loop, but you want the variable to exist only within the body of the loop.

Solution

Declare the variable type directly using the optional syntax for doing this in the For… Next loop command.

Discussion

If you include As Type immediately after the variable name used in the For…Next statement, Visual Basic 2005 creates this variable on the spot, and its scope is limited to the body of the For…Next loop. If you declare the variable elsewhere, don’t add the As Type clause in the loop statement; doing so triggers an exception.

This sample code creates nested For…Next loops, with the outer loop counter variable declared outside the loop and the inner loop variable declared just for the body of the loop. Study the lines starting with For to see the difference:

	Dim formatString As String = "outerLoop: {0} innerLoop: {1}
	"Dim result As String = ""
	Dim outerLoop As Integer

	For outerLoop = 1 To 2
	   For innerLoop As Integer = 1 To 2
	      result &= String.Format(formatString, _
	         outerLoop, innerLoop)
	      result &= vbNewLine
	   Next innerLoop
	Next outerLoop

	MsgBox(result)

These two loops are nearly the same. Their counter variable values are displayed each time through the inner loop, as shown in Figure 6-9. The variable outerLoop can be referenced past the end of the sample lines of code, but referencing innerLoop will causes an exception. innerLoop exists only within the For…Next loop where it is declared.

The results of our nested loops using two different counter declaration methods
Figure 6-9. The results of our nested loops using two different counter declaration methods

See Also

See “For…Next statements” in Visual Studio Help for more information.

6.10. Converting Between Radians and Degrees

Problem

You want a simple, consistent, easy-to-read, and easy-to-use way to convert angles between radians and degrees.

Solution

Define two constants, RadPerDeg and DegPerRad, and multiply by degrees or radians, respectively, to convert to the other units.

Discussion

You can create standalone functions to perform these conversions, but these constants are straightforward definitions, and your code will compile to inline conversions that are compact and fast. The following code defines the constants and uses them to convert a few sample angular values. It’s generally best to define your constants at the top of your source-code files or in a global module, but here they are shown close to the code where they are used for easy reference:

	Const RadPerDeg As Double = Math.PI / 180#
	Const DegPerRad As Double = 180# / Math.PI

	Dim radians As Double
	Dim degrees As Double

	radians = Math.PI / 4#
	degrees = radians * DegPerRad
	radians = degrees * RadPerDeg

	MsgBox("Radians: " & radians.ToString & _
	vbNewLine & "Degrees: " & degrees.ToString)

This code rather redundantly converts radians to degrees and then immediately converts degrees right back to radians. You wouldn’t want to do this normally, but it shows both conversions side by side for easy comparison.

Figure 6-10 shows the same angle (45 degrees, or π/4 radians) expressed in the calculated units after conversion using the constants.

Using the RadPerDeg and DegPerRad constants to convert between degrees and radians
Figure 6-10. Using the RadPerDeg and DegPerRad constants to convert between degrees and radians

Both constants are defined using a division calculation. The Visual Basic 2005 compiler converts this math statement to a single constant by doing the division at compile time rather than at runtime, so there is no inefficiency in expressing the constants this way. The value of π is defined as a constant in the Math object with full double-precision accuracy, so the constants defined here are also accurate with Double values.

See Also

See “Derived Math Functions” in Visual Studio Help for additional derived functions, many of which assume radian units.

6.11. Limiting Angles to a Range

Problem

You want to shift intermediate angular calculation results into a range such as 0° to 360°, −180° to 180°, 0 to 2π radians, or –π to π radians.

Solution

Sample code folder: Chapter 06AngleRange

Create a function that handles all these range conversions efficiently.

Discussion

Some scientific calculations produce angular results that are beyond normal ranges, requiring adjustment to bring them into the standard range of values. For example, in astronomical calculations a variety of polynomials are used to compute highly accurate positions of the planets and stars, but the polynomials often return angles representing many revolutions of the various orbs. You might say the angles are astronomical in size before they are adjusted into a normalized range such as 0° to 360°. The following function handles these range adjustments efficiently, bringing the values back down to earth:

	Public Function FixRange(ByVal origValue As Double, _
	      ByVal rangeMin As Double, ByVal rangeMax As Double) _
	      As Double
	   ' ----- Adjust a value to within a specified range.
	   '       Use the range size as the adjustment factor.
	   Dim shiftedValue As Double
	   Dim delta As Double
	
	   shiftedValue = origValue - rangeMin
	   delta = rangeMax - rangeMin
	   Return (((shiftedValue  
Mod delta) + delta) Mod delta) + _
	      rangeMin
	End Function

The FixRange() function accepts an out-of-range angular value expressed in either degrees or radians (or any range-limited system), followed by the minimum and maximum limits of the desired normalized range. All three parameters must use the same measurement system, such as radians, for the results to make sense.

The function uses a double application of the Mod operator plus some additions and subtractions to bring the value into the desired range. This calculation is more straightforward and efficient than adding or subtracting values in a loop until the value is brought into range, which is the technique sometimes shown in astronomical calculation books.

The following code demonstrates the use of the Range() function on a variety of positive and negative angular values as they are brought into a number of desired ranges:

	Dim result As New System.Text.StringBuilder
	Dim formatDegrees As String = _
	   "Degrees: {0} Range: {1},{2} Value: {3}"
	Dim formatRadians As String = _
	   "Radians: {0} Range: {1},{2} Value: {3}"
	Dim degrees As Double
	Dim radians As Double
	Dim ranged As Double
	
	' ----- Degrees over the range.
	degrees = 367.75
	ranged = FixRange(degrees, 0, 360)
	result.AppendLine(String.Format(formatDegrees, _
	   degrees, 0, 360, ranged))

	' ----- Degress under the range.
	degrees = -97.5
	ranged = FixRange(degrees, 0, 360)
	result.AppendLine(String.Format(formatDegrees, _
	   degrees, 0, 360, ranged))
	
	' ----- Degrees in range.
	degrees = -97.5
	ranged = FixRange(degrees, -180, 180)
	result.AppendLine(String.Format(formatDegrees, _
	   degrees, -180, 180, ranged))

	' ----- Radians over the range.
	radians = Math.PI * 3.33
	ranged = FixRange(radians, -Math.PI, Math.PI)
	result.AppendLine(String.Format(formatRadians, _
	   radians, -Math.PI, Math.PI, ranged))

	MsgBox(result.ToString())

Figure 6-11 shows the results produced by this sample code.

Using the Range( ) function to normalize angles in degrees or radians
Figure 6-11. Using the Range( ) function to normalize angles in degrees or radians

See Also

Search for information on the Mod operator in Visual Studio Help.

6.12. Creating Double-Precision Point Variables

Problem

The PointF structure used in many graphics and other methods is defined to hold single-precision X and Y values, but you need greater precision.

Solution

Sample code folder: Chapter 06DoublePoint

Create your own Point2D class with double-precision X and Y values.

Discussion

The following simple class provides a blueprint for creating Point2D objects containing double-precision X and Y values:

	Public Class Point2D
	   Public X As Double
	   Public Y As Double

	   Public Sub New(ByVal xPoint As Double, _
	         ByVal yPoint As Double)
	      Me.X = xPoint
	      Me.Y = yPoint
	   End Sub
	
	   Public Overrides Function  
Tostring() As String
	      Return "{X=" & X & ",Y=" & Y & "}"
	   End Function
	End Class

As shown in the sample class code, the ToString() function overrides the default ToString() and returns a string formatted in a way that’s similar to the PointF class in the .NET Framework.

The following code demonstrates the creation of both the PointF and new Point2D objects. Both types of objects have the same “look and feel” in that they allow access directly to the X and Y values, they both can be populated with a pair of X, Y values at the moment of creation, and they both return similar strings via their respective ToString() functions:

	Dim result As New System.Text.StringBuilder

	' ----- Original PointF version.
	Dim singlePoint As New PointF(1 / 17, Math.PI)
	result.AppendLine("PointF: " & singlePoint.ToString()
	result.AppendLine("X: " & singlePoint.X)
	result.AppendLine()
	
	' ----- New Point2D version.
	Dim doublePoint As New Point2D(1 / 17, Math.PI)
	result.AppendLine("Point2D: " & doublePoint.ToString())
	result.AppendLine("X: " & doublePoint.X)
	result.AppendLine()

	MsgBox(result.ToString())

Figure 6-12 shows the results displayed by the message box in this sample code.

Point2D objects have double the precision of PointF objects
Figure 6-12. Point2D objects have double the precision of PointF objects

See Also

See “Graphics” in Visual Studio Help for more information about the use of two-dimensional points.

6.13. Converting Between Rectangular and Polar Coordinates

Problem

You want to convert between two-dimensional coordinates expressed in either rectangular or polar notation.

Solution

Sample code folder: Chapter 06ConvertPolar

Create two functions for the two conversions: ToPolar() and ToRectangular().

Discussion

The PointF structure provides a natural way to handle two-dimensional coordinates because each X, Y pair is handled as a single unit. A straightforward way to handle conversions between coordinates expressed in either rectangular (X, Y) or polar (radius, radians) notation is to simply pass and return PointF objects. This requires you, the programmer, to keep track of the current notation of each PointF object, but this is generally easy to do. Here are the two functions for making the conversions:

	Public Function ToPolar(ByVal sourcePoint As PointF) _
	      As PointF
	   ' ----- Convert  
rectangular coordinates to polar.
	   Dim magnitude As Single
	   Dim radians As Single
	   
	   magnitude = CSng(Math.Sqrt(sourcePoint.X ^ 2 + _
	      sourcePoint.Y ^ 2))
	   radians = CSng(Math.Atan2(sourcePoint.Y, sourcePoint.X))
	   Return New PointF(magnitude, radians)
	End Function

	Public Function ToRectangular(ByVal sourcePoint As PointF) _
	      As PointF
	   ' ----- Convert polar coordinates to rectangular.
	   Dim X As Single
	   Dim Y As Single

	   X = CSng(sourcePoint.X * Math.Cos(sourcePoint.Y))
	   Y = CSng(sourcePoint.X * Math.Sin(sourcePoint.Y))
	   Return New PointF(X, Y)
	End Function

Both functions assume angles will be expressed in radians, which is consistent with the way angles are expressed in Visual Basic. You can convert angles to and from degrees using the constants presented in Recipe 6.10.

The following block of code demonstrates the use of the ToPolar() and ToRectangular() functions:

	Dim result As New System.Text.StringBuilder
	Dim pointA As PointF
	Dim pointB As PointF
	Dim pointC As PointF

	pointA = New PointF(3, 4)
	pointB = ToPolar(pointA)
	pointC = ToRectangular(pointB)

	result.AppendLine("Rectangular: " & pointA.ToString())
	result.AppendLine("Polar: " & pointB.ToString())
	result.AppendLine("Rectangular: " & pointC.ToString())
	MsgBox(result.ToString())

The ToString() function presents the X and Y values of the PointF data using “X=” and “Y=” labels, which can be misleading when the PointF is holding a coordinate in polar mode. Be sure to keep track of the state of the data as you work with it.

Figure 6-13 shows the formatted string results of the ToRectangular() and ToPolar() functions in action.

Rectangular and polar two-dimensional coordinate conversions using PointF variables
Figure 6-13. Rectangular and polar two-dimensional coordinate conversions using PointF variables

See Also

Searching for " polar rectangular” on the Web will lead you to a variety of explanations and learning materials about this subject.

6.14. Creating Three-Dimensional Variables

Problem

You want to work with three-dimensional coordinates as single entities.

Solution

Sample code folder: Chapter 06ThreePoint

Create a Point3D class that works like the PointF class except that it contains a Z property in addition to X and Y.

Discussion

The following class definition is similar to the Point2D class presented in Recipe 6.12:

	Public Class Point3D
	   Public X As Double
	   Public Y As Double
	   Public Z As Double
	
	   Public Sub  
New(ByVal xPoint As Double, _
	         ByVal yPoint As Double, ByVal zPoint As Double)
	      Me.X = xPoint
	      Me.Y = yPoint
	      Me.Z = zPoint
	   End Sub

	   Public Overrides Function Tostring() As String
	      Return "{X=" & X & ",Y=" & Y & ",Z=" & Z & "}"
	   End Function
	End Class

The most important modification is the addition of a public Z value for the third dimension. As presented here, the X, Y, and Z properties are all Double precision, but you can easily redefine these to Single if that provides sufficient precision for your calculations, and if you want to save memory when you create large arrays of this data type.

The following code demonstrates the use of some Point3D objects. Notice how the New() function lets you create a Point3D variable with nonzero X, Y, and Z values:

	Dim result As New System.Text.StringBuilder
	Dim distance As Double
	Dim point1 As Point3D
	Dim point2 As Point3D
	Dim deltaX As Double

	Dim deltaY As Double
	Dim deltaZ As Double

	point1 = New Point3D(3, 4, 5)
	point2 = New Point3D(7, 2, 3)
	deltaX = point1.X - point2.X
	deltaY = point1.Y - point2.Y
	deltaZ = point1.Z - point2.Z
	distance = Math.Sqrt(deltaX ^ 2 + deltaY ^ 2 + deltaZ ^ 2)
	
	result.AppendLine("3D Point 1: " & point1.ToString())
	result.AppendLine("3D Point 2: " & point2.ToString())
	result.AppendLine("Distance: " & distance.ToString())

	MsgBox(result.ToString())

Figure 6-14 shows the results of calculating the distance in space between these two coordinates.

Manipulating three-dimensional coordinates with a Point3D class
Figure 6-14. Manipulating three-dimensional coordinates with a Point3D class

See Also

Search for “basic 3D math” on the Web for a variety of explanations and further information about this subject.

6.15. Converting Between Rectangular, Spherical, and Cylindrical Coordinates

Problem

You need to convert three-dimensional coordinates between rectangular, spherical, and cylindrical notation.

Solution

Sample code folder: Chapter 06Convert3D

Create a set of six functions to convert Point3D variables to and from each coordinate notation.

Discussion

The following six functions convert from any one of the three types of three-dimensional coordinates to any of the others. All these functions accept a Point3D argument and return a Point3D value. It is up to you to keep track of the current type of coordinate notation in each Point3D variable. Note that in all cases the Point3D value passed in to any of these functions is not altered; a new Point3D instance is returned instead. Here are the six functions:

	Public Function RectToCylinder(ByVal pointA As Point3D) _
	      As Point3D
	   ' ----- Convert  
rectangular 3D coordinates to
	   '       cylindrical coordinates.
	   Dim rho As Double
	   Dim theta As Double
	
	   rho = Math.Sqrt(pointA.X ^ 2 + pointA.Y ^ 2)
	   theta = Math.Atan2(pointA.Y, pointA.X)
	   Return New Point3D(rho, theta, pointA.Z)
	End Function

	Public Function CylinderToRect(ByVal pointA As Point3D) _
	      As Point3D
	   ' ----- Convert cylindrical coordinates to
	   '        
rectangular 3D coordinates.
	   Dim x As Double
	   Dim y As Double

	   x = pointA.X * Math.Cos(pointA.Y)
	   y = pointA.X * Math.Sin(pointA.Y)
	   Return New Point3D(x, y, pointA.Z)
	End Function

	Public Function RectToSphere(ByVal pointA As Point3D) _
	      As Point3D
	   ' ----- Convert rectangular 3D coordinates to
	   '        
spherical coordinates.
	   Dim rho As Double
	   Dim theta As Double
	   Dim phi As Double
	
	   rho = Math.Sqrt(pointA.X ^ 2 + pointA.Y ^ 2 + _
	      pointA.Z ^ 2)
	   theta = Math.Atan2(pointA.Y, pointA.X)
	   phi = Math.Acos(pointA.Z / Math.Sqrt( _
	      pointA.X ^ 2 + pointA.Y ^ 2 + pointA.Z ^ 2))
	   Return New Point3D(rho, theta, phi)
	End Function

	Public Function SphereToRect(ByVal pointA As Point3D) _
	      As Point3D
	   ' ----- Convert spherical coordinates to
	   '       rectangular 3D coordinates.
	   Dim x As Double
	   Dim y As Double
	   Dim z As Double

	   x = pointA.X * Math.Cos(pointA.Y) * Math.Sin(pointA.Z)
	   y = pointA.X * Math.Sin(pointA.Y) * Math.Sin(pointA.Z)
	   z = pointA.X * Math.Cos(pointA.Z)
	   Return New Point3D(x, y, z)
	End Function

	Public Function CylinderToSphere(ByVal pointA As Point3D) _
	      As Point3D
	   ' ----- Convert cylindrical  
coordinates to
	   '        
spherical coordinates.
	   Dim rho As Double
	   Dim theta As Double
	   Dim phi As Double

	   rho = Math.Sqrt(pointA.X ^ 2 + pointA.Z ^ 2)
	   theta = pointA.Y
	   phi = Math.Acos(pointA.Z / _
	      Math.Sqrt(pointA.X ^ 2 + pointA.Z ^ 2))
	   Return New Point3D(rho, theta, phi)
	End Function

	Public Function SphereToCylinder(ByVal pointA As Point3D) _
	      As Point3D
	   ' ----- Convert spherical coordinates to
	   '       cylindrical coordinates.
	   Dim rho As Double
	   Dim theta As Double
	   Dim z As Double
	
	   rho = pointA.X * Math.Sin(pointA.Z)
	   theta = pointA.Y
	   z = pointA.X * Math.Cos(pointA.Z)
	   Return New Point3D(rho, theta, z)
	End Function

The following code creates several Point3D variables using names that indicate the types of coordinates they contain. For example, pointCyl is a Point3D variable containing three-dimensional cylindrical coordinates. The various conversion functions are used to populate the variables, and the results are shown in Figure 6-15:

	Dim result As New System.Text.StringBuilder
	Dim pointRec As New Point3D(3, 4, 5)
	Dim pointCyl As Point3D = RectToCylinder(pointRec)
	Dim pointSph As Point3D = RectToSphere(pointRec)
	Dim pointRecToCyl As Point3D = RectToCylinder(pointRec)
	Dim pointRecToSph As Point3D = RectToSphere(pointRec)
	Dim pointCylToRec As Point3D = CylinderToRect(pointCyl)
	Dim pointCylToSph As Point3D = CylinderToSphere(pointCyl)
	Dim pointSphToRec As Point3D = SphereToRect(pointSph)
	Dim pointSphToCyl As Point3D = SphereToCylinder(pointSph)
	
	result.AppendLine("Rec: " & pointRec.ToString())
	result.AppendLine("Cyl: " & pointCyl.ToString())
	result.AppendLine("Sph: " & pointSph.ToString())
	result.AppendLine()
	
	result.AppendLine("Rec to Cyl: " & pointRecToCyl.ToString())
	result.AppendLine("Rec to Sph: " & pointRecToSph.ToString())
	result.AppendLine("Cyl to Rec: " & pointCylToRec.ToString())
	result.AppendLine("Cyl to Sph: " & pointCylToSph.ToString())
	result.AppendLine("Sph to Rec: " & pointSphToRec.ToString())
	result.AppendLine("Sph to Cyl: " & pointSphToCyl.ToString())

	MsgBox(result.ToString())
Converting Point3D variables between three different types of spatial coordinates
Figure 6-15. Converting Point3D variables between three different types of spatial coordinates

See Also

Search for " rectangular cylindrical spherical” on the Web for a variety of explanations and further information about this subject.

6.16. Working with Complex Numbers

Problem

You want an easy way to calculate with complex numbers.

Solution

Sample code folder: Chapter 06ComplexNumbers

Create a ComplexNumber structure. Overload the standard mathematical operators so that using complex number variables is easy and natural.

Discussion

This recipe provides a great way to see how overloading standard operators can enhance the usability of your classes and structures. In this case, we’ve created a ComplexNumber structure. Structures are similar to classes, except that they exist as value types rather than reference types. This allows complex number instances to act the same as other simple variables, such as standard numerical variables.

The following code defines the ComplexNumber number structure. Place this code in its own file named ComplexNumber.vb for easy inclusion in any application that requires complex numbers:

	Structure ComplexNumber
	   Public Real As Double
	   Public Imaginary As Double
	   
	   Public Sub New(ByVal realPart As Double, _
	         ByVal imaginaryPart As Double)
	      Me.Real = realPart
	      Me.Imaginary = imaginaryPart
	   End Sub

	   Public Sub New(ByVal sourceNumber As ComplexNumber)
	      Me.Real = sourceNumber.Real
	      Me.Imaginary = sourceNumber.Imaginary
	   End Sub

	   Public Overrides Function ToString() As String
	      Return Real & "+" & Imaginary & "i"
	   End Function

	   Public Shared Operator +(ByVal a As ComplexNumber, _
	         ByVal b As ComplexNumber) As ComplexNumber
	      ' ----- Add two  
complex numbers together.
	      Return New ComplexNumber(a.Real + b.Real, _
	         a.Imaginary + b.Imaginary)
	   End Operator

	   Public Shared Operator -(ByVal a As ComplexNumber, _
	         ByVal b As ComplexNumber) As ComplexNumber
	      ' ----- Subtract one complex number from another.
	      Return New ComplexNumber(a.Real - b.Real, _
	         a.Imaginary - b.Imaginary)
	   End Operator

	   Public Shared Operator *(ByVal a As ComplexNumber, _
 	         ByVal b As ComplexNumber) As ComplexNumber
	      ' ----- Multiply two complex numbers together.
	      Return New ComplexNumber(a.Real * b.Real - _
	         a.Imaginary * b.Imaginary, _
	         a.Real * b.Imaginary + a.Imaginary * b.Real)
	   End Operator
	  
	   Public Shared Operator /(ByVal a As ComplexNumber, _
	         ByVal b As ComplexNumber) As ComplexNumber
	      ' ----- Divide one complex number by another.
	      Return a * Reciprocal(b)
	   End Operator

	   Public Shared Function Reciprocal( _
	         ByVal a As ComplexNumber) As ComplexNumber
	      ' ----- Calculate the reciprocal of a complex number;
	      '       that is, the 1/x calculation.
	      Dim divisor As Double
	  
	      ' ----- Check for divide-by-zero possibility.
	      divisor = a.Real * a.Real + a.Imaginary * a.Imaginary
	      If (divisor = 0.0#) Then Throw New DivideByZeroException
	  
	      ' ----- Perform the operation.
	      Return New ComplexNumber(a.Real / divisor, _
	         -a.Imaginary / divisor)
	   End Function
	End Structure

The overloaded New() function lets you instantiate a ComplexNumber number using either a pair of numbers (the real and imaginary parts) or another ComplexNumber number.

The following code demonstrates how complex numbers are created and how standard operators allow mathematical operations such as addition and subtraction in a natural way. The overloaded + operator also impacts the += assignment operator. The last example in the code demonstrates this by adding complex number b to complex number a using the new assignment-operator syntax:

	Dim result As New System.Text.StringBuilder
	Dim a As ComplexNumber
	Dim b As ComplexNumber
	Dim c As ComplexNumber
	
	a = New ComplexNumber(3, 4)
	b = New ComplexNumber(5, -2)
	c = a + b

	result.AppendLine(" 
Complex Numbers")
	result.AppendLine("a = " & a.ToString())
	result.AppendLine("b = " & b.ToString())

	' ----- Addition.
	c = a + b
	result.AppendLine("a + b = " & c.ToString())

	' ----- Subtraction.
	c = a - b
	result.AppendLine("a - b = " & c.ToString())

	' ----- Multiplication.
	c = a * b
	result.AppendLine("a * b = " & c.ToString())

	' ----- Division.
	c = a / b
	result.AppendLine("a / b = " & c.ToString())

	' ----- Addition as assignment.
	a += b
	result.AppendLine("a += b … a = " & a.ToString())

	MsgBox(result.ToString())

The ToString() function is overridden in the ComplexNumber structure to format the real and imaginary parts. Figure 6-16 shows the output from the sample code.

Working with complex numbers in VB 2005
Figure 6-16. Working with complex numbers in VB 2005

See Also

Search for " complex numbers” on the Web for more information on this subject.

6.17. Solving Right Triangles

Problem

You want to calculate all the remaining sides and angles of a right triangle given two known parts of the triangle.

Solution

Sample code folder: Chapter 06RightTriangle

Create a RightTriangle class that calculates all parts of a right triangle given any two of its parts.

Discussion

The parts of a right triangle we are concerned with are the two sides A and B adjacent to the right angle, the hypotenuse (the side opposite the right angle), and the two angles formed where the hypotenuse meets sides A and B. If you know any two of these values, all the rest can be determined.

There are many ways to set up the RightTriangle class, and the technique chosen here is not the only reasonable approach to the problem. We chose to use the initializing function New() to define the triangle by passing in nonzero numbers for the known parts and a value of zero for the unknowns. The IntelliSense pop-up prompt makes it easy to remember what parts of the triangle are passed in at each parameter position. It’s as easy as filling in the blanks. The code for the RightTriangle class is as follows:

	Public Class RightTriangle
	   Private StoredSideA As Double
	   Private StoredSideB As Double
	   Private StoredHypotenuse As Double
	   Private StoredAngleA As Double
	   Private StoredAngleB As Double
	
	   Public Sub New(ByVal hypotenuse As Double, _
	         ByVal sideA As Double, ByVal sideB As Double, _
	         ByVal angleA As Double, ByVal angleB As Double)
	      Me.StoredHypotenuse = hypotenuse
	      Me.StoredSideA = sideA
	      Me.StoredSideB = sideB
	      Me.StoredAngleA = angleA
	      Me.StoredAngleB = angleB
	      Me.Resolve()
	   End Sub

	   Public ReadOnly Property SideA() As Double
	      Get
	         Return StoredSideA
	      End Get
	   End Property

	   Public ReadOnly Property SideB() As Double
	      Get
	         Return StoredSideB
	      End Get
	   End Property

	   Public ReadOnly Property AngleA() As Double
	      Get
	         Return StoredAngleA
	      End Get
	   End Property

	   Public ReadOnly Property AngleB() As Double
	      Get
	         Return StoredAngleB
	      End Get
	   End Property

	   Public ReadOnly Property Hypotenuse() As Double
	      Get
	         Return StoredHypotenuse
	      End Get
	   End Property

	   Private Sub Resolve()
	      ' ----- Figure out the missing (zero) parts of the
	      '       triangle. Start with the angles.
	      If (StoredAngleA = 0.0#) And _
	         (StoredAngleB <> 0.0#) Then _
	         StoredAngleA = Math.PI / 2 - StoredAngleB
	      If (StoredAngleB = 0.0#) And _
	         (StoredAngleA <> 0.0#) Then _
	         StoredAngleB = Math.PI / 2 - StoredAngleA
	      If (StoredAngleA <> 0.0#) And _
	         (StoredHypotenuse <> 0.0#) Then _
	         StoredSideB = StoredHypotenuse * _
	         Math.Cos(StoredAngleA)
	      If (StoredAngleB <> 0.0#) And _
	         (StoredHypotenuse <> 0.0#) Then _
	         StoredSideA = StoredHypotenuse * _
	         Math.Cos(StoredAngleB)
	      If (StoredAngleA <> 0.0#) And _
	         (StoredSideA <> 0.0#) Then _
	         StoredHypotenuse = StoredSideA / _
	         Math.Sin(StoredAngleA)
	      If (StoredAngleB <> 0.0#) And _
	         (StoredSideB <> 0.0#) Then _
	         StoredHypotenuse = StoredSideB / _
	         Math.Sin(StoredAngleB)
	      If (StoredAngleA <> 0.0#) And _
	         (StoredSideB <> 0.0#) Then _
	         StoredHypotenuse = StoredSideB / _
	         Math.Cos(StoredAngleA)
	      If (StoredAngleB <> 0.0#) And _
	         (StoredSideA <> 0.0#) Then _
	         StoredHypotenuse = StoredSideA / _
	         Math.Cos(StoredAngleB)

	      ' ----- Now calculate the sides.
	      If (StoredSideA <> 0.0#) And _
	         (StoredSideB <> 0.0#) Then _
	         StoredHypotenuse = Math.Sqrt(StoredSideA ^ 2 + _
	         StoredSideB ^ 2)
	      If (StoredSideA <> 0.0#) And _
	         (StoredHypotenuse <> 0.0#) Then _
	         StoredSideB = Math.Sqrt(StoredHypotenuse ^ 2 - _
	         StoredSideA ^ 2)
	      If (StoredSideB <> 0.0#) And _
	         (StoredHypotenuse <> 0.0#) Then _
	         StoredSideA = Math.Sqrt(StoredHypotenuse ^ 2 - _
	         StoredSideB ^ 2)
	      If (StoredAngleA = 0.0#) Then StoredAngleA = _
	         Math.Asin(StoredSideA / StoredHypotenuse)
	      If (StoredAngleB = 0.0#) Then StoredAngleB = _
	         Math.Asin(StoredSideB / StoredHypotenuse)
	   End Sub

	   Public Overrides Function Tostring() As String
	      ' ----- Display all values of the triangle.
	      Dim result As New System.Text.StringBuilder

	      result.AppendLine(" 
Right Triangle:")
	      result.AppendLine("Hypotenuse=" & _
	          StoredHypotenuse.ToString)
	      result.AppendLine("Side A=" & StoredSideA.ToString)
	      result.AppendLine("Side B=" & StoredSideB.ToString)
	      result.AppendLine("Angle A=" & StoredAngleA.ToString)
	      result.Append("Angle B=" & StoredAngleB.ToString)
	      Return result.ToString()
	   End Function
	End Class

The core calculations of this class are performed in the private Resolve() function. There, the various triangle parts are tested to see if they are nonzero, and the appropriate calculations are performed to start filling in the blanks for the unknowns. Resolve() is called just once, at the moment when the RightTriangle object is instantiated. All the parts of the right triangle are later returned as required via read-only properties.

Visual Basic internally always assumes angles to be in radians, even though degrees are the most commonly used units for angles among the general population. It’s tempting to use degrees in user-defined classes and procedures, but for consistency this book will assume radians throughout.

The following sample code creates an instance of the RightTriangle object and uses it to calculate a typical right triangle. In this example, the lengths of sides A and B are known. All other parts of the triangle are passed as zero when the RightTriangle is instantiated:

	Dim testTriangle As RightTriangle
	Dim area As Double

	testTriangle = New RightTriangle(0, 3, 4, 0, 0)
	area = (testTriangle.SideA * testTriangle.SideB) / 2
	MsgBox(testTriangle.Tostring & vbNewLine & _
	   "Area = " & area.ToString)

Figure 6-17 shows the results of calculating the missing parts of a right triangle with sides A and B of lengths 3 and 4.

Using the RightTriangle class to calculate unknown parts of a right triangle
Figure 6-17. Using the RightTriangle class to calculate unknown parts of a right triangle

See Also

Search for “right triangle” on the Web for more information about this subject (see, for example, http://mathworld.wolfram.com/RightTriangle.html).

6.18. Solving Any Triangle

Problem

You want to solve any triangle given any three known parts. Examples might include the lengths of any two sides and the measure of the angle between them, or the measures of two angles and the length of the side between them.

Solution

Sample code folder: Chapter 06AnyTriangle

Create a Triangle class to handle the details of calculating all the remaining parts of a triangle given any combination of three of its parts. Also create a separate utility function to calculate any triangle’s area given the lengths of its three sides.

Discussion

The Triangle class, presented below, allows the remaining elements of any triangle to be calculated given the measures of any three of its sides and angles. The only combination that won’t work, of course, is when three angles are given, as these pin down the shape of a triangle but not its size. Here is the code for the Triangle class:

	Imports System.Math

	Public Class Triangle
	   Private StoredSideA As Double
	   Private StoredSideB As Double
	   Private StoredSideC As Double
	   Private StoredAngleA As Double
	   Private StoredAngleB As Double
	   Private StoredAngleC As Double

	   ' ----- The GivenParts variable indicates which parts
	   '       the user has already supplied. Uppercase letters
	   '       (A, B, C) indicate sides; lowercase letters
	   '       (a, b, c) are angles.
	   Private GivenParts As String = ""

	   Public Overrides Function ToString() As String
	      ' ----- Show the details of the triangle.
	      Return String.Format( _
	         "SideA={0}, SideB={1}, SideC={2}, " & _
	         "AngleA={3}, AngleB={4}, AngleC={5}", _
	         StoredSideA, StoredSideB, StoredSideC, _
	         StoredAngleA, StoredAngleB, StoredAngleC)
	   End Function

	   Public Property SideA() As Double
	      Get
	         If (GivenParts.Length >= 3) Then _
	            Return StoredSideA Else NotYet()
	      End Get
	      Set(ByVal Value As Double)
	         If (Value < 0) Then _
	            Throw New ArgumentOutOfRangeException( _
	            "Negative side length (A) not allowed.")
	         CheckIt("A")
	         StoredSideA = Value
	         Resolve()
	      End Set
	   End Property

	   Public Property SideB() As Double
	      Get
	         If (GivenParts.Length >= 3) Then _
	            Return StoredSideB Else NotYet()
	      End Get
	      Set(ByVal Value As Double)
	         If (Value < 0) Then _
	            Throw New ArgumentOutOfRangeException( _
	            "Negative side length (B) not allowed.")
	         CheckIt("B")
	         StoredSideB = Value
	         Resolve()
	      End Set
	   End Property
	
	   Public Property SideC() As Double
	      Get
	         If (GivenParts.Length >= 3) Then _
	            Return StoredSideC Else NotYet()
	      End Get
	      Set(ByVal Value As Double)

	         If (Value < 0) Then _
	            Throw New ArgumentOutOfRangeException( _
	            "Negative side length (C) not allowed.")
	         CheckIt("C")
	         StoredSideC = Value
	         Resolve()
	      End Set
	   End Property

	   Public Property AngleA() As Double
	      Get
	         If (GivenParts.Length >= 3) Then _
	            Return StoredAngleA Else NotYet()
	      End Get
	      Set(ByVal Value As Double)
	         If (Value < 0) Or (Value > Math.PI) Then _
	            Throw New Exception( _
	            "Angle (A) must range from 0 to PI.")
	         CheckIt("a")
	         StoredAngleA = Value
	         Resolve()
	      End Set
	   End Property

	   Public Property AngleB() As Double
	      Get
	         If (GivenParts.Length >= 3) Then _
	            Return StoredAngleB Else NotYet()
	      End Get
	      Set(ByVal Value As Double)
	         If (Value < 0) Or (Value > Math.PI) Then _
	            Throw New Exception( _
	            "Angle (B) must range from 0 to PI.")
	         CheckIt("b")
	         StoredAngleB = Value
	         Resolve()
	      End Set
	   End Property

	   Public Property AngleC() As Double
	      Get
	         If (GivenParts.Length >= 3) Then _
	            Return StoredAngleC Else NotYet()
	      End Get
	      Set(ByVal Value As Double)
	         If (Value < 0) Or (Value > Math.PI) Then _
	            Throw New Exception( _
	            "Angle (C) must range from 0 to PI.")
	         CheckIt("c")
	         StoredAngleC = Value
	         Resolve()
	      End Set
	   End Property

	   Private Sub CheckIt(ByVal whatToCheck As String)
	      ' ----- Make sure it is OK to adjust a component.
	      If (GivenParts.Length >= 3) Then Throw New Exception( _
	         "Triangle is immutable once defined by three parts.")
	      If (GivenParts.IndexOf(whatToCheck) >= 0) Then _
	         Throw New Exception( _
	         "Triangle component cannot be modified once set.")

	      ' ---- Mark this part as modified.
	      GivenParts &= whatToCheck
	   End Sub

	   Private Sub NotYet()
	      ' ----- The user tried to access components before
	      '       anything was calculated.
	      Throw New Exception( _
	         "Triangle has not yet been completely defined.")
	   End Sub

	   Private Sub Resolve()
	      ' ----- Calculate the missing angles and sides of
	      '       the triangle.
	      Dim sinRatio As Double
	      Dim inSort() As Char

	      ' ----- Wait for the triangle to be completely defined.
	      If (GivenParts.Length < 3) Then Return

	      ' ----- Sort the known parts list.
	      inSort = GivenParts.ToCharArray()
	      Array.Sort(inSort)
	      GivenParts = New String(inSort)

	      ' ----- Time to resolve. In all cases, the goal is to
	      '       get three known sides. Then, the ResolveABC()
	      '       method can work on getting the missing angles.
	      Select Case GivenParts
	         Case "ABC"
	            ResolveABC()
	         Case "ABa"
	            sinRatio = Sin(StoredAngleA) / StoredSideA
	            StoredAngleB = Asin(StoredSideB * sinRatio)
	            StoredAngleC = PI - StoredAngleA - StoredAngleB
	            StoredSideC = Sin(StoredAngleC) / sinRatio

	         Case "ABb"
	            sinRatio = Sin(StoredAngleB) / StoredSideB
	            StoredAngleA = Asin(StoredSideA * sinRatio)
	            StoredAngleC = PI - StoredAngleA - StoredAngleB
	            StoredSideC = Sin(StoredAngleC) / sinRatio
	         Case "ABc"
	            StoredSideC = Sqrt(StoredSideA ^ 2 + _
	               StoredSideB ^ 2 - 2 * StoredSideA * _
	               StoredSideB * Cos(StoredAngleC))
	         Case "ACa"
	            sinRatio = Sin(StoredAngleA) / StoredSideA
	            StoredAngleC = Asin(StoredSideC * sinRatio)
	            StoredAngleB = PI - StoredAngleA - StoredAngleC
	            StoredSideB = Sin(StoredAngleB) / sinRatio
	         Case "ACb"
	            StoredSideB = Sqrt(StoredSideA ^ 2 + _
	               StoredSideC ^ 2 - 2 * StoredSideA * _
	               StoredSideC * Cos(StoredAngleB))
	         Case "ACc"
	            sinRatio = Sin(StoredAngleC) / StoredSideC
	            StoredAngleA = Asin(StoredSideA * sinRatio)
	            StoredAngleB = PI - StoredAngleA - StoredAngleC
	            StoredSideB = Sin(StoredAngleB) / sinRatio
	         Case "Aab"
	            sinRatio = Sin(StoredAngleA) / StoredSideA
	            StoredSideB = Sin(StoredAngleB) / sinRatio
	            StoredAngleC = PI - StoredAngleA - StoredAngleB
	            StoredSideC = Sin(StoredAngleC) / sinRatio
	         Case "Aac"
	            sinRatio = Sin(StoredAngleA) / StoredSideA
	            StoredSideC = Sin(StoredAngleC) / sinRatio
	            StoredAngleB = PI - StoredAngleA - StoredAngleC
	            StoredSideB = Sin(StoredAngleB) / sinRatio
	         Case "Abc"
	            StoredAngleA = PI - StoredAngleB - StoredAngleC
	            sinRatio = Sin(StoredAngleA) / StoredSideA
	            StoredSideB = Sin(StoredAngleB) / sinRatio
	            StoredSideC = Sin(StoredAngleC) / sinRatio
	         Case "BCa"
	            StoredSideA = Sqrt(StoredSideB ^ 2 + _
	               StoredSideC ^ 2 - 2 * StoredSideB * _
	               StoredSideC * Cos(StoredAngleA))
	         Case "BCb"
	            sinRatio = Sin(StoredAngleB) / StoredSideB
	            StoredAngleC = Asin(StoredSideC * sinRatio)
	            StoredAngleA = PI - StoredAngleB - StoredAngleC
	            StoredSideA = Sin(StoredAngleA) / sinRatio
	         Case "BCc"
	            sinRatio = Sin(StoredAngleC) / StoredSideC
	            StoredAngleB = Asin(StoredSideB * sinRatio)
	            StoredAngleA = PI - StoredAngleB - StoredAngleC
	            StoredSideA = Sin(StoredAngleA) / sinRatio
	         Case "Bab"
	            StoredAngleC = PI - StoredAngleA - StoredAngleB
	            sinRatio = Sin(StoredAngleB) / StoredSideB
	            StoredSideA = Sin(StoredAngleA) / sinRatio
	            StoredSideC = Sin(StoredAngleC) / sinRatio
	         Case "Bac"
	            StoredAngleB = PI - StoredAngleA - StoredAngleC
	            sinRatio = Sin(StoredAngleB) / StoredSideB
	            StoredSideA = Sin(StoredAngleA) / sinRatio
	            StoredSideC = Sin(StoredAngleC) / sinRatio
	         Case "Bbc"
	            StoredAngleA = PI - StoredAngleB - StoredAngleC
	            sinRatio = Sin(StoredAngleB) / StoredSideB
	            StoredSideA = Sin(StoredAngleA) / sinRatio
	            StoredSideC = Sin(StoredAngleC) / sinRatio
	         Case "Cab"
	            StoredAngleC = PI - StoredAngleA - StoredAngleB
	            sinRatio = Sin(StoredAngleC) / StoredSideC
	            StoredSideA = Sin(StoredAngleA) / sinRatio
	            StoredSideB = Sin(StoredAngleB) / sinRatio
	         Case "Cac"
	            StoredAngleB = PI - StoredAngleA - StoredAngleC
	            sinRatio = Sin(StoredAngleC) / StoredSideC
	            StoredSideA = Sin(StoredAngleA) / sinRatio
	            StoredSideB = Sin(StoredAngleB) / sinRatio
	         Case "Cbc"
	            StoredAngleA = PI - StoredAngleB - StoredAngleC
	            sinRatio = Sin(StoredAngleC) / StoredSideC
	            StoredSideA = Sin(StoredAngleA) / sinRatio
	            StoredSideB = Sin(StoredAngleB) / sinRatio
	         Case "abc"
	            Throw New Exception("Cannot resolve " & _
	               "triangle with only angles specified.")
	         Case Else
	            Throw New Exception( _
	               "Undefined combination of triangle parts.")
	      End Select
	      ResolveABC()
	   End Sub
	  
	   Private Sub ResolveABC()
	      ' ----- All three sides are known. Calculate the angles.
	      LengthCheck(StoredSideA, StoredSideB, StoredSideC)
	      StoredAngleC = Acos((StoredSideA ^ 2 + _
	         StoredSideB ^ 2 - StoredSideC ^ 2) / _
	         (2 * StoredSideA * StoredSideB))
	      StoredAngleB = Acos((StoredSideA ^ 2 + _
	         StoredSideC ^ 2 - StoredSideB ^ 2) / _
	         (2 * StoredSideA * StoredSideC))
	      StoredAngleA = PI - StoredAngleB - StoredAngleC
	   End Sub

	   Private Sub LengthCheck(ByVal A As Double, _
	         ByVal B As Double, ByVal C As Double)
	      ' ----- Make sure that one of the sides isn't
	      '       too long for the other two.
	      If (A >= B) AndAlso (A >= C) AndAlso _
	         (A <= (B + C)) Then Return
	      If (B >= A) AndAlso (B >= C) AndAlso _
	         (B <= (A + C)) Then Return
	      If (C >= A) AndAlso (C >= B) AndAlso _
	         (C <= (A + B)) Then Return
	      Throw New Exception( _
	         "One side is too long for the others.")
	   End Sub
	End Class

Exceptions are thrown if the triangle “doesn’t make sense.” For example, if the sum of two sides is less than the length of the third, or if three angles are given, the triangle is impossible, or at least the data is insufficient to completely define the triangle.

To find the area of any triangle, you could include a shared function within the Triangle class, but for the sake of demonstration (and because it can be useful in a wider variety of computational situations) we’ve chosen to create a TriangleArea() function separate from the class. This makes it easy to find the area of any triangle given the lengths of its three sides, whether or not you’re solving triangles using the Triangle class:

	Public Function TriangleArea(ByVal sideA As Double, _
	      ByVal sideB As Double, _
	      ByVal sideC As Double) As Double
	   ' ----- Calculate the area of a triangle.
	   Dim sumHalfSides As Double
	   Dim deltaA As Double
	   Dim deltaB As Double
	   Dim deltaC As Double

	   sumHalfSides = (sideA + sideB + sideC) / 2
	   deltaA = sumHalfSides - sideA
	   deltaB = sumHalfSides - sideB
	   deltaC = sumHalfSides - sideC
	   Return Math.Sqrt(sumHalfSides * deltaA * deltaB * deltaC)
	End Function

The following code demonstrates the use of the Triangle class by solving for a triangle that has two sides of length 4 and 5, with a 75°; angle between the two sides. The RadPerDeg constant (see Recipe 6.10) converts 75°to radians at compile time rather than at runtime (to be consistent with all other angular measurements in Visual Basic 2005, radians are always assumed in all the procedures in this book that involve angles):

	Const RadPerDeg As Double = Math.PI / 180
	Dim testTriangle As New Triangle
	Dim area As Double

	' ----- Build a triangle with sides of 4 and 5, and an
	'       angle between them of 75 degrees.
	testTriangle.SideA = 4
	testTriangle.SideB = 5
	testTriangle.AngleC = 75 * RadPerDeg

	' ----- The triangle is already resolved. Calculate area.
	area = TriangleArea(testTriangle.SideA, _
	   testTriangle.SideB, testTriangle.SideC)

	MsgBox(testTriangle.ToString & vbNewLine & _
	   "Area = " & area.ToString)

A ToString() function is included in the Triangle class to provide a default format for presenting the triangle’s parts in a single string. The solved triangle for our example is shown in Figure 6-18.

Solving a triangle with the Triangle class
Figure 6-18. Solving a triangle with the Triangle class

6.19. Determining if a String Contains a Valid Number

Problem

You want to verify that a user-entered string contains a valid number.

Solution

Use Visual Basic’s IsNumeric() function to check the string.

Discussion

Visual Basic 2005 provides a function named IsNumeric() that checks the content of any string, returning a Boolean True if the string contains a valid number representation and False if it doesn’t:

	Dim result As New System.Text.StringBuilder
	Dim testString As String
	
	testString = "2.E3"
	result.Append(testString).Append(vbTab)
	result.AppendLine(IsNumeric(testString).ToString)

	testString = "2.D3"
	result.Append(testString).Append(vbTab)
	result.AppendLine(IsNumeric(testString).ToString)

	testString = "-123"
	result.Append(testString).Append(vbTab)
	result.AppendLine(IsNumeric(testString).ToString)

	testString = "-1 2 3"
	result.Append(testString).Append(vbTab)
	result.AppendLine(IsNumeric(testString).ToString)

	testString = "$54.32"
	result.Append(testString).Append(vbTab)
	result.AppendLine(IsNumeric(testString).ToString)

	MsgBox(result.ToString())

Currency values are valid numbers, even with the currency symbol included. The IsNumeric() function expects a single number in the string, so extra spaces, such as those shown in the next-to-last string in the example, cause IsNumeric() to return False. If you want to determine how many valid numbers are in a string, and be able to grab them all, consider using regular expressions instead.

Figure 6-19 shows the strings used in this example and the results returned by IsNumeric() for each.

Testing whether a string contains a valid number using IsNumeric( )
Figure 6-19. Testing whether a string contains a valid number using IsNumeric( )

6.20. Converting Numbers to Integers

Problem

You want to convert numbers to integers, or perhaps truncate or round values to integer values, and you want to understand the various ways to do this.

Solution

As always, use the best tool for the job. If you want to remove decimal parts of a number, consider using Int(), Floor(), or the Round() function. But if you want to convert a numeric value to an Integer data type, use CInt() or Convert.ToInteger() instead.

Discussion

The following code demonstrates differences between the CInt() and Int() functions. Once you gain a good understanding of these two functions, you’ll be well on your way to understanding similar functions such as Round(), Convert.ToInteger(), and so on.

One important difference between CInt() and Int() is that Int() is overloaded to work with a wide variety of numeric data types. For example, you can pass a Double, such as the value of π, to Int(), and it will return another Double value that no longer has any post-decimal digits (i.e., it will round to a whole number). This is entirely different from converting a number to an Integer. The Int() function works on numbers that are way out of the legal range for an Integer. Using CInt() on similar numbers would throw an exception.

The two functions are demonstrated in the following code:

	Dim result As New System.Text.StringBuilder
	Dim number As Double
	
	' ----- Positive decimal value.
	number = 3.14
	result.Append(number)
	result.Append("   CInt(): ")
	result.Append(CInt(number).ToString)
	result.Append("   Int(): ")
	result.AppendLine(Int(number).ToString)

	' ----- Negative decimal value.
	number = -3.14
	result.Append(number)
	result.Append("   CInt(): ")
	result.Append(CInt(number).ToString)
	result.Append("   Int(): ")
	result.AppendLine(Int(number).ToString)

	' ----- Number that won't fit in an Integer.
	number = 3000000000.0
	result.Append(number)
	result.Append("   CInt(): ")
	Try
	    result.Append(CInt(number).ToString)
	Catch
	    result.Append("(error)")
	End Try
	result.Append("   Int(): ")
	result.Append(Int(number).ToString)
	
	MsgBox(result.ToString())

There are some other functions in the Math object that provide similar functionality to Int(). For example, the Math.Floor() and Math.Ceiling() functions also operate on numbers that might be out of the range of Integers. Floor() returns the largest whole number less than or equal to a given number, and Ceiling() returns the smallest whole number that’s greater than or equal to a given number. See Figure 6-20.

The CInt( ) function converts numbers to Integer data types, while the Int( ) function returns whole numbers
Figure 6-20. The CInt( ) function converts numbers to Integer data types, while the Int( ) function returns whole numbers

6.21. Calculating π to Thousands of Digits

Problem

You want to impress people by showing how quickly Visual Basic 2005 can calculate π to a thousand or more decimal places. While you’re at it, you might want to discover how to create multidigit mathematical functions using integer arrays of digits.

Solution

Sample code folder: Chapter 06 CalculatePi

Create functions for basic mathematical operations (+, -, *, /) that operate on integer arrays of any reasonable size. Then demonstrate these functions by calculating π to many digits using one of the standard π-calculation algorithms.

Discussion

This recipe includes a module called PiCalculator that contains the functions needed to perform multidigit math, along with one to calculate π to any number of digits. The four main multidigit functions are named ArrayMult(), ArrayDivide(), ArrayAdd(), and ArraySub(). These are declared as Private to the module because they serve only as support routines to the FindPi() function, but you can change them to Public to experiment with them for other purposes. Other supporting functions include ArrayZero(), which sets all “digits” in an array to zeros, and ArcTangent(), which calls the other functions to calculate the arctangent of a multi-digit number.

The way the basic math functions work is similar to the way math is performed on paper by grade-schoolers: when two digits are added, any overflow is added into the next pair of digits, and so on. Calculating π to 500 decimal places requires a huge number of these small repetitive calculations, but that’s what computers are really good at doing.

Here is the code to calculate π. It is based on the following calculation for π:

π/4 = (arctan 1/2)+ (arctan 1/3)

Each part of the algorithm is performed manually, including the arctangent calculation:

	Module PiCalculator
	   Private NumberDigits As Integer

	   Public Function FindPi(ByVal digits As Integer) As String
	      ' ----- Calculate Pi to the specified number of digits,
	      '       based on the formula:
	      '          Pi/4 = arctan(1/2) + arctan(1/3)
	      Dim result As New System.Text.StringBuilder("PI=3.")
	      Dim digitIndex As Integer
	      Dim divFactor As Integer

	      ' ----- Build an array that will hold manual calculations.
	      NumberDigits = digits + 2
	      Dim targetValue(NumberDigits) As Integer
	      Dim sourceValue(NumberDigits) As Integer

	      ' ----  
Perform the calculation.
	      divFactor = 2
	      ArcTangent(targetValue, sourceValue, divFactor)
	      divFactor = 3
	      ArcTangent(targetValue, sourceValue, divFactor)
	      ArrayMult(targetValue, 4)

	      ' ----- Return a string version of the calculation.
	      For digitIndex = 1 To NumberDigits - 3
	         result.Append(Chr(targetValue(digitIndex) + Asc("0"c)))
	      Next digitIndex
	      Return result.ToString
	   End Function

	   Private Sub ArrayMult(ByRef baseNumber() As Integer, _
	         ByRef multiplier As Integer)
	      ' ----- Multiply an array number by another number by hand.
	      ' The product remains in the array number.
	      Dim carry As Integer
	      Dim position As Integer
	      Dim holdDigit As Integer

	      ' ----- Multiple each base digit, from right to left.
	      For position = NumberDigits To 0 Step -1
	         ' ----- If the multiplication went past 9, carry the
	         ' tens value to the next column.
	         holdDigit = (baseNumber(position) * multiplier) + carry
	         carry = holdDigit  10
	         baseNumber(position) = holdDigit Mod 10
	      Next position
	   End Sub
	    
Private Sub ArrayDivide(ByRef dividend() As Integer, ByRef divisor As Integer)
	      ' ----- Divide an array number by another number by hand.
	      '       The quotient remains in the array number.
	      Dim borrow As Integer
	      Dim position As Integer
	      Dim holdDigit As Integer

	      ' ----- Process division for each digit.
	      For position = 0 To NumberDigits
	         ' ----- If the division can't happen directly, borrow from
	         '       the previous position.
	         holdDigit = dividend(position) + borrow * 10
	         dividend(position) = holdDigit  divisor
	         borrow = holdDigit Mod divisor
	      Next position
	   End Sub
	
	   Private Sub ArrayAdd(ByRef baseNumber() As Integer, ByRef addend() As Integer)
	      ' ----- Add two array numbers together.
	      '       The sum remains in the first array number.
	      Dim carry As Integer
	      Dim position As Integer
	      Dim holdDigit As Integer

	      ' ----- Add each digit from right to left.
	      For position = NumberDigits To 0 Step -1
	         ' ----- If the sum goes beyond 9, carry the tens
	         '       value to the next column.
	         holdDigit = baseNumber(position) + addend(position) + carry
	         carry = holdDigit  10
	         baseNumber(position) = holdDigit Mod 10
	      Next position
	   End Sub
	
	   Private Sub ArraySub(ByRef minuend() As Integer, ByRef subtrahend() As Integer)
	      ' ----- Subtract one array number from another.
	      '       The difference remains in the first array number.
	      Dim borrow As Integer
	      Dim position As Integer
	      Dim holdDigit As Integer

	      ' ---- Subtract the digits from right to left.
	      For position = NumberDigits To 0 Step -1
	         ' ----- If the subtraction would give a negative value
	         '       for a column, we will have to borrow.
	         holdDigit = minuend(position) - subtrahend(position) + 10
	         borrow = holdDigit  10
	         minuend(position) = holdDigit Mod 10
	         If (borrow = 0) Then minuend(position - 1) -= 1
	      Next position
	   End Sub

	   Private Function ArrayZero(ByRef baseNumber() As Integer) As Boolean
	      ' ----- Report whether an array number is all zero.
	      Dim position As Integer
	
	      ' ----- Examine each digit.
	      For position = 0 To NumberDigits
	         If (baseNumber(position) <> 0) Then
	            ' ----- The number is nonzero.
	            Return False
	         End If
	      Next position

	      ' ----- The number is zero.
	      Return True
	   End Function

	   Private Sub ArcTangent(ByRef targetValue() As Integer, _
	         ByRef sourceValue() As Integer, _
	         ByVal divFactor As Integer)
	      ' ----- Calculate an arctangent of a fraction,
	      '       1/divFactor. This routine  
performs a modified
	      '       Maclaurin series to calculate the arctangent.
	      '       The base formula is:
	      '         arctan(x) = x - x^3/3 + x^5/5 -' x^7/7 + x^9/9 - …
	      '       where -1 < x < 1 (1/divFactor in this case).
	      Dim workingFactor As Integer
	      Dim incremental As Integer

	      ' ----- Figure out the "x" part, 1/divFactor.
	      sourceValue(0) = 1
	      incremental = 1
	      workingFactor = divFactor
	      ArrayDivide(sourceValue, workingFactor)

	      ' ----- Add "x" to the total.
	      ArrayAdd(targetValue, sourceValue)
	      Do
	         ' ----- Perform the "- (x^y)/y" part.
	         ArrayMult(sourceValue, incremental)
	         workingFactor = divFactor * divFactor
	         ArrayDivide(sourceValue, workingFactor)
	         incremental += 2
	         workingFactor = incremental
	         ArrayDivide(sourceValue, workingFactor)
	         ArraySub(targetValue, sourceValue)

	         ' ----- Perform the "+ (x^y)/y" part.
	         ArrayMult(sourceValue, incremental)
	         workingFactor = divFactor * divFactor
	         ArrayDivide(sourceValue, workingFactor)
	         incremental += 2
	         workingFactor = incremental
	         ArrayDivide(sourceValue, workingFactor)
	         ArrayAdd(targetValue, sourceValue)
	      Loop Until ArrayZero(sourceValue)
	   End Sub
	End Module

To exercise these procedures, the following statement uses the FindPi() function to calculate π to 500 digits:

	MsgBox(FindPi(500))

You can change the 500 argument to obtain a different number of digits. However, even though the time required to calculate π to 500 or even 1,000 digits is fairly negligible, every time you double the count, the FindPi() function requires around four times as long to return the results. Try smaller counts first, moving up to larger counts when you have a good feel for just how long the calculation will take on your computer.

Figure 6-21 shows the first 500 digits of π as formatted by the FindPi() function. If you prefer to format the digits differently, say in groups of 10 digits or with occasional end-of-line characters, you might want to change FindPi() to return an array of digits. The calling code can then format the digits as desired. The “digits” in the array have values in the range 0 to 9, and they need to be converted to ASCII digits by adding the ASCII equivalent of “0” (zero) to their value before applying the Chr() conversion function.

Pi calculated to 500 decimal places
Figure 6-21. Pi calculated to 500 decimal places

See Also

There are many places on the Web to see many digits of π and to learn of the different algorithms used for calculating π to even millions of decimal places. See, for example, http://www.exploratorium.edu/pi/Pi10-6.html.

6.22. Getting a Number’s Prime Factors

Problem

You need to determine all prime factors of a given number, perhaps for demonstrating cryptographic algorithms or for some other purpose.

Solution

Sample code folder: Chapter 06PrimeFactor

Create a function called PrimeFactors() that analyzes any Long integer and returns a string listing all the number’s prime factors in a clear format.

Discussion

The algorithm used here is fairly straightforward, suitable for reasonably sized Long integers. The prime factors are found by checking for even divisibility by numbers from 2 to the square root of the number being checked. Whenever a factor is found, it is extracted, and the divisibility check is repeated. Tallies for the factors are converted to string format during this process, and the string is returned when all the checks are completed:

	Private Function PrimeFactors( _
	      ByVal numberToFactor As Long) As String
	   ' ----- Calculate the prime factors of a starting number.
	   Dim result As New System.Text.StringBuilder
	   Dim testFactor As Long
	   Dim workNumber As Long
	   Dim factorCount As Long

	   ' ----- Scan through all numbers up to
	   '       Sqrt(numberToFactor).
	   workNumber = numberToFactor
	   testFactor = 1
	   Do While (testFactor < Math.Sqrt(CType(workNumber, _
	         Double)))
	      testFactor += 1
	      factorCount = 0
	      Do While (workNumber / testFactor) = _
	            (workNumber  testFactor)
	         ' ----- Found a factor.
	         factorCount += 1
	         workNumber = testFactor
	      Loop
	      Select Case factorCount
	         Case 1
	            ' ----- Show a prime factor.
	            result.AppendLine(testFactor)
	         Case Is > 1
	            ' ----- Show a prime factor as a power.
	            result.Append(testFactor)
	            result.Append("^")
	            result.AppendLine(factorCount)
	      End Select
	   Loop
	
	   ' ----- Include the final prime factor, if available.
	   If (workNumber > 1) Then result.Append(workNumber)
	   Return result.ToString
	End Function

Here’s the code that drives the example, which finds and displays the prime factors for the number 7999848:

	Dim result As New System.Text.StringBuilder
	Dim number As Long = 7999848
	
	result.AppendLine("PrimeFactors(" & number & ")… ")
	result.AppendLine()
	result.Append(PrimeFactors(number))

	MsgBox(result.ToString())

Figure 6-22 shows the results of calculating that number’s prime factors.

See Also

There are many good resources on the Web for learning about prime numbers and prime factors. See, for example, http://primes.utm.edu/largest.html.

Using the PrimeFactors( ) function to find all the prime factors of a number in one call
Figure 6-22. Using the PrimeFactors( ) function to find all the prime factors of a number in one call

6.23. Using Recursion to Calculate Factorials

Problem

You want to study a sample of Visual Basic’s ability to define recursive functions, or you need a factorial function for smaller integers.

Solution

Sample code folder: Chapter 06Factorial

Create a Factorial() function that recursively calls itself.

Discussion

The code in this recipe does not represent the most efficient way to calculate factorials for larger integers. You’ll want to use a standard For…Next loop or similar process when working with larger numbers, simply because each recursive function call uses up stack space and adds a little overhead. However, recursive functions can be quite useful in some programming situations. A simple recursive function that calculates the factorial of a number is a great way to understand recursion.

The factorial of a number N is the product of all numbers from 1 to N. For example, the factorial of 3 is calculated as 3 x 2 x 1, which results in a value of 6. The Factorial() function returns the value 1 if it is passed a value of zero; otherwise, it returns the passed value times the factorial of the next smaller integer. Study the Select Case lines of code in the function to see how this is accomplished:

	Public Function Factorial(ByVal number As Decimal) As Decimal
	    Select Case number
	        Case Is < 0
	            Throw New Exception("Factorial: Bad argument")
	        Case Is = 0
	            Return 1
	        Case Else
	            Return number * Factorial(number - 1)
	    End Select
	End Function

Calling the Factorial() function from inside its own code is what recursion is all about. All pending returns are literally stacked up until the value of the passed number finally reaches zero, at which time the pending multiplications all happen in a hurry. As a result of the way this recursion works, if you request the Factorial() of a large number, you run the risk of running out of stack memory or of numeric over-flow. With Decimal variables, as shown in the previous code, the largest value you can pass to the function without overflow is just 27. Of course, the factorial of 27 is a huge number, and the answer is exact when using Decimal values. You might consider switching the algorithm to use Double values to find approximations of even larger factorials.

The following lines demonstrate the Factorial() function by calculating and displaying the factorial of 7:

	Dim result As New System.Text.StringBuilder
	Dim number As Decimal = 7

	result.AppendLine("Factorial(" & number & ")… ")
	result.AppendLine()
	result.Append(Factorial(number))

	MsgBox(result.ToString())

Figure 6-23 shows the results of calculating the factorial of 7.

Calculating the factorial of a number with the Factorial( ) function
Figure 6-23. Calculating the factorial of a number with the Factorial( ) function

See Also

Search for “Factorial” on the Web to learn more about factorials (see, for example, http://mathworld.wolfram.com/Factorial.html).

6.24. Manipulating Bits with Bitwise Operators

Problem

You need to shift, mask, and perform other bitwise manipulations on integers.

Solution

Visual Basic 2005 has functions for all the major bit-manipulation techniques, and it’s easy to combine these to perform more complicated bitwise calculations as required.

Discussion

There are several operators that are most often thought of as Boolean operators, working with and returning True and False (Boolean) values. However, these operators also accept and return integer values of various sizes, and this is where they can be of value for bit manipulations. These bitwise operators include the following:

And

Bits are combined to 1 only if they are both 1.

Not

Bits are inverted, 0 to 1 and 1 to 0.

Xor

Bits are combined to 1 only if the two bits are not the same.

Or

Bits are combined to 1 if either bit is a 1.

<<

Bits are all shifted left a given number of bit positions.

>>

Bits are all shifted right a given number of bit positions.

Tip

The two bit-shift operators can be used as assignment operators. That is, the following two lines of code provide identical results:

	a = a << 3
	a <<= 3

In both cases the bits in integer variable a are shifted to the left three positions. The And, Or, Not, and Xor operators don’t support assignment notation.

The following code demonstrates a sampling of these bit manipulations. You can change the program to experiment with the various operators:

	Dim result As New System.Text.StringBuilder
	Dim number As Integer = 7

	result.Append(number)
	result.Append(" <<= 3 … ")
	number <<= 3
	result.AppendLine(number)
	result.Append(number)
	result.Append(" Xor 17 … ")
	number = number Xor 17
	result.AppendLine(number)

	MsgBox(result.ToString())

Figure 6-24 shows the output displayed by this sample code.

Bit manipulations with Visual Basic 2005
Figure 6-24. Bit manipulations with Visual Basic 2005

See Also

Search for “Logical and Bitwise Operators in Visual Basic” in Visual Studio Help to learn more about this topic.

6.25. Storing and Retrieving Bits in a BitArray

Problem

You want to store and retrieve a lot of bits without wasting memory and without sacrificing speed of operation.

Solution

Sample code folder: Chapter 06GetPrimes

Use a BitArray to store and access individual bits in memory efficiently.

Discussion

The BitArray object lets you access bits by indexed position, and all the details of decoding which bit position of which byte the bit is stored in are taken care of transparently behind the scenes. A BitArray of 80 bits is actually stored in 10 bytes of memory.

To demonstrate using a BitArray, we’ve created a module named Eratosthenes.vb that contains code to find all prime numbers between 2 and 8,000,000 very quickly. The 8 million bits are stored in 1 million bytes of memory, and the individual bits are accessed using indexes in the range 0 to 8,000,000.

The Sieve of Eratosthenes (http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes) works by first setting all bits to 1, or True. The BitArray can be instantiated with a count and an optional second Boolean parameter that presets all bits to True or False. In this case, True sets them all to 1. Starting with 2, each prime number, or bit that is set, clears all bits that are exact multiples of that number. So, for instance, bit 2 is kept at True, but bits 4, 6, 8, and so on, are all set to False. This marks all even numbers except for 2 as nonprime. Similarly, bit 3 is left True, and bits 6, 9, 12, 15, etc., are set to False to mark all multiples of 3 as nonprime. This looping technique very quickly sets all bits in the BitArray that appear in prime number positions to True and all other bits to False.

The Eratosthenes module contains the BitArray itself, a Sieve() method to set all the prime number bits as described earlier, and a GetBit() function to retrieve the bit at any location, converting the bit’s True or False Boolean value to a 1 or 0 integer value:

	Module Eratosthenes
	   Private Const MaxNumber As Integer = 8000000
	   Private PrimeStorage As New BitArray(MaxNumber, True)

	   Public Sub Sieve()
	      ' ----- Get all the prime numbers from 1 to MaxNumber.
	      Dim index As Integer = 1
	      Dim counter As Integer

	      ' ----- Scan through all primes.
	      Do While (index < (MaxNumber - 1))
	         index += 1
	         If (PrimeStorage(index) = True) Then
	            ' ----- Found a prime. Set all of its multiples
	            '       to non-prime.
	            For counter = index * 2 To MaxNumber - 1 _
	                  Step index
	               PrimeStorage(counter) = False
	            Next counter
	         End If
	      Loop
	   End Sub

	   Public Function GetBit(ByVal index As Integer) As Integer
	      ' ----- Retrieve the status of a single prime bit.
	      If (PrimeStorage(index) = True) Then _
	         Return 1 Else Return 0
	   End Function
	End Module

The following block of code demonstrates the BitArray in action, displaying the prime numbers up to the size of the BitArray. To prevent information overload, only the first and last few numbers in the desired range are formatted into a string for display, as there are a lot of prime numbers between 0 and 8,000,000:

	Dim result As New System.Text.StringBuilder
	Dim counter As Integer
	Dim needBreak As Boolean = True
	result.AppendLine( _
	   "Prime numbers using the ""Sieve of Eratosthenes""")
	
	' ----- Generate the primes.
	Sieve()

	' ----- Report each prime.
	For counter = 2 To 7999999
	   If (GetBit(counter) = 1) Then
	      If (counter < 50) Or (counter > 7999800) Then
	         ' ----- Only show a limited number of primes.
	         result.AppendLine(counter)
	      ElseIf (needBreak = True) Then
	         ' ----- Show that we are leaving something out.
	         result.AppendLine("…")
	         needBreak = False
	      End If
	   End If
	Next counter
	MsgBox(result.ToString())

Figure 6-25 shows the partial list of all the prime numbers as determined by the bits in the BitArray. On your system there could be less than a second’s delay during the computation and display of these prime numbers!

All the prime numbers between 0 and 8,000,000, calculated quickly using a BitArray
Figure 6-25. All the prime numbers between 0 and 8,000,000, calculated quickly using a BitArray

See Also

Search for “prime numbers” on the Web for more information. See also “Logical and Bitwise Operators in Visual Basic” in the Visual Studio online help.

6.26. Enhancing the Random Number Generator

Problem

You want to greatly extend the cycle length of Visual Basic’s pseudorandom number generator.

Solution

Sample code folder: Chapter 06RepeatRandom

You can use the RNGCryptoServiceProvider class to generate cryptographically strong random numbers, or you can use the technique presented here to greatly extend the cycle length of the standard pseudorandom number generator and make it easier to use.

Discussion

The BetterRandom class presented here uses the standard Rnd() function and the Randomize() initialization method, but it enhances them in several ways. Contrary to what some people claim, it is possible to initialize the random number generator to a unique but repeatable sequence, but the technique is far from obvious. You have to call the Randomize() method immediately after calling the Rnd() function, but only after passing Rnd() a negative numerical value. So, one advantage of this BetterRandom class is the encapsulation of this technique into something that makes a lot more sense. If you instantiate a BetterRandom object by passing any string to it, each unique string initializes the generator to a unique but repeatable state. If you instantiate a BetterRandom object with no string, the system clock generates a unique sequence for every system tick, which means it is always unique.

The cycle length of the generator is greatly enhanced by maintaining a table of pseudorandom Double numbers in the normalized range 0 to 1. Rolling indexes are used to add table entries together along with the next value returned by Rnd(), and the result is brought back into the range 0 to 1 using the Mod operator. The GetNextDouble() function forms the core of this algorithm, as shown here:

	Public Function GetNextDouble() As Double
	   ' ----- Return the next pseudorandom number as a Double.
	   ' ----- Move to the next index positions.
	   Index1 = (Index1 + 1) Mod TableSize
	   Index2 = (Index2 + 1) Mod TableSize
	   
	   ' ----- Update the random numbers at those positions.
	   RandomTable(Index1) += RandomTable(Index2) + Rnd()
	   RandomTable(Index1) = RandomTable(Index1) Mod 1.0
	
	   ' ----- Return the newest random table value.
	   Return RandomTable(Index1)
	End Function

This table keeps the pseudorandom values well mixed while providing a nice flat distribution of the values with excellent statistical results. When the Rnd() function cycles back around to its starting point, the table will be in a completely different state, which means the cycle length of the values returned from this table will be some off-the-chart astronomical value. It simply won’t repeat in the amount of time there is in this universe to exercise the algorithm.

The table size is set to 32, but feel free to make the table larger or smaller as desired. A larger table will be slightly slower to initialize, but subsequent pseudorandom numbers will be calculated and returned just as fast.

Another advantage of this class is that it can be used to return several types of pseudorandom numbers. The GetNextDouble() function, which is demonstrated in this recipe, returns a double-precision value between 0 and 1. The next few recipes in this chapter will demonstrate how the BetterRandom class can be used to return several other types of pseudorandom numbers. The code for the class is presented here in its entirety for easy review:

	Public Class BetterRandom
	   Private Const TableSize As Integer = 32
	   Private RandomTable(TableSize - 1) As Double
	   Private Index1 As Integer
	   Private Index2 As Integer

	   Public Sub New()
	      ' ----- Generate truly pseudorandom numbers.
	      InitRandom(Now.Ticks.ToString)
	   End Sub

	   Public Sub New(ByVal Key As String)
	      ' ----- Generate a repeatable random sequence.
	      InitRandom(Key)
	   End Sub

	   Private Sub InitRandom(ByVal repeatKey As String)
	      ' ----- Prepare the random number generator.
	      Dim stringIndex As Integer
	      Dim workNumber As Double
	      Dim counter As Integer

	      ' ----- All sequences start with the same base sequence.
	      Randomize(Rnd(-1))

	      ' ----- Initialize the table using the key string.
	      For counter = 0 To TableSize - 1
	         stringIndex = counter Mod repeatKey.Length
	         workNumber = Math.PI / _
	            Asc(repeatKey.Substring(stringIndex, 1))
	         RandomTable(counter) = (Rnd() + workNumber) Mod 1.0
	      Next counter

	      ' ----- Set the starting state for the table.
	      Index1 = TableSize  2
	      Index2 = TableSize  3

	      ' ----- Cycle through a bunch of values to get a good
	      '       starting mix.
	      For counter = 0 To TableSize * 5
	         GetNextDouble()
	      Next counter

	      ' ----- Reset the random sequence based on our
	      '       preparations.
	      Randomize(Rnd(-GetNextSingle()))
	   End Sub

	   Public Function GetNextDouble() As Double
	      ' ----- Return the next pseudorandom number as
	      '       a Double.

	      ' ----- Move to the next index positions.
	      Index1 = (Index1 + 1) Mod TableSize
	      Index2 = (Index2 + 1) Mod TableSize

	      ' ----- Update the random numbers at those positions.
	      RandomTable(Index1) += RandomTable(Index2) + Rnd()
	      RandomTable(Index1) = RandomTable(Index1) Mod 1.0

	      ' ----- Return the newest random table value.
	      Return RandomTable(Index1)
	   End Function

	   Public Function GetNextSingle() As Single
	      ' ----- Return the next pseudorandom number as
	      '       a Single.
	      Return CSng(GetNextDouble())
	   End Function

	   Public Function GetNextInteger(ByVal minInt As Integer, _
	         ByVal maxInt As Integer) As Integer
	      ' ----- Return the next pseudorandom number within an
	      '       Integer range.
	      Return CInt(Int(GetNextDouble() * _
	         (maxInt - minInt + 1.0) + minInt))
	   End Function

	   Public Function GetNextReal(ByVal minReal As Double, _
	         ByVal maxReal As Double) As Double
	      ' ----- Return the next pseudorandom number within a
	      '       floating-point range.
	      Return GetNextDouble() * (maxReal - minReal) + minReal
	   End Function
	
	   Public Function GetNextNormal(ByVal mean As Double, _
	         ByVal stdDev As Double) As Double
	      ' ----- Return the next pseudorandom number adjusted
	      '       to a normal distribution curve.
	      Dim x As Double
	      Dim y As Double
	      Dim factor As Double
	      Dim radiusSquared As Double

	      Do
	         x = GetNextReal(-1, 1)
	         y = GetNextReal(-1, 1)
	         radiusSquared = x * x + y * y
	      Loop Until radiusSquared <= 1.0
	      factor = Math.Sqrt(-2.0 * Math.Log(radiusSquared) / _
	         radiusSquared)

	      Return x * factor * stdDev + mean
	   End Function

	   Public Function GetNextExp(ByVal mean As Double) As Double
	      ' ----- Return the next pseudorandom number adjusted
	      '       for exponential distribution.
	      Return -Math.Log(GetNextDouble) * mean
	   End Function
	End Class

The following code demonstrates the BetterRandom class by generating two short sequences of pseudorandom Double numbers in the range 0 to 1. The first sequence is generated uniquely each time by not passing a string during initialization of the BetterRandom object. The second sequence uses the same string each time for initialization, and therefore the sequence is always repeated:

	Dim result As New System.Text.StringBuilder
	Dim generator As BetterRandom
	
	result.AppendLine("Never the same sequence:")
	generator = New BetterRandom
	result.AppendLine(generator.GetNextDouble.ToString)
	result.AppendLine(generator.GetNextDouble.ToString)
	result.AppendLine(generator.GetNextDouble.ToString)
	result.AppendLine()

	result.AppendLine("Always the same sequence:")
	generator = New BetterRandom( _
	   "Every string creates a unique, repeatable sequence")

	result.AppendLine(generator.GetNextDouble.ToString)
	result.AppendLine(generator.GetNextDouble.ToString)
	result.AppendLine(generator.GetNextDouble.ToString)

	MsgBox(result.ToString())

Figure 6-26 shows the never-and always-repeating sequences generated by this demonstration code.

Two pseudorandom sequences are generated: one that’s always unique and one that always repeats
Figure 6-26. Two pseudorandom sequences are generated: one that’s always unique and one that always repeats

See Also

Search Visual Studio Help for “Random Class” and “RNGCryptoServiceProvider Class” for information about other ways to generate pseudorandom numbers in Visual Basic.

6.27. Generating Random Integers in a Range

Problem

You need to generate a sequence of pseudorandom integers with a flat distribution over a given range.

Solution

Sample code folder: Chapter 06RepeatRandom

The BetterRandom class (see Recipe 6.26) sports a GetNextInteger() function. Two parameters define the range limits for the returned pseudorandom integer, as shown here:

	newRnd.GetNextInteger(minInt, maxInt)

The returned integer has a statistically flat distribution across the given range.

Discussion

The following code creates a new instance of the BetterRandom object, which it then uses to generate 200 pseudorandom integers in the range −10 to +10. The results are collected and then displayed for review. As a programming exercise, you might consider changing this code to display the average and perhaps the standard deviation for these returned values.

The generator object is created without passing a string to initialize the generator, so a unique sequence is created every time this program is run:

	Dim result As New System.Text.StringBuilder
	Dim generator As New BetterRandom
	Dim minInt As Integer = -10
	Dim maxInt As Integer = 10
	Dim counter As Integer

	result.Append("Random integers in range ")
	result.AppendLine(minInt & " to " & maxInt)
	For counter = 1 To 200
	   ' ----- Add one random number.
	   result.Append(generator.GetNextInteger(-10, 10))
	   If ((counter Mod 40) = 0) Then
	      ' ----- Group on distinct lines periodically.
	      result.AppendLine()
	   Else
	      result.Append(",")
	   End If
	Next counter

	MsgBox(result.ToString())

Figure 6-27 shows the results of generating the 200 pseudorandom integers.

Pseudorandom integers in the range −10 to +10 generated by the BetterRandom object
Figure 6-27. Pseudorandom integers in the range −10 to +10 generated by the BetterRandom object

See Also

Recipe 6.26 shows the full code for the BetterRandom class.

Search Visual Studio Help for “Random Class” and “RNGCryptoServiceProvider Class” for information about other ways to generate pseudorandom numbers in Visual Basic.

6.28. Generating Random Real Numbers in a Range

Problem

You need to generate a sequence of pseudorandom real numbers with a flat distribution over a given range.

Solution

Sample code folder: Chapter 06RepeatRandom

The BetterRandom class (see Recipe 6.26) sports a GetNextReal() function. Two parameters define the range limits for the returned pseudorandom real values, and the returned value has a statistically flat distribution across the given range:

	GetNextReal(minReal, maxReal)

Discussion

The following code creates a new instance of the BetterRandom object, which it then uses to generate 20 pseudorandom double-precision real numbers in the range −10.0 to +10.0. The results are collected and then displayed for review. As a programming exercise, you might consider changing this code to display the average and perhaps the standard deviation for these returned values.

The generator object is created without passing a string to initialize the generator, so a unique sequence will be created every time this program is run:

	Dim result As New System.Text.StringBuilder
	Dim generator As New BetterRandom
	Dim minReal As Integer = -10
	Dim maxReal As Integer = 10
	Dim counter As Integer

	result.Append("Random reals in range ")
	result.AppendLine(minReal & " to " & maxReal)
	result.AppendLine()
	For counter = 1 To 20
	   ' ----- Add one random number.
	   result.Append(generator.GetNextReal(minReal, maxReal))
	   If ((counter Mod 5) = 0) Then
	      ' ----- Group on distinct lines periodically.
	      result.AppendLine()
	  Else
	     result.Append(", ")
	  End If
	Next counter

	MsgBox(result.ToString())

Figure 6-28 shows the results of generating the 20 pseudorandom double-precision real values.

Pseudorandom reals in the range −10.0 to +10.0 generated by the BetterRandom object
Figure 6-28. Pseudorandom reals in the range −10.0 to +10.0 generated by the BetterRandom object

See Also

Recipe 6.26 shows the full code for the BetterRandom class.

There are many good references on the Web to learn more about random number generation (see, for example, http://random.mat.sbg.ac.at).

6.29. Generating Normal-Distribution Random Numbers

Problem

You need to generate a sequence of pseudorandom numbers with a normal distribution, given the distribution’s mean and standard deviation.

Solution

Sample code folder: Chapter 06RepeatRandom

The BetterRandom class (see Recipe 6.26) sports a GetNextNormal() function. Two parameters passed to this function define the mean and standard deviation for the distribution of the generated values:

	GetNextNormal(mean, stdDev)

Discussion

The following code creates a new instance of the BetterRandom object, which it then uses to generate 20 pseudorandom double-precision numbers with the desired normal distribution. As a programming exercise you might consider changing this code to display the mean and standard deviation for the returned values, to compare the results with the goal.

The generator object is created without passing a string to initialize the generator, so a unique sequence will be created every time this program is run:

	Dim result As New System.Text.StringBuilder
	Dim generator As New BetterRandom
	Dim mean As Double = 100
	Dim stdDev As Double = 10
	Dim counter As Integer

	result.Append("Normal distribution randoms with mean ")
	result.AppendLine(mean & " and standard deviation " & stdDev)
	result.AppendLine()
	For counter = 1 To 20
	   ' ----- Add one random number.
	   result.Append(generator.GetNextNormal(mean, stdDev))
	   If ((counter Mod 3) = 0) Then
	      ' ----- Group on distinct lines periodically.
	      result.AppendLine()
	   Else
	      result.Append(", ")
	   End If
	Next counter

	MsgBox(result.ToString())

Figure 6-29 shows the results of generating the 20 pseudorandom double-precision normal-distribution numbers.

Pseudorandom normally distributed numbers generated by the BetterRandom object
Figure 6-29. Pseudorandom normally distributed numbers generated by the BetterRandom object

See Also

Recipe 6.26 shows the full code for the BetterRandom class.

There are many good references on the Web to learn more about random number generation (see, for example, http://random.mat.sbg.ac.at).

6.30. Generating Exponential-Distribution Random Numbers

Problem

You need to generate a sequence of pseudorandom numbers with an exponential distribution given the distribution’s mean.

Solution

Sample code folder: Chapter 06RepeatRandom

The BetterRandom class (see Recipe 6.26) sports a GetNextExp() function. One parameter passed to this function defines the mean of the exponentially distributed return values:

	GetNextExp(mean)

Discussion

The following code creates a new instance of the BetterRandom object, which it then uses to generate 20 pseudorandom double-precision numbers with the desired exponential distribution. As a programming exercise you might consider changing this code to display the mean of the returned values, to compare the results with the goal.

The generator object is created without passing a string to initialize the generator, so a unique sequence is created every time this program is run:

	Dim result As New System.Text.StringBuilder
	Dim generator As New BetterRandom
	Dim mean As Double = 10
	Dim counter As Integer

	result.Append("Exponential distribution randoms with mean ")
	result.AppendLine(mean)
	result.AppendLine()
	For counter = 1 To 20
	   ' ----- Add one random number.
	   result.Append(generator.GetNextExp(mean))
	   If ((counter Mod 3) = 0) Then
	      ' ----- Group on distinct lines periodically.
	      result.AppendLine()
	   Else
	      result.Append(", ")
	   End If
	Next counter

	MsgBox(result.ToString())

Figure 6-30 shows the results of generating the 20 pseudorandom double-precision exponential-distribution numbers.

Pseudorandom exponentially distributed numbers generated by the BetterRandom object
Figure 6-30. Pseudorandom exponentially distributed numbers generated by the BetterRandom object

See Also

Recipe 6.26 shows the full code for the BetterRandom class.

There are many good references on the Web to learn more about random number generation (see, for example, http://random.mat.sbg.ac.at).

6.31. Creating a Matrix

Problem

You want to declare a matrix, populate it with nonzero values, and perform several standard matrix calculations on it.

Solution

Sample code folder: Chapter 06Matrix

This recipe demonstrates how to declare and populate a matrix in a clear, readable way. A module of matrix functions is also included, although several of the functions it contains will be presented in follow-up recipes.

Discussion

Nested braces containing comma-separated numbers can be used to fill arrays of one or more dimensions. In the case of a two-dimensional matrix, the braces can optionally be separated to show each row of numbers on its own line using the underscore (_) line-continuation character. Feel free to use whatever layout details work for you, but the following sample of a 3 x 3 matrix can provide a decent, visually appealing layout in your source code:

	Dim matrixA(,) As Double = { _
	   {4, 5, 6}, _
	   {7, 8, 9}, _
	   {3, 2, 1}}
	MsgBox(MatrixHelper.MakeDisplayable(matrixA))

The last line of this code uses a function named MakeDisplayable() to return a string representation of a matrix suitable for display, as shown in Figure 6-31. This function is one of several to be presented in the code module named MatrixHelper.

The custom output of the matrix
Figure 6-31. The custom output of the matrix

The MatrixHelper module contains several functions to work with matrices, and the recipes that follow will describe them further. A complete listing of MatrixHelper.vb can be found at the end of this chapter.

See Also

See the full MatrixHelper.vb listing in Recipe 6.35.

6.32. Inverting a Matrix

Problem

You want to invert a matrix.

Solution

Sample code folder: Chapter 06Matrix

Use the MatrixHelper. Inverse() function presented here and expanded upon in the MatrixHelper module presented in Recipe 6.35.

Discussion

The inverse of a matrix is another identically sized matrix that, when multiplied with the original matrix, gives the identity matrix. Only square matrices can be inverted. Matrix inversion is one of the basic matrix operations used for scientific, engineering, and computer graphics work. (A full description of matrices and their operations is beyond the scope of this book.)

Visual Basic 2005 is a good language for developing high-speed .NET Framework–based mathematical collections of number-crunching routines. It allows you to create fast-running classes, structures, and modules containing related functions or methods to meet many requirements. This recipe presents the code required to invert a matrix efficiently:

	Dim matrixA(,) As Double = { _
	   {1, 3, 3}, _
	   {2, 4, 3}, _
	   {1, 3, 4}}
	Dim matrixB(,) As Double = MatrixHelper.Inverse(matrixA)

	MsgBox(MatrixHelper.MakeDisplayable(matrixA) & _
	   vbNewLine & vbNewLine & "Inverse: " & _
	   vbNewLine & MatrixHelper.MakeDisplayable(matrixB))

Tip

The MatrixHelper module is listed in its entirety in Recipe 6.35; it includes the Inverse() function and other functions called by Inverse().

Figure 6-32 shows the result of finding the inverse of a 3 x 3 matrix.

Finding the inverse of a square matrix with the MatrixHelper.Inverse( ) function
Figure 6-32. Finding the inverse of a square matrix with the MatrixHelper.Inverse( ) function

To use the MatrixHelper.Inverse() function in your own applications, add the MatrixHelper module to your project and call the function from anywhere within your application.

See Also

See the full MatrixHelper.vb listing in Recipe 6.35.

6.33. Calculating the Determinant of a Matrix

Problem

You need to calculate the determinant of a matrix.

Solution

Sample code folder: Chapter 06Matrix

Add the MatrixHelper module to your application, and pass your matrix to the MatrixHelper.Determinant() function.

Discussion

The determinant of a matrix is a single number derived from a matrix. It helps determine if a matrix is invertible, and it also comes into play when using matrices to solve simultaneous equations. (A full description of matrices and their operations is beyond the scope of this book.)

The following sample code creates a square matrix of double-precision numbers and passes it to the MatrixHelper.Determinant() function in the MatrixHelper module, which returns the determinant of the matrix:

	Dim matrixA(,) As Double = { _
	   {1, 2, 3}, _
	   {5, 4, 6}, _
	   {9, 7, 8}}
	Dim determinant As Double = MatrixHelper.Determinant(matrixA)

	MsgBox(MatrixHelper.MakeDisplayable(matrixA) & _
	   vbNewLine & vbNewLine & "Determinant: " & _
	   determinant.ToString)

The complete MatrixHelper module is listed in Recipe 6.35. The Determinant() function is listed here for easy reference:

	Public Function Determinant(ByVal sourceMatrix(,) _
	      As Double) As Double
	   ' ----- Calculate the determinant of a matrix.
	   Dim result As Double
	   Dim pivots As Integer
	   Dim count As Integer

	   ' ----- Only calculate the determinants of square matrices.
	   If (UBound(sourceMatrix, 1) <> _
	         UBound(sourceMatrix, 2)) Then
	      Throw New Exception("Determinant only " & _
	         "calculated for square matrices.")
	   End If
	   Dim rank As Integer = UBound(sourceMatrix, 1)
	   ' ----- Make a copy of the matrix so we can work
	   '       inside of it.
	   Dim workMatrix(rank, rank) As Double
	    
Array.Copy(sourceMatrix, workMatrix, _
	      sourceMatrix.Length)

	   ' ----- Use LU decomposition to form a
	   '       triangular matrix.
	   Dim rowPivots(rank) As Integer
	   Dim colPivots(rank) As Integer
	   workMatrix = FormLU(workMatrix, rowPivots, _
	      colPivots, count)

	   ' ----- Get the product at each of the pivot points.
	   result = 1
	   For pivots = 0 To rank
	      result *= workMatrix(rowPivots(pivots), _
	         colPivots(pivots))
	   Next pivots
	   
	   ' ----- Determine the sign of the result using
	   '       LaPlace's formula.
	   result = (-1) ^ count * result
	   Return result
	End Function

A very useful technique for copying one array into another is shown in one of the program lines in the Determinant() function. Consider the following line of code:

	Array.Copy(a, b, a.Length)

The Array class sports a shared Copy() method that provides a high-speed way to copy the binary data from one array into another. There are several overloaded versions of this method, but as used here, all bytes in array a are copied into array b, starting at the first byte location in each array. The transfer of these bytes from one location in memory to another is highly efficient. You could loop through all of array a’s indexed variable locations and copy them one at a time into corresponding locations within array b, but the Array.Copy() method copies all the bytes with one function call and no looping.

Figure 6-33 shows the calculated determinant of a 3 x 3 matrix.

Finding the determinant of a square matrix with the MatrixHelper.Determinant( ) function
Figure 6-33. Finding the determinant of a square matrix with the MatrixHelper.Determinant( ) function

See Also

See the full MatrixHelper.vb listing in Recipe 6.35.

6.34. Solving Simultaneous Equations

Problem

You want to solve a set of n simultaneous equations containing n unknowns.

Solution

Sample code folder: Chapter 06Matrix

Use the matrix operations presented in the MatrixHelper module to solve the equation.

Discussion

Matrices are useful in solving simultaneous equations. The solution is defined in Cramer’s Rule, a theorem of linear algebra named after mathematician Gabriel Cramer. (A full description of matrices and their operations is beyond the scope of this book.)

The MatrixHelper module contains a special-purpose function that solves simultaneous equations by calling several matrix-analysis functions. You pass a square matrix of size n containing the coefficients of the unknowns from the equations, along with a one-dimensional array containing the equation constants. The MatrixHelper.SimultEq() function then returns a one-dimensional array containing the solution values for the equation’s unknowns. Here is the code listing for the MatrixHelper.SimultEq() function:

	Public Function SimultEq( _
	     ByVal sourceEquations(,) As Double, _
	     ByVal sourceRHS() As Double) As Double()
	   ' ----- Use matrices to solve simultaneous equations.
	   Dim rowsAndCols As Integer

	   ' ----- The matrix must be square and the array size
	   '       must match.
	   Dim rank As Integer = UBound(sourceEquations, 1)
	   If (UBound(sourceEquations, 2) <> rank) Or _
	         (UBound(sourceRHS, 1) <> rank) Then
	      Throw New Exception( _
	         "Size problem for simultaneous equations.")
	   End If

	   ' ----- Create some arrays for doing all of the work.
	   Dim coefficientMatrix(rank, rank) As Double
	   Dim rightHandSide(rank) As Double
	   Dim solutions(rank) As Double
	   Dim rowPivots(rank) As Integer
	   Dim colPivots(rank) As Integer

	   ' ----- Make copies of the original matrices so we don't
	   '       mess them up.
	   Array.Copy(sourceEquations, coefficientMatrix, _
	      sourceEquations.Length)
	   Array.Copy(sourceRHS, rightHandSide, sourceRHS.Length)

	   ' ----- Use LU decomposition to form a triangular matrix.
	   coefficientMatrix = FormLU(coefficientMatrix, _
	      rowPivots, colPivots, rowsAndCols)

	   ' ----- Find the unique solution for the upper-triangle.
	   BackSolve(coefficientMatrix, rightHandSide, solutions, _
	      rowPivots, colPivots)

	   ' ----- Return the simultaneous equations result in
	   '       an array.
	   Return solutions
	End Function

For example, say you have a pile of 18 coins comprised of pennies, nickels, dimes, and quarters totaling $2.23. The nickels and dimes total $.70, and the dimes and quarters total $2.00. The unknowns are the numbers of each of the four types of coins. The given information provides all you need to solve a set of four equations with four unknowns:

P + N + D + Q + = 18
P + 5N + 10D + 25Q = 223
0P + 5N + 10D + 0Q= 70
0P + 0N + 10D + 25Q= 200

The following code sets up the 4 x 4 matrix of coefficients and the array of constants, then passes these two arrays to MatrixHelper.SimultEq() to solve for the four unknowns:

	Dim matrixA(,) As Double = { _
	   {1, 1, 1, 1}, _
	   {1, 5, 10, 25}, _
	   {0, 5, 10, 0}, _
	   {0, 0, 10, 25}}
	Dim arrayB() As Double = {18, 223, 70, 200}
	Dim arrayC() As Double = _
	   MatrixHelper.SimultEq(matrixA, arrayB)

	MsgBox(MatrixHelper.MakeDisplayable(matrixA) & vbNewLine & _
	   vbNewLine & MatrixHelper.MakeDisplayable(arrayB) & _
	   vbNewLine & vbNewLine & _
	   "Simultaneous Equations Solution:" & _
	   vbNewLine & MatrixHelper.MakeDisplayable(arrayC))

As shown by the results displayed in Figure 6-34, there are three pennies, four nickels, five dimes, and six quarters in the pile.

Solving a set of four equations with four unknowns
Figure 6-34. Solving a set of four equations with four unknowns

The MatrixHelper.SimultEq() function is listed in the MatrixHelper module code, presented in the next recipe.

See Also

See the full MatrixHelper.vb listing in Recipe 6.35.

6.35. Listing of the MatrixHelper Class

Sample code folder: Chapter 06Matrix

Following is the full code for the MatrixHelper class described in Recipes 6.31, 6.32, 6.33 through 6.34:

	Module MatrixHelper
	   Public Function MakeDisplayable( _
	         ByVal sourceMatrix(,) As Double) As String
	      ' ----- Prepare a multi-line string that shows the
	      ' contents of a matrix, a 2D array.
	      Dim rows As Integer
	      Dim cols As Integer
	      Dim eachRow As Integer
	      Dim eachCol As Integer
	      Dim result As New System.Text.StringBuilder

	      ' ----- Process all rows of the matrix, generating one
	      '       output line per row.
	      rows = UBound(sourceMatrix, 1) + 1
	      cols = UBound(sourceMatrix, 2) + 1
	      For eachRow = 0 To rows - 1
	         ' ----- Process each column of the matrix on a
	         '       single row, separating values by commas.
	         If (eachRow > 0) Then result.AppendLine()
	         For eachCol = 0 To cols - 1
	            ' ----- Add a single matrix element to the output.
	            If (eachCol > 0) Then result.Append(",")
	            result.Append(sourceMatrix(eachRow, _
	               eachCol).ToString)
	         Next eachCol
	      Next eachRow

	      ' ----- Finished.
	      Return result.ToString
	   End Function

	   Public Function MakeDisplayable( _
	         ByVal sourceArray() As Double) As String
	      ' ----- Present an array as multiple lines of output.
	      Dim result As New System.Text.StringBuilder
	      Dim scanValue As Double

	      For Each scanValue In sourceArray
	         result.AppendLine(scanValue.ToString)
	      Next scanValue

	      Return result.ToString
	   End Function

	   Public Function Inverse( _
	         ByVal sourceMatrix(,) As Double) As Double(,)
	      ' ----- Build a new matrix that is the mathematical
	      '       inverse of the supplied matrix. Multiplying
	      '       a matrix and its inverse together will give
	      '       the identity matrix.
	      Dim eachCol As Integer
	      Dim eachRow As Integer
	      Dim rowsAndCols As Integer

	      ' ----- Determine the size of each dimension of the
	      '       matrix. Only square matrices can be inverted.
	      If (UBound(sourceMatrix, 1) <> _
	            UBound(sourceMatrix, 2)) Then
	         Throw New Exception("Matrix must be square.")
	      End If
	      Dim rank As Integer = UBound(sourceMatrix, 1)

	      ' ----- Clone a copy of the matrix (not just a
	      '       new reference).
	      Dim workMatrix(,) As Double = _
	         CType(sourceMatrix.Clone, Double(,))
	      ' ----- Variables used for backsolving.
	      Dim destMatrix(rank, rank) As Double
	      Dim rightHandSide(rank) As Double
	      Dim solutions(rank) As Double
	      Dim rowPivots(rank) As Integer
	      Dim colPivots(rank) As Integer

	      ' ----- Use LU decomposition to form a
	      '       triangular matrix.
	      workMatrix = FormLU(workMatrix, rowPivots, _
	         colPivots, rowsAndCols)

	      ' ----- Backsolve the triangular matrix to get the
	      '       inverted value for each position in the
	      '       final matrix.
	      For eachCol = 0 To rank
	         rightHandSide(eachCol) = 1
	         BackSolve(workMatrix, rightHandSide, solutions, _
	            rowPivots, colPivots)
	         For eachRow = 0 To rank
	            destMatrix(eachRow, eachCol) = solutions(eachRow)
	            rightHandSide(eachRow) = 0
	         Next eachRow
	      Next eachCol

	      ' ----- Return the inverted matrix result.
	      Return destMatrix
	   End Function

	   Public Function Determinant(ByVal sourceMatrix(,) _
	         As Double) As Double
	      ' ----- Calculate the determinant of a matrix.
	      Dim result As Double
	      Dim pivots As Integer
	      Dim count As Integer

	      ' ----- Only calculate the determinants of square
	      '       matrices.
	      If (UBound(sourceMatrix, 1) <> _
	            UBound(sourceMatrix, 2)) Then
	         Throw New Exception("Determinant only " & _
	            "calculated for square matrices.")
	      End If
	      Dim rank As Integer = UBound(sourceMatrix, 1)

	      ' ----- Make a copy of the matrix so we can work
	      '       inside of it.
	      Dim workMatrix(rank, rank) As Double
	      Array.Copy(sourceMatrix, workMatrix, _
	         sourceMatrix.Length)

	      ' ----- Use LU decomposition to form a
	      '       triangular matrix.
	      Dim rowPivots(rank) As Integer
	      Dim colPivots(rank) As Integer
	      workMatrix = FormLU(workMatrix, rowPivots, _
	         colPivots, count)

	      ' ----- Get the product at each of the pivot points.
	      result = 1
	      For pivots = 0 To rank
	         result *= workMatrix(rowPivots(pivots), _
	            colPivots(pivots))
	      Next pivots

	      ' ----- Determine the sign of the result using
	      '       LaPlace's formula.
	      result = (-1) ^ count * result
	      Return result
	   End Function

	   Public Function SimultEq( _
	         ByVal sourceEquations(,) As Double, _
	         ByVal sourceRHS() As Double) As Double()
	      ' ----- Use matrices to solve simultaneous equations.
	      Dim rowsAndCols As Integer

	      ' ----- The matrix must be square and the array size
	      '       must match.
	      Dim rank As Integer = UBound(sourceEquations, 1)
	      If (UBound(sourceEquations, 2) <> rank) Or _
	            (UBound(sourceRHS, 1) <> rank) Then
	         Throw New Exception( _
	            "Size problem for simultaneous equations.")
	      End If

	      ' ----- Create some arrays for doing all of the work.
	      Dim coefficientMatrix(rank, rank) As Double
	      Dim rightHandSide(rank) As Double
	      Dim solutions(rank) As Double
	      Dim rowPivots(rank) As Integer
	      Dim colPivots(rank) As Integer

	      ' ----- Make copies of the original matrices so we don't
	      '       mess them up.
	      Array.Copy(sourceEquations, coefficientMatrix, _
	         sourceEquations.Length)
	      Array.Copy(sourceRHS, rightHandSide, sourceRHS.Length)

	      ' ----- Use LU decomposition to form a triangular matrix.
	      coefficientMatrix = FormLU(coefficientMatrix, _
	         rowPivots, colPivots, rowsAndCols)

	      ' ----- Find the unique solution for the upper-triangle.
	      BackSolve(coefficientMatrix, rightHandSide, solutions, _
	         rowPivots, colPivots)
	      ' ----- Return the simultaneous equations result in
	      '       an array.
	      Return solutions
	   End Function

	   Private Function FormLU(ByVal sourceMatrix(,) As Double, _
	         ByRef rowPivots() As Integer, _
	         ByRef colPivots() As Integer, _
	         ByRef rowsAndCols As Integer) As Double(,)
	      ' ----- Perform an LU (lower and upper) decomposition
	      '       of a matrix, a modified form of Gaussian
	      '       elimination.
	      Dim eachRow As Integer
	      Dim eachCol As Integer
	      Dim pivot As Integer
	      Dim rowIndex As Integer
	      Dim colIndex As Integer
	      Dim bestRow As Integer
	      Dim bestCol As Integer
	      Dim rowToPivot As Integer
	      Dim colToPivot As Integer
	      Dim maxValue As Double
	      Dim testValue As Double
	      Dim oldMax As Double
	      Const Deps As Double = 0.0000000000000001

	      ' ----- Determine the size of the array.
	      Dim rank As Integer = UBound(sourceMatrix, 1)
	      Dim destMatrix(rank, rank) As Double
	      Dim rowNorm(rank) As Double
	      ReDim rowPivots(rank)
	      ReDim colPivots(rank)

	      ' ----- Make a copy of the array so we don't mess it up.
	      Array.Copy(sourceMatrix, destMatrix, _
	         sourceMatrix.Length)

	      ' ----- Initialize row and column pivot arrays.
	      For eachRow = 0 To rank
	         rowPivots(eachRow) = eachRow
	         colPivots(eachRow) = eachRow
	         For eachCol = 0 To rank
	            rowNorm(eachRow) += _
	               Math.Abs(destMatrix(eachRow, eachCol))
	         Next eachCol
	         If (rowNorm(eachRow) = 0) Then
	            Throw New Exception( _
	               "Cannot invert a singular matrix.")
	         End If
	      Next eachRow

	      ' ----- Use Gauss-Jordan elimination on the matrix rows.
	      For pivot = 0 To rank - 1
	         maxValue = 0
	      For eachRow = pivot To rank
	         rowIndex = rowPivots(eachRow)
	         For eachCol = pivot To rank
	            colIndex = colPivots(eachCol)
	            testValue = Math.Abs(destMatrix(rowIndex, _
	               colIndex)) / rowNorm(rowIndex)
	            If (testValue > maxValue) Then
	               maxValue = testValue
	               bestRow = eachRow
	               bestCol = eachCol
	            End If
	         Next eachCol
	      Next eachRow

	      ' ----- Detect a singular, or very nearly
	      '       singular, matrix.
	      If (maxValue = 0) Then
	         Throw New Exception( _
	            "Singular matrix used for LU.")
	      ElseIf (pivot > 1) Then
	         If (maxValue < (Deps * oldMax)) Then
	            Throw New Exception( _
	               "Non-invertible matrix used for LU.")
	         End If
	      End If
	      oldMax = maxValue

	      ' ----- Swap row pivot values for the best row.
	      If (rowPivots(pivot) <> rowPivots(bestRow)) Then
	         rowsAndCols += 1
	         Swap(rowPivots(pivot), rowPivots(bestRow))
	      End If

	      ' ----- Swap column pivot values for the best column.
	      If (colPivots(pivot) <> colPivots(bestCol)) Then
	         rowsAndCols += 1
	         Swap(colPivots(pivot), colPivots(bestCol))
	      End If

	      ' ----- Work with the current pivot points.
	      rowToPivot = rowPivots(pivot)
	      colToPivot = colPivots(pivot)

	      ' ----- Modify the remaining rows from the
	      '       pivot points.
	      For eachRow = (pivot + 1) To rank
	         rowIndex = rowPivots(eachRow)
	         destMatrix(rowIndex, colToPivot) = _
	            -destMatrix(rowIndex, colToPivot) / _
	            destMatrix(rowToPivot, colToPivot)
	         For eachCol = (pivot + 1) To rank
	            colIndex = colPivots(eachCol)
	            destMatrix(rowIndex, colIndex) += _
	               destMatrix(rowIndex, colToPivot) * _
	               destMatrix(rowToPivot, colIndex)
	         Next eachCol
	      Next eachRow
	   Next pivot

	   ' ----- Detect a non-invertible matrix.
	   If (destMatrix(rowPivots(rank), _
	         colPivots(rank)) = 0) Then
	       Throw New Exception( _
	         "Non-invertible matrix used for LU.")
	   ElseIf (Math.Abs(destMatrix(rowPivots(rank), _
	         colPivots(rank))) / rowNorm(rowPivots(rank))) < _
	         (Deps * oldMax) Then
	       Throw New Exception( _
	         "Non-invertible matrix used for LU.")
	   End If

	   ' ----- Success. Return the LU triangular matrix.
	   Return destMatrix
	End Function

	Private Sub Swap(ByRef firstValue As Integer, _
	      ByRef secondValue As Integer)
	   ' ----- Reverse the values of two reference integers.
	   Dim holdValue As Integer
	   holdValue = firstValue
	   firstValue = secondValue
	   secondValue = holdValue
	End Sub

	Private Sub BackSolve(ByVal sourceMatrix(,) As Double, _
	      ByVal rightHandSide() As Double, _
	      ByVal solutions() As Double, _
	      ByRef rowPivots() As Integer, _
	      ByRef colPivots() As Integer)
	   ' ----- Solve an upper-right-triangle matrix.
	   Dim pivot As Integer
	   Dim rowToPivot As Integer
	   Dim colToPivot As Integer
	   Dim eachRow As Integer
	   Dim eachCol As Integer
	   Dim rank As Integer = UBound(sourceMatrix, 1)

	   ' ----- Work through all pivot points. This section
	   '       builds the "B" in the AX=B formula.
	   For pivot = 0 To (rank - 1)
	      colToPivot = colPivots(pivot)
	      For eachRow = (pivot + 1) To rank
	         rowToPivot = rowPivots(eachRow)
	         rightHandSide(rowToPivot) += _
	            sourceMatrix(rowToPivot, colToPivot) _
	            * rightHandSide(rowPivots(pivot))
	      Next eachRow
	   Next pivot
	   ' ----- Now solve for each X using the general formula
	   '       x(i) = (b(i) - summation(a(i,j)x(j)))/a(i,i)
	   For eachRow = rank To 0 Step -1
	      colToPivot = colPivots(eachRow)
	      rowToPivot = rowPivots(eachRow)
	      solutions(colToPivot) = rightHandSide(rowToPivot)
	      For eachCol = (eachRow + 1) To rank
	         solutions(colToPivot) -= _
	            sourceMatrix(rowToPivot, colPivots(eachCol)) _
	            * solutions(colPivots(eachCol))
	      Next eachCol
	      solutions(colToPivot) /= sourceMatrix(rowToPivot, _
	         colToPivot)
	    Next eachRow
	  End Sub
	End Module
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.225.234.28