More Math in C#

More Math

The Crash Course

  • There's a lot here. Don't stress if you don't catch it all on your first pass. Come back when you want to know more details about something.
  • Division in C# follows standard "integer division" rules when done with integer types.
  • Many things are implicitly cast from narrow types to wider types.
  • You can explicitly cast from one type to another by putting the type you want in parentheses in front (float a = (float)3.4948393;)
  • Division by zero causes an exception to be thrown for integer types and results in infinity for floating point types.
  • NaN, PositiveInfinity, and NegativeInfinity are defined for the float and double types (float.NaN or Double.PositiveInfinity for example).
  • MaxValue and MinValue are defined for essentially all of the numeric types, defining the minimum and maximum values for any given type.
  • Pi and e are defined in the Math class and can be accessed like this: double area = Math.PI * radius * radius;
  • Mathematical operations can result in numbers beyond the range of the type of data in which it is being stored. For integer values, this results in truncation (and wrapping around), while for floating-point types, it results in PositiveInfinity or NegativeInfinity.
  • You can use the increment operator (++) and decrement operator (--) to add one or subtract one from a variable. int a = 3; a++; results in a containing a value of 4.

Introduction

Here we are, back at more math. You know I wouldn't drag you through all of this math if it wasn't worth it. Seriously. There's so much math that computers can do—in fact, it is all computers can do.

This will be our last tutorial that focuses on math, at least for a while. We'll soon move on to much cooler things.

But there are still quite a few things we need to discuss. I'll admit that the things we're going to talk about in this tutorial are not very related to each other. Each section will be its own thing. This is a good thing because if you already know and understand one topic, you can jump down to the next.

Here are the basic things we're going to discuss in this tutorial. We'll start off by talking about doing division with integers (a.k.a. "integer division") and an interesting problem that comes up when we do that. We'll then talk about converting one type of data to another (called "typecasting" or simply "casting"). Next, we'll talk about dividing by zero and what happens in C#. We'll then talk about a few cool special numbers in C#, like Infinity, NaN, the number e, and pi. Then we'll take a look at overflow and roundoff error and then finish up with one of the cooler/most usable features of C#, incrementing and decrementing.

If you want to skip a few of these sections, that's probably OK, but don't skip casting, incrementing, and decrementing. You'll regret it if you do, I promise!

Here's the MOST IMPORTANT THING: there's a ton to learn here. Don't worry if you don't catch it all your first time. You won't. But when you are trying to figure out some of the more advanced details of something, check back on this tutorial to refresh yourself. Have fun and enjoy. Read through it, then move on to the next tutorial. This one will always be here for you when you need it.

Integer Division

Let's start this section off with a thought problem to help illustrate what's going on. In your head, figure out what 7 / 2 (seven divided by two) is. Got it? No, really, don't just skip this. Get an answer in your head. Got it for real this time? Good. Your answer was probably 3.5, or maybe 3 remainder 1. Both are correct.

Now go into C#, write the following, and see what ends up in result:

int a = 7;
int b = 2;
int result = a / b;

The answer is 3. Not 3.5, or 3 remainder 1. What we're doing isn't the "normal" division you learned in elementary school, but rather a thing called integer division. In integer division, there's no such thing as fractional numbers.

Integer division works by simply taking the largest number that can divide evenly, ignoring the fractional part, no matter how close it is. (For example, 99 / 100 is 0, even though technically, from a pure math standpoint, it is 0.99, which is close to 1.)

Integer division is at work when you do division with the int type, as well as short, long, byte, or any of their unsigned counterparts (uint, ulong, and ushort).

This gets especially tricky when you mix different data types like this:

int a = 7;
int b = 2;
float c = 4;
float d = 3;
float result = a / b + c / d;

Here, the a / b part becomes 3 (like before), but the c / d part does floating point division (the normal division) and gives us 1.33333. Adding the two gives us 4.33333.

It is important to keep in mind that when you are using integer types, this is what's going on. It can easily mess you up, but there are also plenty of times you can use it to your advantage. You just need to remember that it is happening.

Casting (Typecasting)

Typically, when you do math with two things that have the same type (two ints, for example), the result is the same type as the things you started with. But what if you do math with two different types? For example, what if you add an int with a long? The answer is typecasting, which I'll shorten to the common alternative casting because that is so much shorter.

