Please read the section titled Known Issues further below before reporting bugs for this application.
Introduction
Program csEquationSolver
solves simultaneous equations. The program has a single richtext control that allows loading text, saving text, and printing text. The text can be anything, but is intended to be a set of simultaneous equations.
The user enters the equations on the input form and then uses the Operation/Solve menu item to solve the equations.
The purpose of this is to demonstrate using the generic SparseArray
class for a vector, the generic Sparse2DMatrix
class as a matrix, and the LinearEquationSolver
class. The code uses a crude parser to parse simple equations. It's crude because it requires a somewhat rigid input format and doesn't support parenthesis or math functions.
The user enters equations in the form using the following format, which allows using the addition and subtraction sign to combine terms of the form:
[number][variable]
The square brackets indicate the the number or variable are optionals, but there must be at least one of these in a term. There must also be a single equals sign in the equations and at least one term on each side of the equals sign.
Either the number or variable are optional and as many terms as desired can be combined using either the plus sign or a minus sign. The number can contain a decimal point and an exponent. Variables can only contain alphabetic characters or the underscore character. Floating point exponents are preceded by the ^
character instead of the usual E
character to avoid any ambiguity with variable names. The following line shows a floating point number equal to 2.3 million.
X = 2.3^6
An equation must contain an equals sign. An equation ends at a newline character.
An example set of equations is:
3 X + 4 Y = 5 Z
X + Z = 10 Y
X + Z = 42.5
The program produces the solution for those equations:
X = 114.75
Y = 4.25
Z = 72.25
Another example set of equations might be:
MARYS_AGE=2BOBS_AGE
BOBS_AGE = 3 CATHYS_AGE
CATHYS_AGE = 4 D
D = 2
or another example:
HEIGHT = 5 + 10
Below is the program immediately after solving three equations. Once solved, trying to solve the equations again will fail because there are then 6 equations and only 3 variables! If one of the original equations is wrong, delete the solution, edit that equation, and use the Operations/Solve menu item to solve the equations again.
Background
When I was in college, I wrote a circuit analysis program in Fortran. I needed a way to solve simultaneous equations, and I stumbled on the following book and algorithm:
"The solution of illconditioned linear equations", by J. H. Wilkinson, "Mathematical Methods For Digital Computers" Volume 2, Edited by Anthony Ralston and Herbert S. Wilf, 1967, John Wiley and Sons, pages 6593.
An illconditioned set of equations is a set of equations that is either difficult or impossible to solve using the given floating point precision.
I was lucky to stumble on this particular reference. While this book gives a standard implementation of Gaussian Elimination with partial pivoting, as is taught in Computer Science Linear Algebra courses, this algorithm also determines whether an accurate solution can be found. This is done using both the matrix norms and a constant that is set based on the number of bits in the mantissa of a floating point number.
I've rewritten this algorithm several times. I wrote this in
Fortran, C++ using simple arrays, again in C++ using a sparse container classes, and finally in C# using a SparseArray
and a Sparse2DMatrix
. The SparseArray
and Sparse2DMatrix
are in another codeproject article I posted earlier that also provides other higherdimension sparse containers.
Back
in the late 1970s, I ran this algorithm on a DEC10 computer and solved 1000
equations with 1000 variables in about 30 seconds. Today, with that original C code, a problem that size on a PC runs in the
blink of an eye. This code is slower because it's not C, but it is still very fast.
File List
 Program.cs  The main program file.
 LinearEquationParser.cs  Parses the equations to populate a matrix and a vector and saves a list of the variable names.
 LinearEquationParserStatusInterpreter.cs  Provides text messages based on parser status values.
 LinearEquationSolver.cs  Solve the matrix equations.
 Sparse2DMatrix.cs  A sparse 2 dimensional matrix.
 ComparableTuple2.cs  Use by the sparse 2 dimensional matrix to store indices.
 SparseArray.cs  A sparse vector
 csEquationsSolver.ico  Stores several sizes of icons for the application.
 EquationsSolverForm.cs  The main form class that hosts controls.
 EquationsSolverForm.Designer.cs  Form information
 EquationsSolverForm.resx  Form resources
 app.config  Contains XML configuration data.
 csEquationSolver.csproj  The Visual Studio C# project file
 csEquationSolver.sln  The Visual Studio C# solution file
