9 min read

Advanced Math

The Crash Course

Introduction

In this tutorial, we’re going to return to the topic of math, and dig just a bit deeper on a few important topics. The topics we’ll cover here are not very closely related to each other, aside from the fact that they deal with some of the important complexities of doing math in C#.be here for you when you need it.

Integer Division and Floating-Point Division

Pop quiz: what is 7 divided by 2?

Got an answer in your head?

Now go run this code:

int a = 7;
int b = 2;
Console.WriteLine(a / b);

Did the answer surprise you?

Unless you’ve done a lot of programming in languages similar to C#, it probably seemed at least a little surprising.

C# thinks 7 / 2 is 3, not 3.5!

Try this next:

float a = 7;
float b = 2;
Console.WriteLine(a / b);

Did that surprise you?

By changing the types from int to float, we now get an answer of 3.5.

In C#, there are two modes of doing division: integer division and floating-point division.

Under integer division, only whole numbers are considered. After all, an integer cannot represent the fractional or decimal values. Note that it does not round, but “truncates” anything after the decimal point! Even 199/100 comes out as 1, even though it is 1.99, and would round to 2.

Under floating-point division, you get your digits after the decimal point, as you would expect.

When you do division with integer types (int, long, ushort, etc.), C# uses integer division. When you do division with floating-point types (float, double, decimal), C# uses floating-point division.

What about when you mix types, like the following?

int a = 7;
float b = 2;
Console.WriteLine(a / b);

That’s something of a trick question. Division isn’t defined between an int and a float! In general, math operations are only defined between two things of the same types.

Yet the code above compiles and runs. What’s going on?

A few tutorials back, we talked about how when you mix types, sometimes, C# will automatically promote a value from one type to another. That’s what’s happening here. In the expression a / b, a is an int, and b is a float. The value in a will be promoted to a float, and then the division will be performed between two float values, and with a result type of float.

The difference between integer and floating-point division is important to keep in mind. You will–someday soon–do something where you were expecting the computer to do floating-point division, but your code is actually doing integer division, or vice versa, and the result will not be what you were expecting. With an understanding of the differences, you will be able to figure out what the problem is and fix it.

Special Numbers

There are a handful of special numbers that are worth knowing about. We’ll cover those here.

MaxValue and MinValue

Since every type on a computer is limited, they’ll inevitably have a maximum and minimum value they can represent. If you know those values, you can just type them in, but nobody memorizes them, and there’s a more efficient way.

All of the floating-point types and integer types define a MaxValue and MinValue property.

info

We’ll learn about properties in depth later. For now, think of them as being a variable.

They are used like so:

int max = int.MaxValue;
float min = float.MinValue;

PositiveInfinity and NegativeInfinity

Floating-point types (but not integer types) can also represent the concept of both positive and negative infinity. You can assign a variable a value of positive or negative infinity by using the type’s PostiveInfinity or NegativeInfinity properties:

float positiveInfinity = float.PositiveInfinity;
double negativeInfinity = double.NegativeInfinity;

However, if you want to check if some computed value has ended up resulting in infinity, you are better off using the methods IsInfinity, IsFinite (if you want to know if it is not infinity), IsPositiveInfinity and IsNegativeInfinity:

float n = float.PostiveInfinity;
bool isInfinity = float.IsInfinity(n);

NaN: Not a Number

Floating-point types (but not integer types) also have a special value called “NaN” or “not a number.” This is usually used in situations where the math has fallen apart (such as division by zero, below), and the result of the operation isn’t an actual number.

You can use the type’s NaN property:

float notANumber = float.NaN;

You will want to use the IsNaN method to see if the result of an expression or variable has become this special not-a-number value:

bool isNotANumber = double.IsNaN(double.NaN); // This will always be true.

Division By Zero

Everybody knows that if you divide by zero, you’re going to have a terrible, horrible, no good, very bad day.

Dividing by zero is a mathematical curiosity. It is totally illogical, and there is no good answer for what it means. (Though, I suppose, you can divide by numbers extremely close to zero, and that often has value in certain types of problems in the math world.)

