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 rich-text 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 ill-conditioned 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 65-93.
An ill-conditioned 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 higher-dimension sparse containers.
Back
in the late 1970s, I ran this algorithm on a DEC-10 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 Ill-Conditioned
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.69E-14;
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 rich-text box. The solution is written into the rich-text 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.69E-14;
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 non-extensible 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 multi-character variable names.
- I added an image to the article.