Updated on

To round a number down to the nearest integer in C#, we call Math.Floor() and cast the result to int: (int)Math.Floor(-1.25) gives -2. A plain cast gives -1 instead.

Negative numbers are where most of the usual candidates go wrong. So we test the methods that would probably first cross a developer’s mind on the same positive and negative values, and see which of them really round down.

To download the source code for this article, you can visit our GitHub repository.

We will explore Math.Floor(), Math.Truncate(), Math.Round() with different MidpointRounding modes, Convert.ToInt32(), casting to an integer, and subtraction using the modulo operator. We will test each of those methods with different edge cases, and then look at rounding up and at integer division.

Set up the Stage

We will define an array of double values. Four are halfway between two integers, two are closer to the bigger integer, and two are closer to the smaller one. With these examples, we are covering all possible different outputs of tested methods. 

Here is the defined array:

var testCases = new double[] { 2.5, 1.75, 1.5, 1.25, -1.25, -1.5, -1.75, -2.5 };

We want to round down numbers from the test cases to the nearest integer number. That means that rounding the numbers halfway from integers or those closer to bigger integers results in a smaller integer. For example, both 1.75 and 1.25 are rounded down to 1. And for -1.25 and -1.75 the next smaller integer is -2.

Our desired results for the example test cases look like this:

{ 2, 1, 1, 1, -2, -2, -2, -3 };

How Does Math.Floor() Round Down in C#?

Math.Floor() returns the largest integral value that is less than or equal to its argument. In plain words, it always moves down the number line, toward negative infinity.

For positive numbers, that looks like dropping the fraction: Math.Floor(1.75) is 1. For negative numbers, it is the step people forget. Math.Floor(-1.25) is -2, not -1, because -2 is the first integer below -1.25.

Midpoints need no special handling either. Math.Floor(2.5) is 2 and Math.Floor(-2.5) is -3, which is exactly what rounding down means.

The method has overloads for double and decimal, and it returns the type it receives. So Math.Floor(1.75) hands back the double 1, and we still need a cast to get an int.

That cast loses nothing, because no fraction is left to lose. As long as the value fits into an int, (int)Math.Floor(value) is the whole recipe. We look at values that do not fit in the casting section.

We keep every method in a static RoundingMethods class. Here is the method that floors every test case and casts the result:

public static int[] RoundDownUsingMathFloor(double[] testCases)
{
    var results = new int[testCases.Length];

    for (var cnt = 0; cnt < testCases.Length; cnt++)
    {
        results[cnt] = (int)Math.Floor(testCases[cnt]);
    }

    return results;
}

When we call it with each of the defined input values, the results are: 

Round down using Math.Floor():
Rounding 2.5 results in 2 (desired result: 2)
Rounding 1.75 results in 1 (desired result: 1)
Rounding 1.5 results in 1 (desired result: 1)
Rounding 1.25 results in 1 (desired result: 1)
Rounding -1.25 results in -2 (desired result: -2)
Rounding -1.5 results in -2 (desired result: -2)
Rounding -1.75 results in -2 (desired result: -2)
Rounding -2.5 results in -3 (desired result: -3)

Math.Floor() is indeed returning values rounded down to the nearest integer.

Math.Truncate() Method

Math.Truncate() returns an integral part of a number. It returns double, so we need to cast results to an integer:

public static int[] RoundDownUsingMathTruncate(double[] testCases)
{
    var results = new int[testCases.Length];

    for (var cnt = 0; cnt < testCases.Length; cnt++)
    {
        results[cnt] = (int)Math.Truncate(testCases[cnt]);
    }

    return results;
}

The results of our testing are:

Round down using Math.Truncate():
Rounding 2.5 results in 2 (desired result: 2)
Rounding 1.75 results in 1 (desired result: 1)
Rounding 1.5 results in 1 (desired result: 1)
Rounding 1.25 results in 1 (desired result: 1)
Rounding -1.25 results in -1 (desired result: -2)
Rounding -1.5 results in -1 (desired result: -2)
Rounding -1.75 results in -1 (desired result: -2)
Rounding -2.5 results in -2 (desired result: -3)

We can see that it returns the expected result only for positive numbers.

Use Math.Round() With a MidpointRounding Mode

Math.Round() rounds the value to the nearest integral value or the provided number of fractional digits. It will return double, so we must cast the result to int.

Rounding to a fixed number of decimal places is a different job, which we cover in our article on decimal precision in C#.

This method comes with several overloads. Some have a MidpointRounding parameter that determines the rounding convention or modes. For ToEven and AwayFromZero, the value of this parameter affects only results for numbers halfway between two integer values (values 2.5, 1.5, -1.5, and -2.5 from our example). The other three modes are directed: they push every value in one direction, midpoint or not. That is why ToPositiveInfinity turns 1.25 into 2 below. .NET defines five possible values for this parameter, and we will explore each.