Casting is an awesome feature of the C# language (like Java and C++ before it) that allows you to convert one type to another.

There are two types of casting that are working in C#. One is implicit casting, meaning it happens for you, without you having to tell it to do it (so, magically), while the other is explicit casting, meaning you have to indicate that you want to do it, or it won't happen.

Generally speaking, implicit casting happens for you whenever you go from a "narrower" type to a "wider" type. To help explain, remember that an int in C# uses 32 bits, while a long uses 64 bits. C# will happily cast an int to a long when it sees a need without having to be told (an implicit cast from int to long).

So, for instance, you can do the following code:

int a = 4049;
long b = 284404039;
long sum = a + b;

When you do the adding, it will take the value in a and turn it into a long and add it to b, then stick it back in the sum variable.

Floating point types are considered "wider" than integer types, so when there's a need, C# will convert integer types to floating point types. For example:

int a = 7;
float b = 2; // this converts the integer value 2 into the floating point value 2.0.
 
// Since b is a floating point value, the value in a gets implicitly 
// cast into a float, and floating point division happens instead of 
// integer division, meaning that result will contain a value of 3.5,
// instead of 3 like we saw in the Integer Division section, even
// though we used one int type.
float result = a / b;

Explicit casting is also very critical because there are plenty of times that you want to go from a wider type to a narrower type. To do this, you simply put the type you want to cast to in parentheses in front of what you want to cast. For instance, look at this example, which turns a long into an int:

long a = 3;
int b = (int)a;

It is worth mentioning that casting doesn't just magically convert anything to anything else. Not anything can be converted to any other type. It has its limitations. The compiler will give you an error if you try to do an explicit cast to something it can't do. (In a much later tutorial, we'll look at how to define your explicit casts for things you create.)

It also needs to be said that C# does casting first. So if you want to cast the result of a math operation, you need to put it in parentheses, then put the cast in front of the parentheses. Let me give you an example:

int a = 7;
int b = 2;
int c = 4;
 
// This gives you 7.5, because 'b' gets turned into a float right away,
// and then 'a' gets implicitly converted to a float to do the division,
// then 'c' gets converted to a float to do the addition.
float result1 = a / (float)b + c; 
 
// This gives you 7 because the a / b happens with integer division,
// giving you 3, then the addition is done with 'c', giving you 7, then
// finally, the result is turned into a floating point value of 7.
float result2 = (float)(a / b + c);

Division By Zero

Everybody knows what happens when you divide by zero, right? Nothing good. It just doesn't work, mathematically speaking.

So let's take a brief second and discuss what happens in C# when you divide by zero. To be honest, it is kind of weird. If you divide by zero with integer types, a strange thing happens (which we'll discuss in much more detail later). An exception is thrown. That's a phrase we'll come back to in a whole lot more detail later when we talk about exceptions, but for now, it is enough to know that your program will immediately die.

Interestingly enough, if you are using a floating point type, like double or float, it doesn't crash. I don't know why the two categories are handled differently, but they are. Instead, you'll get the resulting value of Infinity. Which we'll discuss more… right… now….

Special Numbers: Infinity, NaN, e, pi, MinValue, and MaxValue

Infinity

There are a few special values that we should probably discuss. Let's start off by taking a look at infinity. Both the double and the float type define a special value for both positive and negative infinity. This, of course, represents positive and negative infinity. Remember that doing math with a number that is infinity does weird things. positive infinity plus 1 is still going to end up being positive infinity. So is positive infinity minus 1.

To use these, simply use either of the following two options:

double a = double.PositiveInfinity;
float b = float.PositiveInfinity;

or:

double a = Double.PositiveInfinity;
float b = Single.PositiveInfinity;

NaN (Not a Number)

NaN is a similar special value, which means "not a number". This, too, can come up when you do something crazy, like infinity divided by infinity. Like with infinity, there's a way to access this value:

double a = double.NaN;
float b = float.NaN;
 
double c = Double.NaN;
float d = Single.NaN;

E and PI

Now, let's move on to e and pi. These are two special numbers that can be used frequently, depending on your work. It is worth knowing about them at this point in time. You can always do what we did a few tutorials ago and create a variable to store those values, but why do that when there's already a built-in variable that does the same thing?

To use these, we'll use the Math class (we'll talk about classes a whole lot more in the future, and when we do, we'll revisit the other stuff in the Math class). You can do this with the following code:

