Calculator Demo -- Part 7

I discovered last time that every operation button invokes the "OperBtn_Click" event handler on click which calls the "ProcessOperation" method with the appropriate argument. I want to briefly touch upon each operation in the switch statement of both the "ProcessOperation" and "Calc" methods.

ProcessOperation //Comments by original programmer

  • Division
    • C#:
                      case "BDevide":
                          if (EraseDisplay)    //stil wait for a digit...
                          {  //stil wait for a digit...
                              LastOper = Operation.Devide;
                              break;
                          }
                          CalcResults();
                          LastOper = Operation.Devide;
                          LastValue = Display;
                          EraseDisplay = true;
                          break;
                      
    • Notes:

      Many of these are binary operations so if the display has not changed since the last operation that means a second number has not been entered. All of these binary operations wait for a second digit before trying to perform their respective operation or the results wouldn't make sense. Also of note is that these binary operations overwrite "LastOper" with themselves so that they will be performed when the next operation is clicked (this means the order of operations for this calculator is strictly left to right).

  • Multiplication
    • C#:
                      case "BMultiply":
                          if (EraseDisplay)    //stil wait for a digit...
                          {  //stil wait for a digit...
                              LastOper = Operation.Multiply;
                              break;
                          }
                          CalcResults();
                          LastOper = Operation.Multiply;
                          LastValue = Display;
                          EraseDisplay = true;
                          break;
                      
                      
    • Notes:

      Another binary operation like above so it follows the same scheme. If a division occurred before this case, "EraseDisplay" would be true so it would wait for the next digit. If a numerical key was pressed, "EraseDisplay" would be false so the last operation would be calculated (for example, division) and then "LastOper" would be set up so that the multiplication happens next (as long as a numerical key is pressed next).

  • Subtraction
    • C#:
                      case "BMinus":
                          if (EraseDisplay)    //stil wait for a digit...
                          {  //stil wait for a digit...
                              LastOper = Operation.Subtract;
                              break;
                          }
                          CalcResults();
                          LastOper = Operation.Subtract;
                          LastValue = Display;
                          EraseDisplay = true;
                          break;
                      
                      
    • Notes:

      If a numerical key is not pressed after the multiplication button--say, the subtraction button is pressed instead--then "LastOper" will be overridden by the subtraction. In this case, a multiplication would not be performed next, a subtraction would be in the wings.

  • Addition
    • C#:
                      case "BPlus":
                          if (EraseDisplay)
                          {  //stil wait for a digit...
                              LastOper = Operation.Add;
                              break;
                          }
                          CalcResults();
                          LastOper = Operation.Add;
                          LastValue = Display;
                          EraseDisplay = true;
                          break;
                      
                      
    • Notes:

      It's clear that the "ProcessOperation" method does not perform the actual operation. This method only sets up the operation for the calculation and asks for the calculation to be performed under the right conditions. One would think, it must be the "CalcResults" method which must be performing the operation. That's almost correct. As I investigated previously, "CalcResults" does one more check

  • Solution
    • C#:
                      case "BEqual":
                          if (EraseDisplay)    //stil wait for a digit...
                              break;
                          CalcResults();
                          EraseDisplay = true;
                          LastOper = Operation.None;
                          LastValue = Display;
                          //val = Display;
                          break;
                      
                      
    • Notes:

      This isn't a binary operation so why is it waiting for a second digit? I think they just don't want "=" to override any previous operation until there's actually something to calculate. That last comment is a little puzzling to me as well. I guess there was a variable "val" used to store the result but it's not needed since the display is already holding the result so you can always query the display to get the previous result.

  • Take the square root
    • C#:
                      case "BSqrt":
                          LastOper = Operation.Sqrt;
                          LastValue = Display;
                          CalcResults();
                          LastValue = Display;
                          EraseDisplay = true;
                          LastOper = Operation.None;
                          break;
                      
    • Notes:

      Finally, we get an example of a unary operation. It doesn't need to check if the display is empty, it just operates on whatever is in the display. The display defaults to "0" so there's no problem pushing the square root button immediately after starting the program. All the unary operations set "LastOper" and "LastValue" at the start. They set them again after the calculation if needed just like the binary operations.

  • Multiply by a percent
    • C#:
                      case "BPercent":
                          if (EraseDisplay)    //stil wait for a digit...
                          {  //stil wait for a digit...
                              LastOper = Operation.Percent;
                              break;
                          }
                          CalcResults();
                          LastOper = Operation.Percent;
                          LastValue = Display;
                          EraseDisplay = true;
                          //LastOper = Operation.None;
                          break;
                      
    • Notes:

      I wish percent acted as a unary operation, just dividing your number by 100 and displaying the result. But here, it is used as a binary operation for multiplying one number by another number's percentage value. You can't see that from the above except for the hint that given by the conditional that it is a binary operation. I will talk about the "Calc" method later to revisit how each operation is actually performed in code.

  • Take the reciprocal
    • C#:
                      case "BOneOver":
                          LastOper = Operation.OneX;
                          LastValue = Display;
                          CalcResults();
                          LastValue = Display;
                          EraseDisplay = true;
                          LastOper = Operation.None;
                          break;
                      
    • Notes:

      This is the final mathematical unary operation. It doesn't look much different from case "BSqrt" except for the names.

  • Clear all
    • C#:
                      case "BC":
                          LastOper = Operation.None;
                          Display = LastValue = string.Empty;
                          //Paper.Clear();
                          UpdateDisplay();
                          break;
                      
    • Notes:

      This is one of the operations which doesn't perform any calculations. It only operates on some variables to, well, clear them. "LastOper" is cleared with "Operation.None", "Display" and "LastValue" are cleared with "string.Empty", and "Paper", which is the history I haven't yet looked into, is cleared with its "Clear" method.

      A couple interesting things about C# is that there is a constant "string.Empty" supplied out of the box (which actually works differently than the empty string--""--as mentioned in Part 4) and that assignment is carried out right-to-left so Display = LastValue = string.Empty; makes sense and works as expected.
  • Clear Entry
    • C#:
                      case "BCE":
                          LastOper = Operation.None;
                          Display = LastValue;
                          UpdateDisplay();
                          break;
                      
    • Notes:

      If you notice you've made a mistake before you push "=", you can undo it by pressing "CE". This cancels whatever operation you've asked the calculator to perform and then sets the display to the last value showing before you keyed in another. Of course, this will only work with binary operations as the unary operations automatically carry out the calculation.

