C# Crash Course » More Math
More Math in C#
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: float area = Math.PI * radius * radius;
- Mathematical operations can result in numbers that go beyond the range of the type of data it is being stored in. 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.
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, though, at least for a while. We'll soon move on to much cooler things.
But there's still quite a bit of things we need to discuss. I'll admit, 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. Which is a good thing, because if you already know and understand one topic, you can just jump down to the next.
Here's 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 underflow, and 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 be sure to not skip casting and 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.
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#, and 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 "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, and ignores the fractional part, no matter how close it is. (So for example, 99 / 100 is 0, even though technically, from a pure math standpoint, it is 0.99, which is really 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, that 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.
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 we will from now on, always call "casting", because it 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 in to 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 is converting 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 are trying to do an explicit cast to something that it can't do. (In a much later tutorial, we'll take a look at how to define your own 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 in to 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 (that we'll discuss in a whole lot 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 die, then and there, if you are running without debugging. It may or may not show an error message before the program dies. If you are running with debugging, Visual C# Express will activate, right at the line that the exception occurred at, allowing you to (hopefully) fix the problem. It will come up with a little floating panel that says "DivideByZeroException was unhandled".
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
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;
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 very frequently, depending on what you are working on. 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 Underflow
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 extents of the allowed range for that value, 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.0000000000000000001. 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,000,000.0000000000000000001.
A float just simply cannot be that big and precise at the same time. Instead, the addition would result in 1,000,000,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;
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". In a few tutorials, we'll see tons of places to use this.
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.
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!
|Having problems with this tutorial? Try the troubleshooting page!|
C# Crash Course » More Math