MidpointRounding.ToEven Mode

This is the default mode. When the number is halfway between two integers, this mode will round it to the nearest even number. This strategy is also known as banker’s rounding:

public static int[] RoundDownUsingMathRoundWithToEvenMode(double[] testCases)
{
    var results = new int[testCases.Length];

    for (var cnt = 0; cnt < testCases.Length; cnt++)
    {
        results[cnt] = (int)Math.Round(testCases[cnt], MidpointRounding.ToEven);
    }

    return results;
}

Here are the results:

Round down using Math.Round() with ToEven mode:
Rounding 2.5 results in 2 (desired result: 2)
Rounding 1.75 results in 2 (desired result: 1)
Rounding 1.5 results in 2 (desired result: 1)
Rounding 1.25 results in 1 (desired result: 1)
Rounding -1.25 results in -1 (desired result: -2)
Rounding -1.5 results in -2 (desired result: -2)
Rounding -1.75 results in -2 (desired result: -2)
Rounding -2.5 results in -2 (desired result: -3)

MidpointRounding.AwayFromZero Mode

AwayFromZero mode will round the number to the nearest integer. When the number is halfway, this mode will round it to the nearest number away from zero:

public static int[] RoundDownUsingMathRoundWithAwayFromZeroMode(double[] testCases)
{
    var results = new int[testCases.Length];

    for (var cnt = 0; cnt < testCases.Length; cnt++)
    {
        results[cnt] = (int)Math.Round(testCases[cnt], MidpointRounding.AwayFromZero);
    }

    return results;
}

Results are:

Round down using Math.Round() with AwayFromZero mode:
Rounding 2.5 results in 3 (desired result: 2)
Rounding 1.75 results in 2 (desired result: 1)
Rounding 1.5 results in 2 (desired result: 1)
Rounding 1.25 results in 1 (desired result: 1)
Rounding -1.25 results in -1 (desired result: -2)
Rounding -1.5 results in -2 (desired result: -2)
Rounding -1.75 results in -2 (desired result: -2)
Rounding -2.5 results in -3 (desired result: -3)

MidpointRounding.ToZero Mode

ToZero rounding mode will return values toward zero:

public static int[] RoundDownUsingMathRoundWithToZeroMode(double[] testCases)
{
    var results = new int[testCases.Length];

    for (var cnt = 0; cnt < testCases.Length; cnt++)
    {
        results[cnt] = (int)Math.Round(testCases[cnt], MidpointRounding.ToZero);
    }

    return results;
}

Results are:

Round down using Math.Round() with ToZero mode:
Rounding 2.5 results in 2 (desired result: 2)
Rounding 1.75 results in 1 (desired result: 1)
Rounding 1.5 results in 1 (desired result: 1)
Rounding 1.25 results in 1 (desired result: 1)
Rounding -1.25 results in -1 (desired result: -2)
Rounding -1.5 results in -1 (desired result: -2)
Rounding -1.75 results in -1 (desired result: -2)
Rounding -2.5 results in -2 (desired result: -3)

With this mode, desired results are only for positive values. 

MidpointRounding.ToNegativeInfinity Mode

This mode will round numbers down in the direction of negative infinity:

public static int[] RoundDownUsingMathRoundWithToNegativeInfinityMode(double[] testCases)
{
    var results = new int[testCases.Length];

    for (var cnt = 0; cnt < testCases.Length; cnt++)
    {
        results[cnt] = (int)Math.Round(testCases[cnt], MidpointRounding.ToNegativeInfinity);
    }

    return results;
}

Here are the results:

Round down using Math.Round() with ToNegativeInfinity mode:
Rounding 2.5 results in 2 (desired result: 2)
Rounding 1.75 results in 1 (desired result: 1)
Rounding 1.5 results in 1 (desired result: 1)
Rounding 1.25 results in 1 (desired result: 1)
Rounding -1.25 results in -2 (desired result: -2)
Rounding -1.5 results in -2 (desired result: -2)
Rounding -1.75 results in -2 (desired result: -2)
Rounding -2.5 results in -3 (desired result: -3)

The results are the same as desired results. 

With no decimal places, this mode does exactly what Math.Floor() does, which is why the two sets of results match.

MidpointRounding.ToPositiveInfinity Mode

ToPositiveInfinity will round numbers up in the direction of positive infinity:

public static int[] RoundDownUsingMathRoundWithToPositiveInfinityMode(double[] testCases)
{
    var results = new int[testCases.Length];

    for (var cnt = 0; cnt < testCases.Length; cnt++)
    {
        results[cnt] = (int)Math.Round(testCases[cnt], MidpointRounding.ToPositiveInfinity);
    }

    return results;
}