Calc [I will ignore any line that references "Paper"]
  • Division
    • C#:
                      case Operation.Devide:
                          Paper.AddArguments(LastValue + " / " + Display);
                          d = (Convert.ToDouble(LastValue) / Convert.ToDouble(Display));
                          CheckResult(d);
                          Paper.AddResult(d.ToString());
                          break;
                      
    • Notes:

      In order to do math, all values must be numerical types. Recall that "LastValue" and "Display" are strings so they have to be converted to a numerical type. In this case, they are converted to doubles which are 64bit floating point numbers. C# doesn't complain about infinities when dealing with floating point numbers and that makes sense given the IEEE specificiation. The "CheckResult" method is used to discover values that don't make sense (for this calculator).

  • Addition
    • C#:
                      case Operation.Add:
                          Paper.AddArguments(LastValue + " + " + Display);
                          d = Convert.ToDouble(LastValue) + Convert.ToDouble(Display);
                          CheckResult(d);
                          Paper.AddResult(d.ToString());
                          break;
                      
    • Notes:

      Recall that "LastValue" is the value shown in the display before the second operand is entered and "Display" is what is shown to the user. At this point, "Display" still holds the value of the second operand. The display will be updated after the final answer, stored in "d" is returned to the "CalcResults" method.

  • Multiplication
    • C#:
                      case Operation.Multiply:
                          Paper.AddArguments(LastValue + " * " + Display);
                          d = Convert.ToDouble(LastValue) * Convert.ToDouble(Display);
                          CheckResult(d);
                          Paper.AddResult(d.ToString());
                          break;
                      
    • Notes:

      For all of these operations, you just need to look at the assignment to "d" for how the operation is performed. Here, we want to multiply so "LastValue" and "Display" are converted from strings to doubles and then multiplied with the result stored in "d".

  • Multiply by a percent
    • C#:
                      case Operation.Percent:
                          //Note: this is different (but make more sense) then Windows calculator
                          Paper.AddArguments(LastValue + " % " + Display);
                          d = (Convert.ToDouble(LastValue) * Convert.ToDouble(Display)) / 100.0F;
                          CheckResult(d);
                          Paper.AddResult(d.ToString());
                          break;
                      
    • Notes:

      I don't think this makes more sense than Windows calculator. I would prefer the percent operation to be unary and simply divide the current display by 100 to get ready for whatever operation you want to perform next. When I read 50%, I think of 50/100 = 0.5 and end it. I don't think of 50 * 1 / 100. And I definitely don't think of a binary operation.

  • Subtraction
    • C#:
                      case Operation.Subtract:
                          Paper.AddArguments(LastValue + " - " + Display);
                          d = Convert.ToDouble(LastValue) - Convert.ToDouble(Display);
                          CheckResult(d);
                          Paper.AddResult(d.ToString());
                          break;
                      
    • Notes:

      The .NET Framework defines a lot of conversion methods within a variety of classes. I haven't looked at them all yet. And that doesn't even include all the different object castings you can perform.

  • Take the square root
    • C#:
                      case Operation.Sqrt:
                          Paper.AddArguments("Sqrt( " + LastValue + " )");
                          d = Math.Sqrt(Convert.ToDouble(LastValue));
                          CheckResult(d);
                          Paper.AddResult(d.ToString());
                          break;
                      
    • Notes:

      C# doesn't include a native Sqrt operation so it is implemented in the Math namespace.

  • Take the reciprocal
    • C#:
                      case Operation.OneX:
                          Paper.AddArguments("1 / " + LastValue);
                          d = 1.0F / Convert.ToDouble(LastValue);
                          CheckResult(d);
                          Paper.AddResult(d.ToString());
                          break;
                      
    • Notes:

      This programmer is very OCD about making sure all the numbers are represented as floating points. You don't need to but it's good practice to choose one way and stick to it. Here's a little something I made to test how division in particular is handled:

                      using System;
                      
                      namespace testconsole
                      {
                          class Program
                          {
                              static void Main(string[] args)
                              {
                                  Console.WriteLine(7 / 2);
                                  Console.WriteLine(7 / 2.0);
                                  Console.WriteLine(7 / 2F);
                                  Console.WriteLine(7 / 2.0F);
                                  
                                  Console.WriteLine(7.0 / 2);
                                  Console.WriteLine(7.0 / 2.0);
                                  Console.WriteLine(7.0 / 2F);
                                  Console.WriteLine(7.0 / 2.0F);
                                  
                                  Console.WriteLine(7F / 2);
                                  Console.WriteLine(7F / 2.0);
                                  Console.WriteLine(7F / 2F);
                                  Console.WriteLine(7F / 2.0F);
                                  
                                  Console.WriteLine(7.0F / 2);
                                  Console.WriteLine(7.0F / 2.0);
                                  Console.WriteLine(7.0F / 2F);
                                  Console.WriteLine(7.0F / 2.0F);
                                  
                                  Console.ReadLine();
                              }
                          }
                      }
                      
      Result:
      3
      3.5
      3.5
      3.5
      3.5
      3.5
      3.5
      3.5
      3.5
      3.5
      3.5
      3.5
      3.5
      3.5
      3.5
      3.5

      Of course, there are other ways to convert integers to floats but those are the most common shorthands.

CheckResults:

  • Discover invalid numbers
    • C#:
                      private void CheckResult(double d)
                      {
                          if (Double.IsNegativeInfinity(d) || Double.IsPositiveInfinity(d) || Double.IsNaN(d))
                              throw new Exception("Illegal value");
                      }
                      
    • Notes:

      There is very useful stuff to remember here. The facts that Double has a method to check for +/- infinity as well as values which are not numbers (NaN) and throwing exceptions. Very handy.

All that's left is displaying the memory and history (paper trail).

No comments:

Post a Comment