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());
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()
{ Assert.AreEqual(0, engine.Calculate("1+2+3,4")); } [Test] publicvoid TestBlankStringShouldZero() { Assert.AreEqual(0, engine.Calculate(" ")); } [Test] publicvoid TestMultiplication() { Assert.AreEqual(60, engine.Calculate("5*6*2")); Assert.AreEqual(469.929812m, engine.Calculate("5.5*2.456*34.789")); } [Test] publicvoid TestSubtraction() { Assert.AreEqual(45, engine.Calculate("100-35-20")); Assert.AreEqual(469.929812m, engine.Calculate("5.5*2.456*34.789")); } [Test] publicvoid TestSummation() { CalcEngine engine = newCalcEngine(); Assert.AreEqual(10, engine.Calculate("1+2+3+4")); Assert.AreEqual(20.20m, engine.Calculate("1.4+4.5+8.90+5.4")); Assert.AreEqual(20 + 20, engine.Calculate("20+20")); } [Test] publicvoid TestAdd_Multiply_Subtraction() { Assert.AreEqual(-5563.4541m, engine.Calculate("-2*4.5+3+30-20*278.9+30.5459-40")); Assert.AreEqual(-5563.4541m, engine.Calculate("-2*4.5+3+30-20*278.9+30.5459-40")); } [Test] publicvoid TestDecimalNumberWithoutOperators() { Assert.AreEqual(4.5m, engine.Calculate("4.5")); } } }

C-Sharp Arithmatic Expression Calculator

  • 1.
    Problem Statement Create asimple 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()
  • 3.
    { Assert.AreEqual(0, engine.Calculate("1+2+3,4")); } [Test] publicvoid TestBlankStringShouldZero() { Assert.AreEqual(0, engine.Calculate(" ")); } [Test] publicvoid TestMultiplication() { Assert.AreEqual(60, engine.Calculate("5*6*2")); Assert.AreEqual(469.929812m, engine.Calculate("5.5*2.456*34.789")); } [Test] publicvoid TestSubtraction() { Assert.AreEqual(45, engine.Calculate("100-35-20")); Assert.AreEqual(469.929812m, engine.Calculate("5.5*2.456*34.789")); } [Test] publicvoid TestSummation() { CalcEngine engine = newCalcEngine(); Assert.AreEqual(10, engine.Calculate("1+2+3+4")); Assert.AreEqual(20.20m, engine.Calculate("1.4+4.5+8.90+5.4")); Assert.AreEqual(20 + 20, engine.Calculate("20+20")); } [Test] publicvoid TestAdd_Multiply_Subtraction() { Assert.AreEqual(-5563.4541m, engine.Calculate("-2*4.5+3+30-20*278.9+30.5459-40")); Assert.AreEqual(-5563.4541m, engine.Calculate("-2*4.5+3+30-20*278.9+30.5459-40")); } [Test] publicvoid TestDecimalNumberWithoutOperators() { Assert.AreEqual(4.5m, engine.Calculate("4.5")); } } }