Share your repls and programming experiences

← Back to all posts
C# Calculator
mrsprinkletoes (151)

My first C# program, I think it works pretty good

It'll log some errors too

Comments
hotnewtop
[deleted]

try/catch is a dark path you are going to have a hard time getting off if you get hooked on using it. In almost every instance of its use you could employ better coding to avoid needing it altogether. Let's go over a few of those things...

So first... your original code with a few notes outlining some things.

using System;
using System.IO;
using System.Text;

class MainClass {
      public static void Main (string[] args) {
          int num1;
          int num2;
          string oper;

          //this try catch is avoidable if you actually
          //bother to validate what is being input
          //try/catch here can be avoided entirely
          //by coding properly.
          try {
                Console.Write("Number 1: ");
                //use TryParse instead to catch errors
                //and setup a loop to make sure a valid
                //number is entered
                num1 = Int32.Parse(Console.ReadLine());

                //ask this first so you can determine
                //if need to make sure for division for
                //instance needs to check for a divide
                //by zero to make sure zero cannot be
                //entered.
                Console.Write("|+|-|x|/| (choose): ");
                oper = Console.ReadLine();

                //this has some extra work to do
                //making sure you're not going to cause
                //overflow/underflow or divide by zero
                //using the try/catch is avoidable
                Console.Write("Number 2: ");
                num2 = Int32.Parse(Console.ReadLine());
                
                //convert this to a switch/case statement
                //store the evaluation 
                if (oper == "+") {
                    Console.WriteLine(num1 + num2);
                } else if (oper == "-") {
                    Console.WriteLine(num1 - num2);
                } else if (oper == "x") {
                    Console.WriteLine(num1 * num2);
                } else if (oper == "/") {
                    Console.WriteLine(num1 / num2);
                }
          } catch (Exception ex) {
              //you don't need any of this if you just
              //for proper validation checks on what
              //is being input... 
              Console.BackgroundColor = ConsoleColor.Red;
              Console.ForegroundColor = ConsoleColor.Black;
              Console.WriteLine("An error occurred, it has been logged in the errorlog.txt file.");
              if (File.Exists("errorlog.txt")) {
                  File.Delete("errorlog.txt");
              }
              using (StreamWriter sw = File.CreateText("errorlog.txt")) {
                  sw.WriteLine("Source: "+ex.Source+"\n");
                  sw.WriteLine("StackTrace: "+ex.StackTrace+"\n");
                  sw.WriteLine(ex.Message);
              }
          }
      }
}

Alright, so now we need to go over a few things on what those notes mean, and how to tackle some of those things that are addressed. try/catch is not your friend... albeit there are times when you will be forced to use it. JAVA comes to mind and several sections of the .NET libraries also leave you with no choice but to use it. Crappy and poorly implemented socket libraries... ugghgh...

Here we strip out the try/catch so our eyes no longer to bleed.

using System;
using System.IO;
using System.Text;

class MainClass {
  public static void Main(string[] args) {
    int num1;
    int num2;
    string oper;

    Console.Write("Number 1: ");
    //use TryParse instead to catch errors
    //and setup a loop to make sure a valid
    //number is entered
    num1 = Int32.Parse(Console.ReadLine());

    //ask this first so you can determine
    //if need to make sure for division for
    //instance needs to check for a divide
    //by zero to make sure zero cannot be
    //entered.
    Console.Write("|+|-|x|/| (choose): ");
    oper = Console.ReadLine();

    //this has some extra work to do
    //making sure you're not going to cause
    //overflow/underflow or divide by zero
    Console.Write("Number 2: ");
    num2 = Int32.Parse(Console.ReadLine());

    //convert this to a switch/case statement
    //store the evaluation 
    if (oper == "+") {
      Console.WriteLine(num1 + num2);
    } else if (oper == "-") {
      Console.WriteLine(num1 - num2);
    } else if (oper == "x") {
      Console.WriteLine(num1 * num2);
    } else if (oper == "/") {
      Console.WriteLine(num1 / num2);
    }
  }
}

Now let's address the switch/case note at the bottom down there...

see: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/switch

using System;
using System.IO;
using System.Text;