Results are:

Round down using Math.Round() with ToPositiveInfinity mode:
Rounding 2.5 results in 3 (desired result: 2)
Rounding 1.75 results in 2 (desired result: 1)
Rounding 1.5 results in 2 (desired result: 1)
Rounding 1.25 results in 2 (desired result: 1)
Rounding -1.25 results in -1 (desired result: -2)
Rounding -1.5 results in -1 (desired result: -2)
Rounding -1.75 results in -1 (desired result: -2)
Rounding -2.5 results in -2 (desired result: -3)

Use Convert.ToInt32() to Round a Number

Convert.ToInt32(), one of the methods of the Convert class, converts the value to a 32-bit signed integer:

public static int[] RoundDownUsingConvertToInt32(double[] testCases)
{
    var results = new int[testCases.Length];

    for (var cnt = 0; cnt < testCases.Length; cnt++)
    {
        results[cnt] = Convert.ToInt32(testCases[cnt]);
    }

    return results;
}

And here are the results:

Round down using Convert.ToInt32():
Rounding 2.5 results in 2 (desired result: 2)
Rounding 1.75 results in 2 (desired result: 1)
Rounding 1.5 results in 2 (desired result: 1)
Rounding 1.25 results in 1 (desired result: 1)
Rounding -1.25 results in -1 (desired result: -2)
Rounding -1.5 results in -2 (desired result: -2)
Rounding -1.75 results in -2 (desired result: -2)
Rounding -2.5 results in -2 (desired result: -3)

We can notice that Convert.ToInt32() gives the same results as Math.Round() with MidpointRounding.ToEven mode.

For a double, Convert.ToInt32() rounds to the nearest integer and sends a value halfway between two integers to the even one, so Convert.ToInt32(2.5) is 2 while Convert.ToInt32(3.5) is 4. That makes it the wrong tool for rounding down.

Does Casting to int Round Down in C#?

Only for positive numbers. A cast from double to int doesn’t round to the nearest integer. It drops the fractional part, which moves the value toward zero.

For 1.75, toward zero and down are the same direction, so (int)1.75 is 1 and the cast looks like it rounds down. For -1.75, they point opposite ways. Toward zero gives -1, while rounding down gives -2.

That is why casting matches Math.Truncate() and MidpointRounding.ToZero on every one of our test cases, and misses all four negative ones.

If our values can be negative, we floor first and cast second, as in (int)Math.Floor(value).

The same holds for decimal: (int)-1.75m is -1 as well.

There is a second trap, and it applies to double, not decimal. Outside a checked context, a cast doesn’t throw for a value that is too big for an int, and on current .NET it clamps the value silently. We show that right after the results below.

The C# language reference states the rule plainly: a double converted to an integral type “is rounded toward zero to the nearest integral value.”

Number line from -3 to 3. At 1.75, Math.Floor and an int cast both go to 1. At -1.75, Math.Floor goes to -2 while the cast goes to -1.

Math.Floor always moves left and a cast always moves toward zero, so the two agree only on the positive side.