Property Files
 AssemblyInfo.cs  Stores text and a GUID for the .NET program assembly
 Resources.Designer.cs  Stores resources design information for the program.
 Resources.resx  Stores resources for the program.
 Settings.Designer.cs  Stores settings design information for the program.
 Settings.settings  Stores settings for the program.
About the Linear Equation Solver Class
A set of Linear equations are represented by the matrix equation:
aMatrix xVector = bVector
The aMatrix and bVector are given, and the xVector is the solution.
The
article makes the second terms, xVector and bVector matrices, so, after
performing certain calculations on the aMatrix, the original algorithm
could can solve multiple sets of equations with several different
bVectors in a matrix efficiently. I simplified algorithm to only solves a single set of equations by removing a loop in the final step of the algorithm.
The first example set of equations given above can be rewritten as:
3 X + 4 Y + 5 Z = 0
1 X  10 Y + 1 Z = 0
1 X + 0 Y + 1 Z = 42.5
The matrix form of these equations is:
 3 4 5   X   0 
 1 10 1   Y  =  0 
 1 0 1   Z   42.5 
The aMatrix is the square matrix on the left. The bVector is on the right.
The xVector, which contains the variable names, which are the unknowns, is in the middle.
To solve these equations, call the Solver method of the LinearEquationSolver class. The class signature is:
public static LinearEquationSolverStatus Solve(int numberOfEquations,
Sparse2DMatrix<int, int, double> aMatrix,
SparseArray<int, double> bVector,
SparseArray<int, double> xVector)
Note the different order of the arguments from the equation above. The xVector, which will store the solution, is the last argument. The number of equations is the size of one dimension of the square matrix.
The program will also indicate if a set of equations is 'singular' to working accuracy. A
singular set of equations has no single solution because two or more
equations are merely a multiple of the other equation, such as:
X + Y = 7
2X + 2Y = 36
Even if the second equation was, "2X + 2Y = 14", so it was consistent with the first equation, there is no single solution to the two equations, and the program would report that the equations were singular to working accuracy.
An Important Parameter For Determining If Equations Are IllConditioned
The LinearEquationSolver class has a constant that is set based on the number of bits in the mantissa of a double precision floating point number. If you port the linear equation solver to another platform, then be sure to adjust this constant. The LinearEquationSolver code contains the following regarding this issue:
public static readonly double s_smallFloatingPointValue = 5.69E14;
The Solver routine in file EquationsSolverForm.cs
The Solver
method is the primary method in the program. This is called when the user clicks on the "Solve" submenu of the "Operations" menu to solve the set of equations in the richtext box. The solution is written into the richtext box, so trying to solve again without deleting the solution text will result in an error message.
There are three steps in the Solve method. First a LinearEquationParser.Parse method is called to fill in the aMatrix and bVector, and then the LinearEquationSolver.Solve method is called to solve the equations. Finally the solution data is output into the rich text control or an error message is displayed.
See file EquationsSolverForm.cs for the rest of the form code.
private void Solve()
{
mainToolStripStatusLabel.Text = Properties.Resources.IDS_SOLVING_EQUATIONS;
Sparse2DMatrix<int, int, double> aMatrix = new Sparse2DMatrix<int, int, double>();
SparseArray<int, double> bVector = new SparseArray<int, double>();
SparseArray<string, int> variableNameIndexMap = new SparseArray<string, int>();
int numberOfEquations = 0;
LinearEquationParser parser = new LinearEquationParser();
LinearEquationParserStatus parserStatus = LinearEquationParserStatus.Success;
foreach (string inputLine in equationsRichTextBox.Lines)
{
parserStatus = parser.Parse(inputLine,
aMatrix,
bVector,
variableNameIndexMap,
ref numberOfEquations);
if (parserStatus != LinearEquationParserStatus.Success)
{
break;
}
}
string mainStatusBarText = Properties.Resources.IDS_EQUATIONS_SOLVED;
if (parserStatus == LinearEquationParserStatus.Success)
{
if (numberOfEquations == variableNameIndexMap.Count)
{
SparseArray<int, double> xVector = new SparseArray<int, double>();
LinearEquationSolverStatus solverStatus =
LinearEquationSolver.Solve(numberOfEquations,
aMatrix,
bVector,
xVector);
if (solverStatus == LinearEquationSolverStatus.Success)
{
string solutionString = "";
foreach (KeyValuePair<string, int> pair in variableNameIndexMap)
{
solutionString += string.Format("{0} = {1}", pair.Key, xVector[pair.Value]);
solutionString += "\n";
}
equationsRichTextBox.Text += "\n" + solutionString;
}
else if (solverStatus == LinearEquationSolverStatus.IllConditioned)
{
mainStatusBarText = Properties.Resources.IDS_ILL_CONDITIONED_SYSTEM_OF_EQUATIONS;
}
else if (solverStatus == LinearEquationSolverStatus.Singular)
{
mainStatusBarText = Properties.Resources.IDS_SINGULAR_SYSTEM_OF_EQUATIONS;
}
}
else if (numberOfEquations < variableNameIndexMap.Count)
{
mainStatusBarText = string.Format(Properties.Resources.IDS_TOO_FEW_EQUATIONS,
numberOfEquations, variableNameIndexMap.Count);
}
else if (numberOfEquations > variableNameIndexMap.Count)
{
mainStatusBarText = string.Format(Properties.Resources.IDS_TOO_MANY_EQUATIONS,
numberOfEquations, variableNameIndexMap.Count);
}
}
else
{
mainStatusBarText = LinearEquationParserStatusInterpreter.GetStatusString(parserStatus);
}
mainToolStripStatusLabel.Text = mainStatusBarText;
}
}
The LinearEquationSolver Class in file LinearEquationSolver.cs
This method implements Gaussian Elimination with partial pivoting, and then does an iterative refining of the solution vector by computing residuals. For more detail, consult the reference I listed in the Background section of this article above.
using System;
using System.Text;
using SparseCollections;
using Mathematics;
namespace Mathematics
{
enum LinearEquationSolverStatus
{
Success,
IllConditioned,
Singular,
};
class LinearEquationSolver
{
public static readonly double s_smallFloatingPointValue = 5.69E14;
public static LinearEquationSolverStatus Solve(int numberOfEquations,
Sparse2DMatrix<int, int, double> aMatrix,
SparseArray<int, double> bVector,
SparseArray<int, double> xVector)
{
Sparse2DMatrix<int, int, double> aMatrixCopy = new Sparse2DMatrix<int, int, double>(aMatrix);
SparseArray<int, double> rowMaximumVector = new SparseArray<int, double>();
int i = 0;
for (i = 0; i < numberOfEquations; i++)
{
double temp = 0.0;
for (int j = 0; j < numberOfEquations; j++)
{
double test = Math.Abs(aMatrix[i, j]);
if (test > temp)
{
temp = test;
}
}
rowMaximumVector[i] = temp;
if (temp == 0.0)
{
return LinearEquationSolverStatus.Singular;
}
}
SparseArray<int, int> pivotRowArray = new SparseArray<int, int>();
for (int r = 0; r < numberOfEquations; r++)
{
double maximumValue = 0.0;
int rowMaximumValueIndex = r;
double temp;
for (i = r; i < numberOfEquations; i++)
{
temp = aMatrixCopy[i, r];
for (int j = 0; j < r; j++)
{
temp = temp  aMatrixCopy[i, j] * aMatrixCopy[j, r];
}
aMatrixCopy[i, r] = temp;
double test = Math.Abs(temp / rowMaximumVector[i]);
if (test > maximumValue)
{
maximumValue = test;
rowMaximumValueIndex = i;
}
}
if (maximumValue == 0.0)
{
return LinearEquationSolverStatus.IllConditioned;
}
rowMaximumVector[rowMaximumValueIndex] = rowMaximumVector[r];
pivotRowArray[r] = rowMaximumValueIndex;
for (i = 0; i < numberOfEquations; i++)
{
temp = aMatrixCopy[r, i];
aMatrixCopy[r, i] = aMatrixCopy[rowMaximumValueIndex, i];
aMatrixCopy[rowMaximumValueIndex, i] = temp;
}
for (i = r + 1; i < numberOfEquations; i++)
{
temp = aMatrixCopy[r, i];
for (int j = 0; j < r; j++)
{
temp = temp  aMatrixCopy[r, j] * aMatrixCopy[j, i];
}
aMatrixCopy[r, i] = temp / aMatrixCopy[r, r];
}
}
SparseArray<int, double> residualVector = new SparseArray<int, double>();
for (i = 0; i < numberOfEquations; i++)
{
xVector[i] = 0.0;
residualVector[i] = bVector[i];
}
int iteration = 0;
bool notConvergedFlag = true;
do
{
for (i = 0; i < numberOfEquations; i++)
{
int pivotRowIndex = pivotRowArray[i];
double temp = residualVector[pivotRowIndex];
residualVector[pivotRowIndex] = residualVector[i];
for (int j = 0; j < i; j++)
{
temp = temp  aMatrixCopy[i, j] * residualVector[j];
}
residualVector[i] = temp / aMatrixCopy[i, i];
}
for (i = numberOfEquations  1; i >= 0; i)
{
double temp = residualVector[i];
for (int j = i + 1; j < numberOfEquations; j++)
{
temp = temp  aMatrixCopy[i, j] * residualVector[j];
}
residualVector[i] = temp;
}
double normOfX = 0.0;
double normOfError = 0.0;
for (i = 0; i < numberOfEquations; i++)
{
double test = Math.Abs(xVector[i]);
if (test > normOfX)
{
normOfX = test;
}
test = Math.Abs(residualVector[i]);
if (test > normOfError)
{
normOfError = test;
}
}
if (iteration != 0)
{
if ((iteration == 1) && (normOfError / normOfX > 0.5))
{
return LinearEquationSolverStatus.IllConditioned;
}
notConvergedFlag = normOfError / normOfX >= s_smallFloatingPointValue;
#if DEBUGCODE
double normRatioForDebug = normOfError / normOfX;
#endif
}
for (i = 0; i < numberOfEquations; i++)
{
xVector[i] = xVector[i] + residualVector[i];
}
for (i = 0; i < numberOfEquations; i++)
{
double temp = bVector[i];
for (int j = 0; j < numberOfEquations; j++)
{
temp = temp  aMatrix[i, j] * xVector[j];
}
residualVector[i] = temp;
}
iteration++;
}
while (notConvergedFlag);
return LinearEquationSolverStatus.Success;
}
}
}
The (crude) LinearEquationParser class in file LinearEquationParser.cs
using System;
using System.Text;
using SparseCollections;
namespace Mathematics
{
public enum LinearEquationParserStatus
{
Success,
SuccessNoEquation,
ErrorIllegalEquation,
ErrorNoEqualSign,
ErrorMultipleEqualSigns,
ErrorNoTermBeforeEqualSign,
ErrorNoTermAfterEqualSign,
ErrorNoTermEncountered,
ErrorNoVariableInEquation,
ErrorMultipleDecimalPoints,
ErrorTooManyDigits,
ErrorMissingExponent,
ErrorIllegalExponent,
}
internal enum LinearEquationParserState
{
ParseTerm,
ParseOperator
};
public class LinearEquationParser
{
private static readonly int m_maximumNumberLength = 20;
private int m_startPosition;
private int m_equationIndex;
private LinearEquationParserState m_parserState;
private bool m_negativeOperatorFlag;
private bool m_equalSignInEquationFlag;
private bool m_atLeastOneVariableInEquationFlag;
private bool m_termBeforeEqualSignExistsFlag;
private bool m_termAfterEqualSignExistsFlag;
public LinearEquationParserStatus LastStatusValue
{
get;
set;
}
public int ErrorPosition
{
get;
set;
}
public LinearEquationParser()
{
Reset();
}
~LinearEquationParser()
{
}
public LinearEquationParserStatus Parse(string inputLine,
Sparse2DMatrix<int, int, double> aMatrix,
SparseArray<int, double> bVector,
SparseArray<string, int> variableNameIndexMap,
ref int numberOfEquations)
{
inputLine.TrimEnd(null);
int positionIndex = 0;
SetLastStatusValue(LinearEquationParserStatus.Success, positionIndex);
SkipSpaces(inputLine, ref positionIndex);
m_startPosition = positionIndex;
bool operatorFoundLast = false;
while (positionIndex < inputLine.Length)
{
if (m_parserState == LinearEquationParserState.ParseTerm)
{
SkipSpaces(inputLine, ref positionIndex);
if (positionIndex < inputLine.Length)
{
if (GetTerm(inputLine,
ref positionIndex,
aMatrix,
bVector,
variableNameIndexMap))
{
m_parserState = LinearEquationParserState.ParseOperator;
operatorFoundLast = false;
}
else
{
if (LastStatusValue == LinearEquationParserStatus.Success)
{
SetLastStatusValue(LinearEquationParserStatus.ErrorIllegalEquation,
positionIndex);
}
break;
}
}
else
{
break;
}
}
else if (m_parserState == LinearEquationParserState.ParseOperator)
{
SkipSpaces(inputLine, ref positionIndex);
if (positionIndex < inputLine.Length)
{
if (GetOperator(inputLine, ref positionIndex))
{
m_parserState = LinearEquationParserState.ParseTerm;
operatorFoundLast = true;
}
else
{
if (LastStatusValue != LinearEquationParserStatus.Success)
{
if (positionIndex == m_startPosition)
{
SetLastStatusValue(LinearEquationParserStatus.ErrorIllegalEquation,
positionIndex);
}
}
break;
}
}
}
}
if ((positionIndex >= inputLine.Length) && (positionIndex > 0) && (!operatorFoundLast))
{
ResetForNewEquation();
numberOfEquations = m_equationIndex;
}
return LastStatusValue;
}
void Reset()
{
m_startPosition = 0;
ErrorPosition = 0;
LastStatusValue = LinearEquationParserStatus.Success;
m_negativeOperatorFlag = false;
m_equalSignInEquationFlag = false;
m_atLeastOneVariableInEquationFlag = false;
m_termBeforeEqualSignExistsFlag = false;
m_termAfterEqualSignExistsFlag = false;
m_parserState = LinearEquationParserState.ParseTerm;
m_equationIndex = 0;
}
private LinearEquationParserStatus GetEquationStatus()
{
LinearEquationParserStatus status = LinearEquationParserStatus.Success;
if ((!m_equalSignInEquationFlag)
&& (!m_termBeforeEqualSignExistsFlag)
&& (!m_termAfterEqualSignExistsFlag)
&& (!m_atLeastOneVariableInEquationFlag))
{
status = LinearEquationParserStatus.SuccessNoEquation;
}
else if (!m_equalSignInEquationFlag)
{
status = LinearEquationParserStatus.ErrorNoEqualSign;
}
else if (!m_termBeforeEqualSignExistsFlag)
{
status = LinearEquationParserStatus.ErrorNoTermBeforeEqualSign;
}
else if (!m_termAfterEqualSignExistsFlag)
{
status = LinearEquationParserStatus.ErrorNoTermAfterEqualSign;
}
else if (!m_atLeastOneVariableInEquationFlag)
{
status = LinearEquationParserStatus.ErrorNoVariableInEquation;
}
else
{
status = LinearEquationParserStatus.Success;
}
return status;
}
private void ResetForNewEquation()
{
m_startPosition = 0;
m_negativeOperatorFlag = false;
m_equalSignInEquationFlag = false;
m_atLeastOneVariableInEquationFlag = false;
m_termBeforeEqualSignExistsFlag = false;
m_termAfterEqualSignExistsFlag = false;
m_parserState = LinearEquationParserState.ParseTerm;
m_equationIndex++;
}
private bool GetTerm(string inputLine,
ref int positionIndex,
Sparse2DMatrix<int, int, double> aMatrix,
SparseArray<int, double> bVector,
SparseArray<string, int> variableNameIndexMap)
{
bool numberIsNegativeFlag = false;
GetSign(inputLine,
ref positionIndex,
ref numberIsNegativeFlag);
SkipSpaces(inputLine, ref positionIndex);
string numberString = "";
bool haveNumberFlag = GetNumber(inputLine,
ref positionIndex,
ref numberString);
if (LastStatusValue != LinearEquationParserStatus.Success)
{
return false;
}
if (haveNumberFlag)
{
if (positionIndex < inputLine.Length)
{
if (inputLine[positionIndex] == '^')
{
positionIndex++;
bool negativeExponentFlag = false;
GetSign(inputLine,
ref positionIndex,
ref negativeExponentFlag);
string exponentString = "";
if (GetNumber(inputLine,
ref positionIndex,
ref exponentString))
{
int exponentLength = exponentString.Length;
if (exponentLength <= 2)
{
bool exponent_error_flag = false;
for (int i = 0; i < exponentLength; ++i)
{
if (!Char.IsDigit(exponentString[i]))
{
exponent_error_flag = true;
}
}
if (!exponent_error_flag)
{
numberString += 'E';
if (negativeExponentFlag)
{
numberString += '';
}
numberString += exponentString;
}
else
{
SetLastStatusValue(LinearEquationParserStatus.ErrorIllegalExponent,
positionIndex);
return false;
}
}
else
{
SetLastStatusValue(LinearEquationParserStatus.ErrorIllegalExponent,
positionIndex);
return false;
}
}
else
{
SetLastStatusValue(LinearEquationParserStatus.ErrorMissingExponent,
positionIndex);
return false;
}
}
}
}
SkipSpaces(inputLine, ref positionIndex);
string variableName = "";
bool haveVariableNameFlag = GetVariableName(inputLine,
ref positionIndex,
ref variableName);
bool negativeFlag =
m_equalSignInEquationFlag ^ m_negativeOperatorFlag ^ numberIsNegativeFlag;
double value = 0.0;
if (haveNumberFlag)
{
value = Convert.ToDouble(numberString);
if (negativeFlag)
{
value = value;
}
}
else
{
value = 1.0;
if (negativeFlag)
{
value = value;
}
}
bool haveTermFlag = false;
if (haveVariableNameFlag)
{
haveTermFlag = true;
m_atLeastOneVariableInEquationFlag = true;
int variableNameIndex = 0;
if (!variableNameIndexMap.TryGetValue(variableName, out variableNameIndex))
{
variableNameIndex = variableNameIndexMap.Count;
variableNameIndexMap[variableName] = variableNameIndex;
}
aMatrix[m_equationIndex, variableNameIndex] =
aMatrix[m_equationIndex, variableNameIndex] + value;
}
else if (haveNumberFlag)
{
haveTermFlag = true;
bVector[m_equationIndex] = bVector[m_equationIndex]  value;
}
else
{
haveTermFlag = false;
SetLastStatusValue(LinearEquationParserStatus.ErrorNoTermEncountered,
positionIndex);
return false;
}
if (haveTermFlag)
{
if (m_equalSignInEquationFlag)
{
m_termAfterEqualSignExistsFlag = true;
}
else
{
m_termBeforeEqualSignExistsFlag = true;
}
}
SkipSpaces(inputLine, ref positionIndex);
return haveTermFlag;
}
private bool GetSign(string inputLine,
ref int positionIndex,
ref bool negativeFlag)
{
bool haveSignFlag = false;
negativeFlag = false;
if (positionIndex < inputLine.Length)
{
char c = inputLine[positionIndex];
if (c == '+')
{
haveSignFlag = true;
positionIndex ++;
}
else if (c == '')
{
haveSignFlag = true;
negativeFlag = true;
positionIndex ++;
}
}
return haveSignFlag;
}
private bool GetNumber(string inputLine,
ref int positionIndex,
ref string numberString)
{
int decimalPointCount = 0;
int digitLength = 0;
bool haveNumberFlag = false;
bool continueFlag = positionIndex < inputLine.Length;
while (continueFlag)
{
Char c = inputLine[positionIndex];
continueFlag = (c == '.');
if (Char.IsDigit(c))
{
if (++digitLength > m_maximumNumberLength)
{
SetLastStatusValue(LinearEquationParserStatus.ErrorTooManyDigits,
positionIndex);
return false;
}
haveNumberFlag = true;
numberString += c;
positionIndex++;
continueFlag = positionIndex < inputLine.Length;
}
else
{
continueFlag = c == '.';
if (continueFlag)
{
if (++decimalPointCount > 1)
{
SetLastStatusValue(LinearEquationParserStatus.ErrorMultipleDecimalPoints,
positionIndex);
return false;
}
numberString += c;
positionIndex++;
continueFlag = positionIndex < inputLine.Length;
}
}
}
if (numberString.Length > m_maximumNumberLength)
{
SetLastStatusValue(LinearEquationParserStatus.ErrorTooManyDigits,
positionIndex);
return false;
}
return haveNumberFlag;
}
private bool GetVariableName(string inputLine,
ref int positionIndex,
ref string variableName)
{
bool haveVariableNameFlag = false;
bool continueFlag = positionIndex < inputLine.Length;
while (continueFlag)
{
Char c = inputLine[positionIndex];
continueFlag = (Char.IsLetter(c)  c == '_');
if (continueFlag)
{
haveVariableNameFlag = true;
variableName += c;
positionIndex++;
continueFlag = positionIndex < inputLine.Length;
}
}
return haveVariableNameFlag;
}
private bool GetOperator(string inputLine, ref int positionIndex)
{
SkipSpaces(inputLine, ref positionIndex);
m_negativeOperatorFlag = false;
bool haveEqualSignFlag = false;
if (positionIndex < inputLine.Length)
{
if (inputLine[positionIndex] == '=')
{
if (!m_equalSignInEquationFlag)
{
m_equalSignInEquationFlag = true;
haveEqualSignFlag = true;
positionIndex++;
}
else
{
SetLastStatusValue(LinearEquationParserStatus.ErrorMultipleEqualSigns,
positionIndex);
return false;
}
}
}
bool haveSignFlag = GetSign(inputLine,
ref positionIndex,
ref m_negativeOperatorFlag);
return haveSignFlag  haveEqualSignFlag;
}
private void SkipSpaces(string inputLine, ref int positionIndex)
{
bool continueFlag = positionIndex < inputLine.Length;
while (continueFlag)
{
char c = inputLine[positionIndex];
continueFlag = Char.IsWhiteSpace(c);
if (continueFlag)
{
positionIndex++;
continueFlag = positionIndex < inputLine.Length;
}
}
}
private void SetLastStatusValue(LinearEquationParserStatus status,
int positionIndex)
{
LastStatusValue = status;
ErrorPosition = positionIndex;
}
}
}
Known Issues
 The printing code is not complete. The program can print in portrait mode, but many of the other print settings are not yet hooked up. As I improve the printing code, I will upload the new code.
 The parser is crude, but the purpose of this program is to both to provide a simple equation solver and to provide the LinearEquationSolver class, and associated classes, for use in other applications. I might improve the parser someday to be a more general formula parser, but for now it meets my needs.
 The Operations/Solve menu item was made with the intent to add more features to the program. I am conflicted as to whether a nonextensible toolbar button would be better. I welcome ideas on this.
 The code needs more comments. I will be improving the code and uploading the code for this article. Other than printing, I do not expect major changes in functionality, just improvements to the code. Check back for updates.
Points of Interest
I wrote this in 2010, originally using Visual Studio 2005. I'm using Visual Studio 2008 now. While the user interface (UI) is very simple for this application, I'm still struck by how easy it was to create. The Form designer in Visual Studio made producing the UI very simple, particularly when compared to C++, whether using regular Windows programming or MFC.
History

Initial post
 Updated the Sparse2DMatrix.cs file to remove uneeded code. Fixed an error in the Introduction that implied semicolons can be used for input. They cannnot be used. Added that underscores can be used in variable names and gave an example with multicharacter variable names.
 I added an image to the article.