class MainClass {
  public static void Main(string[] args) {
    int num1;
    int num2;
    string oper;

    Console.Write("Number 1: ");
    //use TryParse instead to catch errors
    //and setup a loop to make sure a valid
    //number is entered
    num1 = Int32.Parse(Console.ReadLine());

    //ask this first so you can determine
    //if need to make sure for division for
    //instance needs to check for a divide
    //by zero to make sure zero cannot be
    //entered.
    Console.Write("|+|-|x|/| (choose): ");
    oper = Console.ReadLine();

    //this has some extra work to do
    //making sure you're not going to cause
    //overflow/underflow or divide by zero
    Console.Write("Number 2: ");
    num2 = Int32.Parse(Console.ReadLine());

    //since the div will result in int
    //with a rounding, dropping the
    //floating point/decimal portion.

    int result = 0;

    switch (oper) {
      case "+": result = num1 + num2; break;
      case "-": result = num1 - num2; break;
      case "x": result = num1 * num2; break;
      //should put something that this rounds
      //and we need to make sure we have no
      //chance of a divide by zero...
      case "/": result = num1 / num2; break;
    }

    Console.WriteLine("\n" + num1 + " " + oper + " " + num2 + " = " + result);
  }
}

So let's mash down the Int32.Parse and convert those to Int32.TryParse and move the the operator question to the top of the program.

see: https://docs.microsoft.com/en-us/dotnet/api/system.int32.tryparse?view=netframework-4.8

using System;
using System.IO;
using System.Text;

class MainClass {
  public static void Main(string[] args) {
    int num1;
    int num2;
    string oper;

    //we'll need some sort of validation
    //loop here also to make sure operator
    //is acceptable.
    Console.WriteLine("What type of maths are we doing?");
    Console.Write("Choose an operator [+ - x /]: ");
    oper = Console.ReadLine();
        
    Console.Write("Number 1: ");
    //we'll need to setup a loop so it validates
    bool isValid = Int32.TryParse(Console.ReadLine(), out num1);

    //establish some sort of min/max for input
    //multiply and div are going to be troublesome

    //this has some extra work to do
    //making sure you're not going to cause
    //overflow/underflow or divide by zero
    //we will also need to setup a validation loop
    Console.Write("Number 2: ");
    isValid = Int32.TryParse(Console.ReadLine(), out num2);

    int result = 0;
    switch (oper) {
      case "+": result = num1 + num2; break;
      case "-": result = num1 - num2; break;
      case "x": result = num1 * num2; break;
      //should put something that this rounds
      //and we need to make sure we have no
      //chance of a divide by zero...
      case "/": result = num1 / num2; break;
      default: break; //we'll add some validation here later
    }

    Console.WriteLine("\n" + num1 + " " + oper + " " + num2 + " = " + result);
  }
}

Now we should tackle some basic validation loops using do/while type flow.

see: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/do

Basically in this case we want to make sure that isValid we got back from the Int32.TryParse is TRUE. This way we know we got a valid Int32 and that we can proceed. We also want to make sure that the operator is valid and we will also do a basic repeat of the validation used for the second number.

using System;
using System.IO;
using System.Text;

class MainClass {
  public static void Main(string[] args) {
    int num1;
    int num2;
    string oper;
 
    Console.WriteLine("What type of maths are we doing?");

    bool isValid = false;
    do {
      Console.Write("Choose an operator [+ - x /]: ");
      oper = Console.ReadLine();
      switch (oper) {
        case "+": //waterfalling is required
        case "-": //just like in C/C++ as nesting
        case "x": //cases is not valid...
        case "/": isValid = true; break;
        default: Console.WriteLine("Invalid choice..."); break;
      }
    } while (!isValid);

    do {
      Console.Write("Number 1: ");
      isValid = Int32.TryParse(Console.ReadLine(), out num1);
      if (!isValid)
        Console.WriteLine("Invalid choice...");
      else
        break; //exits the while;
    } while (true);

    //establish some sort of min/max for input
    //multiply and div are going to be troublesome

    //this has some extra work to do
    //making sure you're not going to cause
    //overflow/underflow or divide by zero
    do {
      Console.Write("Number 2: ");
      isValid = Int32.TryParse(Console.ReadLine(), out num2);
      if (!isValid)
        Console.WriteLine("Invalid choice...");
      else
        break; //exits the while;
    } while (true);

    int result = 0;
    switch (oper) {
      case "+": result = num1 + num2; break;
      case "-": result = num1 - num2; break;
      case "x": result = num1 * num2; break;
      case "/": {
          Console.Write("Integer maths used. ");
          Console.WriteLine("Answer subjected to rounding.");
          result = num1 / num2;
        }  break;
    }

    Console.WriteLine("\n" + num1 + " " + oper + " " + num2 + " = " + result);
  }
}

Now we are bit closer to something more user friendly. But we have some immediate code replication that we can put into a function. Since the two number validation loops only vary by the number title and the variable being input, we can put that into a function easily. We will return the validated number as int from the function, and take a single parameter which is the number we are entering.

see: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/methods