Explicit conversion, or casting, instructs the compiler to which type we want to convert the value (we go through the rules in our article on type casting in C#). In our case, we are using (int):

public static int[] RoundDownUsingCasting(double[] testCases)
{
    var results = new int[testCases.Length];

    for (var cnt = 0; cnt < testCases.Length; cnt++)
    {
        results[cnt] = (int)testCases[cnt];
    }

    return results;
}

Results are: 

Round down using casting:
Rounding 2.5 results in 2 (desired result: 2)
Rounding 1.75 results in 1 (desired result: 1)
Rounding 1.5 results in 1 (desired result: 1)
Rounding 1.25 results in 1 (desired result: 1)
Rounding -1.25 results in -1 (desired result: -2)
Rounding -1.5 results in -1 (desired result: -2)
Rounding -1.75 results in -1 (desired result: -2)
Rounding -2.5 results in -2 (desired result: -3)

We notice that casting to an integer value gives the same results as Math.Round() with MidpointRounding.ToZero mode. Casting gives desired results only for positive values.

What happens when the value does not fit into an int at all? Here, the cast and Convert.ToInt32() part ways:

var tooBig = 1e10;
var notANumber = double.NaN;

Console.WriteLine((int)tooBig);              // 2147483647
Console.WriteLine((int)Math.Floor(-tooBig)); // -2147483648
Console.WriteLine((int)notANumber);          // 0
Console.WriteLine(Convert.ToInt32(tooBig));  // throws OverflowException

Since .NET 9, a cast from double to int saturates on x86 and x64. A value above int.MaxValue becomes int.MaxValue, a value below int.MinValue becomes int.MinValue, and NaN becomes 0. Nothing throws and nothing warns us, so the program carries on with a wrong number. Convert.ToInt32() throws an OverflowException instead, and so does a cast inside a checked expression.

So when a silently clamped result would be a bug, checked((int)Math.Floor(value)) is the version to use.

Calculate Subtraction With Modulo

The idea here is to subtract the result of the modulo (%) operator with 1 (resulting in a fractional part of the input number) from the input number. The result is an integral number:

public static int[] RoundDownUsingSubtractionWithModulo(double[] testCases)
{
    var results = new int[testCases.Length];

    for (var cnt = 0; cnt < testCases.Length; cnt++)
    {
        results[cnt] = (int)(testCases[cnt] - (testCases[cnt] % 1));
    }

    return results;
}

Results are:

Round down using subtraction with modulo:
Rounding 2.5 results in 2 (desired result: 2)
Rounding 1.75 results in 1 (desired result: 1)
Rounding 1.5 results in 1 (desired result: 1)
Rounding 1.25 results in 1 (desired result: 1)
Rounding -1.25 results in -1 (desired result: -2)
Rounding -1.5 results in -1 (desired result: -2)
Rounding -1.75 results in -1 (desired result: -2)
Rounding -2.5 results in -2 (desired result: -3)

This approach will give the same result as Math.Round() with MidpointRounding.ToZero and casting to an integer. The method returns desired results only for positive numbers.

Here are all the approaches side by side, on three of our test cases:

Approach1.75-1.25-2.5Rounds every value down?
(int)Math.Floor(x)1-2-3Yes
(int)Math.Round(x, MidpointRounding.ToNegativeInfinity)1-2-3Yes
(int)Math.Truncate(x)1-1-2Positive values only
(int)Math.Round(x, MidpointRounding.ToZero)1-1-2Positive values only
(int)x1-1-2Positive values only
(int)(x - (x % 1))1-1-2Positive values only
(int)Math.Round(x, MidpointRounding.ToEven)2-1-2No, rounds to nearest
Convert.ToInt32(x)2-1-2No, rounds to nearest
(int)Math.Round(x, MidpointRounding.AwayFromZero)2-1-3No, rounds to nearest
(int)Math.Round(x, MidpointRounding.ToPositiveInfinity)2-1-2No, rounds up

Two rows round down every value. Math.Floor() says what it does in its name, so that is the one we reach for.

How Do We Round Up to the Nearest Integer in C#?

Rounding up is the mirror image of rounding down. Math.Ceiling() returns the smallest integral value that is greater than or equal to its argument, so it moves every value toward positive infinity. It sits right next to Math.Floor() in the Math class in C#:

public static int[] RoundUpUsingMathCeiling(double[] testCases)
{
    var results = new int[testCases.Length];

    for (var cnt = 0; cnt < testCases.Length; cnt++)
    {
        results[cnt] = (int)Math.Ceiling(testCases[cnt]);
    }

    return results;
}

For our test cases, it returns { 3, 2, 2, 2, -1, -1, -1, -2 }. Note that -1.75 becomes -1. Up means toward positive infinity, not away from zero.

Those are the same results that Math.Round() gives with MidpointRounding.ToPositiveInfinity.

How Does Integer Division Round in C#?

When both operands are integers, the / operator does not produce a fraction at all. It truncates toward zero, just like a cast (the arithmetic operators in C# article introduces / and % among the other operators):

Console.WriteLine(7 / 2);   // 3
Console.WriteLine(-7 / 2);  // -3

So integer division rounds down for positive results and up for negative ones. When we need the floor of the quotient, we correct the result by one whenever the remainder is not zero and its sign differs from the divisor’s:

public static int DivideAndRoundDown(int dividend, int divisor)
{
    var quotient = dividend / divisor;
    var remainder = dividend % divisor;

    return (remainder != 0 && (remainder < 0) != (divisor < 0))
        ? quotient - 1
        : quotient;
}

Now DivideAndRoundDown(-7, 2) returns -4, and DivideAndRoundDown(7, 2) still returns 3.

We could also convert both numbers to double and call Math.Floor(). That works, but it takes a trip through floating point to answer a question about integers.

Conclusion

In this article, we’ve seen that only Math.Floor() and Math.Round() with MidpointRounding.ToNegativeInfinity will give desired results. Math.Truncate(), Math.Round() with MidpointRounding.ToZero, casting to an integer and subtraction with modulo will give desired results only for positive numbers. All other methods won’t produce desired results.

When the input might not fit into an int, we wrap the cast in checked, or pass the floored value to Convert.ToInt32(), so a bad value throws instead of being clamped.

Tested with .NET 10.0.10 and MSTest 4.5.1.