1. Problem Statement
Create a simple calculation module (and related tests) that can process arithmetic commands with
the input specification:
cmd::= expression* signed_decimal
expresion::= signed_decimal ' '* operator ' '* eg. 2.3 * + * 2.3
operator::= '+' | '-' | '*'
signed_decimal::= '-'? decimal_number
decimal_number::= digits | digits ‘.’ digits
digits::= '0' | non_zero_digit digits*
non_zero_digit::= '1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'
Solution
Above expression example is of Backus–Naur Form. It is mainly used to define syntax of languages in
compiler design world. There are certain readymade solutions are available to calculate expressions.
Microsoft also introduced Expression tree through which we can compile complex expression using
Lambda expressions. We can easily use BinaryExpression, MethodCallExpression,
ConstantExpression etc to compile expression to get required result.
Here I’ll take simple approach to solve this coding task without use of .Net Expression Tree with
possible failure and pass test cases.
NOTE: I tested unit tests in MS-Test because I don’t have n-Unit Framework. I changed on my
assumption to NUnit.
using System;
using System.Linq;
using NUnit.Framework;
namespace Calculator
{
///<summary>
/// Basic calc engine for handling +, -, * operations.
///</summary>
publicclassCalcEngine
{
char[] operatorList = newchar[3] { '+', '-', '*' };
///<summary>
/// Calculate arithmatic Expression
///</summary>
///<param name="expression"></param>
///<returns></returns>
publicdecimal Calculate(string expression)
{
foreach (var oper in operatorList)
{
if (expression.Contains(oper))
{
var parts = expression.Split(oper);
int i = 1;
decimal result = Calculate(parts[0].Trim());
2. while (i < parts.Length)
{
switch (oper)
{
case'+':
result += Calculate(parts[i].Trim());
break;
case'-':
result -= Calculate(parts[i].Trim());
break;
case'*':
result *= Calculate(parts[i].Trim());
break;
}
i++;
}
return result;
}
}
decimal value = 0;
//Note: we can also use decimal.Parse and can catch exception in catch block but it is
expensive task to wait for system exception
//better to use TryParse and then throw custom exception
if (expression.Trim().Length > 0 && !decimal.TryParse(expression,
System.Globalization.NumberStyles.Float,
System.Globalization.CultureInfo.InvariantCulture, out value))
{
thrownewFormatException("Expression is wrong! Please removed un-allowed charactersn
please follow following validations:n" +
"Expression should contain arithmetic operations and operands only n" +
"Expression can only have +, -, * operators n" +
"Numbers can be negative or positive n" +
". as decimal point");
}
return value;
}
}
[TestFixture]
publicclassCalcEngineTest
{
CalcEngine engine;
[Setup]
publicvoid Setup()
{
engine = newCalcEngine();
}
[Test]
[ExpectedException(typeof(FormatException))]
publicvoid TestValidationFailur_Nondigit()
{
Assert.AreEqual(-14, engine.Calculate("1+2+3-4d"));
}
[Test]
[ExpectedException(typeof(FormatException))]
publicvoid TestValidationFailure_NonDecimalNotation()