using System;
using System.IO;
using System.Text;

class MainClass {
  public static int getNumber(string name) {
    int number;
    bool isValid = false;

    do {
      Console.Write("Number " + name + ": ");
      isValid = Int32.TryParse(Console.ReadLine(), out number);
      if (!isValid)
        Console.WriteLine("Invalid choice...");
      else
        break; //exits the while;
    } while (true);

    return number;
  }

  public static void Main(string[] args) {
    int num1;
    int num2;
    string oper;

    //we'll need some sort of validation
    //loop here also to make sure operator
    //is acceptable.
    Console.WriteLine("What type of maths are we doing?");

    bool isValid = false;
    do {
      Console.Write("Choose an operator [+ - x /]: ");
      oper = Console.ReadLine();
      switch (oper) {
        case "+":
        case "-":
        case "x":
        case "/": isValid = true; break;
        default: Console.WriteLine("Invalid choice..."); break;
      }
    } while (!isValid);

    num1 = getNumber("1");

    //establish some sort of min/max for input
    //multiply and div are going to be troublesome

    //this has some extra work to do
    //making sure you're not going to cause
    //overflow/underflow or divide by zero
    num2 = getNumber("2");

    int result = 0;
    switch (oper) {
      case "+": result = num1 + num2; break;
      case "-": result = num1 - num2; break;
      case "x": result = num1 * num2; break;
      case "/": {
          Console.Write("Integer maths used. ");
          Console.WriteLine("Answer subjected to rounding.");
          result = num1 / num2;
        }  break;
    }

    Console.WriteLine("\n" + num1 + " " + oper + " " + num2 + " = " + result);
  }
}

Things are getting a little more organised now. We still need to work out what numbers we can input for validation so we cannot cause overflows/underflows and divide by zero. Let's think about this. The first number we enter shouldn't make a difference, so it can be between Int32.MinValue and Int32.MaxValue. The seconds number though has some special cases we need to address.

Addition can use...