So what does a computer program do when you ask it to divide by zero?

This is another place where integers and floating-point values differ.

With the floating-point types, you will either get positive infinity, negative infinity, or NaN, depending on whether the numerator (the top number) is positive, negative, or zero.

With integer types, your program will actually crash. We’ll look at ways to check to see if you’re about to divide by zero and also to prevent your program from crashing later on.

Overflow and Round-Off Errors

In these tutorials, we’ve stated several times that our numeric types are limited in what they can represent. They have minimum and maximum values, for example.

So what happens if we do something that would push us beyond the limits of the type? Consider this code:

int number = int.MaxValue;
number++;

Since we’re starting at the maximum value for the int type, what happens when we add one to it?

For integer types, it does something a little surprising: it wraps around. The above code will change number from the maximum value of 2,147,483,647 to its minimum value of -2,147,483,648!

note

As strange as this wraparound sounds, it is extremely common in programming languages, simply because that is what is easy to do in the hardware. C# does this like every other language that has a similar integer type.

When a math operation causes a value to exceed the normal limitations of the type, it is called overflow .

In the floating-point world, things are different. If an operation causes overflow, the value becomes either positive infinity or negative infinity, depending on the specific situation.

It is somewhat hard to cause overflow in a floating-point situation in most real-world situations, simply because they can generally store really big numbers. It is a bit more common with integers, and is something to pay attention to, especially if your operations are coming close to the maximum numbers.

For floating-point numbers, a similar issue occurs when numbers get too small to be representable by the type. If you start at 1 and divide a number by 100 several times over, then eventually, you’ll get so small that a float, or even a double or decimal won’t be able to represent the number anymore. This is called underflow .

note

A float’s smallest representable non-zero value is still extremely tiny: about 1×10⁻⁴⁵. For a double, it is about 1×10⁻³²⁴.

But floating-point numbers can have other problems, even if you stay away from the extremes. These errors arise from the fact that floating-point types can’t represent all possible values. As the magnitude of the numbers get bigger, the precision goes down. A float, for example, can understand the difference between 0.0000001 and 0.0000002, and it can see the difference between 1000000 and 100001, but it cannot accurately represent the difference between 1000000 and 1000000.0000001. Floating-point values cannot perfectly represent all possible values within their range.

In some situations the mathematically correct answer and the computed answer differ slightly, due to rounding. This is called a round-off error or a rounding error .

In most circumstances, these rounding errors are tiny and can be ignored. In other situations, especially ones involving a lot of calculations that build on each other, this could eventually cause significant issues, and is important to be aware of.

The Math Class

In previous tutorials, we have encountered the Console class, which has methods for interacting with the console window, as well as the Convert class, which has methods for converting between types.

The Math class has methods (and properties) for doing important math-related things. It’s a useful enough class that it is worth touching on before we move on. We won’t cover everything that the Math class has to offer, though; it is a big class with lots of methods.

E and PI

The first thing the Math class offers is a definition of the special mathematical numbers π and e . These are the PI and E properties:

double areaOfCircle = Math.PI * radius * radius; // A =  πr²`
double halfOfE = Math.E / 2;

Since there is already a place that defines π, it is better to use it, rather than re-defining it yourself. That way, you avoid any chance of a typo.

Maximum and Minimum Values

The Math.Max and Math.Min methods take two values, and tell you which one is the bigger or smaller of the two:

int humanChoice = Console.ReadLine();
int myChoice = 5;
int min = Math.Min(humanChoice, myChoice);
int max = Math.Max(humanChoice, myChoice);

Square Roots and Powers

You can get the square root of a number with the Sqrt method, and you can use exponents by using the Pow (power) method:

double squareRootOfTwo = Math.Sqrt(2);
int fourCubed = Math.Pow(4, 3);

Trigonometry Functions

If you’re looking for the trigonometry functions of sine, cosine, and tangent, you’ll also find them here as Sin, Cos, and Tan:

double cosineOfPi = Math.Cos(Math.PI);

There’s More

The Math class has quite a few other methods. It is worth exploring them all at some point, but we’ve touched on the ones that are most likely to come up.