double radius = 3;
double area = Math.PI * radius * radius;
 
// admittedly, you'll likely find more uses for pi than e.
double eSquared = Math.E * Math.E;

Remember how in the previous tutorial we created our own pi variable? We don't need to do that anymore, because it has already been done for us!

MinValue and MaxValue

Finally, let's talk quickly about MinValue and MaxValue. Most of the numeric types define a MinValue and MaxValue inside of them. These can be used to see the minimum or maximum value that the type can hold. You access these like NaN and infinity for floating-point types:

int maximum1 = Int32.MaxValue;
int maximum2 = int.MaxValue;
 
int minimum1 = Int32.MinValue;
int minimum2 = int.MinValue;

Overflow and Roundoff Error

So think about this. A short can have a maximum value of up to 32767. So what if we do this?

short a = 30000;
short b = 30000;
short sum = a + b; // The sum will be too big to fit into a short.  What happens?

When a mathematical operation causes something to go beyond the supported range for that type, we get what is called overflow. What happens is worth paying attention to. For the integer types (byte, short, int, and long), the most significant bits get dropped. This is especially strange because the computer then interprets it as wrapping around.

Check it out:

int aNumber = Int32.MaxValue;
aNumber = aNumber + 1;
Console.WriteLine(aNumber);// This will print out a large, but negative number.

For the floating point types, things happen differently. Because they have PositiveInfinity and NegativeInfinity defined, instead of wrapping around, they become infinity.

It is worth mentioning that if overflow is a big concern for you, there's a way to run it (in "checked" mode) that causes integer operations to throw an exception (there's that phrase again) instead of wrapping around, but that's a discussion for much later on.

Along with overflow is a similar condition called underflow that can occur sometimes with floating point types. Imagine you have a very large floating point number. Something like 1,000,000,000,000,000,000,000,000,000. A float can store that number. Now let's say you have another very small number, like 0.000000000000000001. You can store that as a float as well. However, if you add the two together, a float cannot store the value 1,000,000,000,000,000,000,000.0000000000001.

A float simply cannot be that big and precise simultaneously. Instead, the addition would result in 1,000,000,000,000,000,000,000 again. Which is perhaps close enough for most things. But still, information is lost which may be valuable.

Imagine adding up a very large amount of very small numbers. It is a place where underflow can sometimes be an issue. Admittedly, this is not nearly as common as overflow, and in fact, many people will never really run into a place where it comes up. (I have, though, so I don't feel like it is safe to ignore.)

Incrementing and Decrementing

OK, let's talk about one final, awesome feature of C#.

Perhaps you've noticed that lots and lots of times in these tutorials, I've tried to add 1 to a value. We've already seen two ways of doing this:

int a = 3;
a = a + 1;

and:

int a = 3;
a += 1;

Here's yet another way of adding 1 to a value:

int a = 3;
a++;

This is called "incrementing", and "++" is called the "increment operator". We'll see tons of places to use this in a few tutorials.

As its counterpart, you can use -- to subtract one from a number. This is called "decrementing", and -- is called the "decrement operator".

Incidentally, the ++ in the C++ programming language comes from this very feature (which shows up in lots of languages, including Java, C, and C++). C++ was sort of a "next step" or "one step beyond" the programming language C, hence the name C++.

One other thing worth mentioning with the increment and decrement operators is that you can write it in one of two ways. You can write a++;, or you can also write ++a;. (Likewise, you can write a--; and --a;.) With the ++ at the end, it is called postfix notation, and with it at the beginning, it is called prefix notation.

And to top it off, there's a subtle difference between the two. To understand the difference, it is important to realize that this operation "returns a value" (an expression we'll see more of in the future). With the more common postfix notation (a++;) the original value of a is returned. With the less common prefix notation (++a;) the new value is returned. This might seem a little confusing, so here's an example:

int a = 3;
int b = ++a; // Both 'a' and 'b' will now be 4.
 
int c = 5;
int d = c++; // The original value of 5 is assigned to 'd', while 'c' is now 6.

What's Next?

That covers a whole pile of math-related topics. And now, perhaps, you feel a bit overwhelmed. Don't! It's a lot of stuff, but, remember, you don't need to have a perfect understanding of these things right now. Just knowing that they exist and that you can come back to this tutorial whenever you want is enough.

In our next tutorial, we're going to dive into one of the most important parts of making a program: decision making!