if (num1 < 0) {
  numMin = Int32.MinValue - num1;
elseif (nume 1 > 0) {
  numMax = Int32.MaxValue - num1;
}

Subtraction can use a variation of the Addition minimum check by reversing the position of the Int32.MinValue and Int32.MaxValue.

if (num1 < 0) {
  numMin = Int32.MaxValue - num1;
elseif (nume 1 > 0) {
  numMin = Int32.MinValue - num1;
}

Division is easy enough, since we are never dealing with fractions, we'll always get back a smaller number. We just need to make sure the second number is never 0.

Multiplication can take the absolute value of the Int32.MaxValue divided by num1 and then use -1 * the result for the numMin and the absolute value as the numMax.

Let's code those in...

using System;
using System.IO;
using System.Text;

class MainClass {
  //need to take a few more args for validation
  public static int getNumber(string name) {
    int number;
    bool isValid = false;

    do {
      Console.Write("Number " + name + ": ");
      
      //show the min max range for valid input

      isValid = Int32.TryParse(Console.ReadLine(), out number);
      
      //need to test for min and max value validation

      if (!isValid)
        Console.WriteLine("Invalid choice...");
      else
        break; //exits the while;
    } while (true);

    return number;
  }

  public static void Main(string[] args) {
    int num1;
    int num2;
    string oper;

    //we'll need some sort of validation
    //loop here also to make sure operator
    //is acceptable.
    Console.WriteLine("What type of maths are we doing?");

    bool isValid = false;
    do {
      Console.Write("Choose an operator [+ - x /]: ");
      oper = Console.ReadLine();
      switch (oper) {
        case "+":
        case "-":
        case "x":
        case "/": isValid = true; break;
        default: Console.WriteLine("Invalid choice..."); break;
      }
    } while (!isValid);

    int numMin = Int32.MinValue;
    int numMax = Int32.MaxValue;
    bool isZeroValid = true;

    //need to pass the min and max values and isZeroValid
    num1 = getNumber("1");
        
    switch (oper) {
      case "+": {
          if (num1 < 0)
            numMin = Int32.MinValue - num1;
          else if (num1 > 0)
            numMax = Int32.MaxValue - num1;
        } break;
      case "-": {
          if (num1 < 0)
            numMin = Int32.MaxValue - num1;
          else if (num1 > 0)
            numMax = Int32.MinValue - num1;
        } break;
      case "x": {
          int t = Math.Abs(Int32.MaxValue / num1);
          numMin = -t;
          numMax = t;
        } break;
      case "/": isZeroValid = false; break;
    }

    //need to pass the min and max values and isZeroValid
    num2 = getNumber("2");

    int result = 0;
    switch (oper) {
      case "+": result = num1 + num2; break;
      case "-": result = num1 - num2; break;
      case "x": result = num1 * num2; break;
      case "/": {
          Console.Write("Integer maths used. ");
          Console.WriteLine("Answer subjected to rounding.");
          result = num1 / num2;
        }
        break;      
    }

    Console.WriteLine("\n" + num1 + " " + oper + " " + num2 + " = " + result);
  }
}

We are almost there and free of having nightmares of relying on try/catch.
Just a few last things to address. This could still use some cleanup and other things, but at least this does validation, provides useful information to the user and does not just subject them to crashing with exceptions, and other silly nonsense.

using System;
using System.IO;
using System.Text;

class MainClass {  
  public static int getNumber(
    string name,
    int min, int max,
    bool isZeroValid
    ) {

    int number;
    bool isValid = false;

    do {
      Console.WriteLine("Enter number " + name);
      Console.Write("[" + min + " to ");

      if (!isZeroValid) Console.Write(" -1 and 1 to ");
      Console.WriteLine(max + "]");      

      isValid = Int32.TryParse(Console.ReadLine(), out number);
      
      if (isValid && (number >= min && number <= max)) {
        if (!isZeroValid) {
          if (number != 0) break; //exits the while;
        }
      } else
        break; //exits the while;

      Console.WriteLine("Invalid choice...");
    } while (true);

    return number;
  }

  public static void Main(string[] args) {
    int num1;
    int num2;
    string oper;

    //we'll need some sort of validation
    //loop here also to make sure operator
    //is acceptable.
    Console.WriteLine("What type of maths are we doing?");

    bool isValid = false;
    do {
      Console.Write("Choose an operator [+ - x /]: ");
      oper = Console.ReadLine();
      switch (oper) {
        case "+":
        case "-":
        case "x":
        case "/": isValid = true; break;
        default: Console.WriteLine("Invalid choice..."); break;
      }
    } while (!isValid);

    int numMin = Int32.MinValue;
    int numMax = Int32.MaxValue;
    bool isZeroValid = true;
    
    num1 = getNumber("1", numMin, numMax, isZeroValid);
        
    switch (oper) {
      case "+": {          
          if (num1 < 0)
            numMin = Int32.MinValue - num1;
          else if (num1 > 0)
            numMax = Int32.MaxValue - num1;
        } break;
      case "-": {
          if (num1 < 0)
            numMin = Int32.MaxValue - num1;
          else if (num1 > 0)
            numMax = Int32.MinValue - num1;
        } break;
      case "x": {
          int t = Math.Abs(Int32.MaxValue / num1);
          numMin = -t;
          numMax = t;
        } break;
      case "/": isZeroValid = false; break;
    }
    
    num2 = getNumber("2", numMin, numMax, isZeroValid);

    int result = 0;
    switch (oper) {
      case "+": result = num1 + num2; break;
      case "-": result = num1 - num2; break;
      case "x": result = num1 * num2; break;
      case "/": {
          Console.Write("Integer maths used. ");
          Console.WriteLine("Answer subjected to rounding.");
          result = num1 / num2;
        }
        break;      
    }

    Console.WriteLine("\n" + num1 + " " + oper + " " + num2 + " = " + result);
  }
}

There might be bugs, I didn't check everything. I'll leave the rest of the cleanup of the output and things to you. But this is a much better way to deal with this type of code than to just bail and puke with a try/catch and leave validation to random chance. Keep your head up... if you have any questions please ask.

CowNationz (50)

@mrsprinkletoes - This is my first C# program.
@KelvinVerhey - i CoULd dO iT BETtEr

[deleted]

@CowNationz has nothing to do with doing it better... but the explanation of avoiding a trap with try/catch and how to do it. Many programmers fall in this dark hole. I did for a solid 16 years. Microsoft still does and Sun built an entire coding paradigm around it in the 90's which persists today in JAVA.

Have you looked through the .NET standard libraries? Or programmed in JAVA? try/catch is littered throughout, and many instances you must use it because of the way the library was setup. If young programmers are going to evolve past this, alternatives must be presented.

For instance, let's take a look at this file...

https://referencesource.microsoft.com/#system/net/system/net/Sockets/Socket.cs

It's riddled with throws. You must try/catch those. You are not given an alternative in the way the code has been written in that library. Is it 9th level evil incarnate? Not really... but does it make you feel dirty inside once you've been coding for a bit? Sure does.

Example. I'm big fan of C. There are few good programming manuals anymore. The defacto goto is still the K&R C manual. The problem is, we've learned a lot about programming since that manual was written. There are a lot of trivial changes that are probably nitpicky, but these models are still being used to train programmers.

Let's take a peak at piece of C# code I found on here... it wasn't released, or shared, or setup in another way just hanging around in someones repls. The same way anyone poking around here might find it.

using System;

class MainClass {
  public static void Main (string[] args) {
  //Console.WriteLine ("Enter start number");
  int target = int.MaxValue ;

  while (true ) {
    looper:
    target--;
    for ( int i = 2; i < target / 2; i++ ) {
      if ( target % i == 0 ) {
        goto looper;
      }
    }
    Console.WriteLine( target + " is prime!");
    }
  }
}

What do we do with this? Is this code you would find acceptable? Say for instance this was something a teacher was showing you? And this might be hard to believe, but I have seen way worse than this being taught in programming manuals.

This is a website at its core that is supposed to be built on the foundation of education. Promoting learning opportunities to explore and build a collection of resources where a future generation can come to learn to code. This website is riddled with shares of repeats of shares with the same bad practices over and over. Resistance to writing good code, and searching for better practices to open dialogues about coding practices should be explored. We need better manuals. Ideas around better coding paradigms. It's going to be people visiting here learning to code that are going to be building the next generation of operating systems, programming languages, libraries that support existing and new frameworks. What sort of future are you expecting?

One with no alternative? No explanations of how to avoid traps? Let's take some code in visual basic from a programming manual that is being used to teach kids here in Ontario.

This is how the original code in the manual looks...

Team = Team & Space(20 - Len(Team))
Played = Played & Space(10 - Len(Played))
Points = Points & Space(6 - Len(Points))
OneTeamDetails = Team & Played & Points
lstOutput.Items.Add(OneTeamDetails)

At first... it's like, okay... no problem. Until you realise this is supposed to be teaching modern VB.NET and not VB6. So this is a mash of old programming paradigms with modern. Len was replaced with string.Length to follow the new object oriented model that .NET was promoting. So let's update that.

Team = Team & Space(20 - Team.Length))
Played = Played & Space(10 - Played.Length))
Points = Points & Space(6 - Points.Length))
OneTeamDetails = Team & Played & Points
lstOutput.Items.Add(OneTeamDetails)

We then get problems with all those & string appends hanging around. Those do nasty copy things that internally to the .NET virtual machine make a horror story, and there are also other methods that have been incorporated like PadLeft and PadRight. They do the maths for you.

Team = Team.PadRight(20)
Played = Played.PadRight(10)
Points =  Points.PadRight(6)
OneTeamDetails = Team & Played & Points
lstOutput.Items.Add(OneTeamDetails)

So, what are we supposed to do with this? Do was just accept what we're being told this is a good solution? Or do we continue to dig down into further ways to clean this up? How far can we go?

Would this be acceptable?

lstOutput.Items.Add(Team.PadRight(20) & Played.PadRight(10) & Points.PadRight(6))

If this isn't a record do we need the .PadRight on points at all? Is there an instance where this might be okay?

lstOutput.Items.Add(Team.PadRight(20) & Played.PadRight(10) & Points)

It is our responsibility regardless where we are in our understanding to seek better ways to do this. We are directly responsible for the mess we have continued to put ourselves in. Manuals are seldom showing good methods to do things. That's a scary fact. The problem is, the understanding changes constantly. And we as programmers are resisting patches, updates, and security fixes in our understanding. That doesn't seem very bright.

mrsprinkletoes (151)

@KelvinVerhey I realize I could probably avoid the possibility of an error, but I purposely tried not to. I wanted to mess around with creating and editing files in C# too, so I figured it'd be cool to create a sort've error logging system

[deleted]

@CowNationz @mrsprinkletoes explanations like that would be useful. But it's still useful to show the alternative. Not enough of that seems to be happening here. A great project would be to take the un-try/catched code and dump the while loops and other checks and log those appropriately without needing try/catch. Here's to upgrading the world. There is a serious shortage of proficient coders out there. In Canada alone we have 142,000 openings, schools without computer labs, serious shortages of computer science teachers, and kids graduating high school that didn't even know computer programming is possible. Do everything you can to educate, upgrade, teach, mentor, and make the world around you better. Keep being awesome.

[deleted]

@mrsprinkletoes It might have been more useful to label it "testing error logging" instead of "calculator". The intent was a bit misleading based on title of focus for the code. Anyway... you could still explore that on a deeper level by modding the de-try/catched code and dumping all the while/retry loops and logging errors. Place the progress in here to expand the exploration. Keep your head up.

ZoftoGaming (0)

You should make it so when you dont choose one of the alternetive it says choose a valid option and then make it do it again