Round numbers in C# to decimal 0 or 5

Asked

Viewed 7,464 times

4

I need to round up numbers in such a way that they only have a decimal value of 0 or 5.

For example

1 -> 1
1.1 -> 1.5
1.4 -> 1.5
1.5 -> 1.5
1.6 -> 2
2 -> 2

Someone knows a simple way to do it?

  • How exactly is this rule? Above an integer X, round to X.5, and above X.5 round to X+1? In what context is this used?

  • Exactly that. I’m using it in a caucule system.

  • 1

    The @mgibsonbr solution got better than mine for general cases. (I would only store the result of x - y in a temporary variable, to avoid repeating the calculation 3 times)

  • 1

    @carlosrafaelgn Well observed! In practice, think that the compiler would do it by itself, but it doesn’t cost to make it explicit...

  • @mgibsonbr is, I also think so, if both variables are local. If at least one of the two is not local, think that he will do 3 times...

3 answers

7

To make this type of rounding, assuming its value is in a variable float calling for x, the following can be done:

x = (float)((int)((x + 0.4f) * 2.0f)) * 0.5f;

If it is double:

x = (double)((int)((x + 0.4) * 2.0)) * 0.5;

To round more boxes, such as 1.51 to 2, you can use 0.49f (or 0.49) instead of 0.4f.

The more houses were needed, the more 9’s would be needed after the 0.4.

  • Thank you!! I am beginner in C# did not know this solution.

  • @Lucasanjos Se x = 1.51 the result will be 1.5. That’s exactly what you want?

  • 1

    @mgibsonbr well noticed! I will edit, to contemplate cases with more decimal places.

  • @carlosrafaelgn Really, thank you his adapts better to my case.

7


Given the specificity of the rule, I suggest separating the whole from the fractional part and - as desired - adding 0.5, 1 or nothing:

int y = (int)x; // Descarta a parte decimal, arredondando pra baixo
if ( 0 < x - y && x - y <= 0.5 ) // Se a parte decimal for não-nula e menor ou igual a 0.5
    x = y + 0.5;                 // acrescenta 0.5 à parte inteira
else if ( 0.5 < x - y ) // Se a parte decimal for não-nula e maior a 0.5
    x = y + 1;          // arredonda pro próximo inteiro
  • 2

    Indeed, for a general case, this solution is safer :)

  • Thank you @mgibsonbr, for your concern!

4

I decided to answer because there are problems in the other answers. They are not wrong, there is a better solution. They do not contemplate negatives and there are performance problems, although this is not very relevant. One of them I find an ugly solution and the other unnecessarily complicated. I hope I have not created other problems in my.

I would start by doing something simple and elegant:

Ceiling(value * 2) / 2

This solution does not solve the problem of negative numbers, it just shows how it can be simplified. The solution of the negatives does not make it much more complicated.

Complete solution

For guys Decimal and double. If you need it, it’s very easy to create for float. Note that I am using the Roslyn compiler (C# 6, see more information on What is the correct way to call the versions of C#?). If you need to use an older compiler just delete the last two using and call static methods with the class name. If you prefer to use without Extension method not to pollute the namespace, just take the this in the first parameter.

using System;
using static System.Console;
using static System.Math;

public class Program {
    public static void Main() {
        WriteLine("Decimal");
        for (var valor = -1M; valor <= 1M; valor += 0.05M) WriteLine("{0:N1} => {1:N1}", valor, valor.RoundMidPoint());
        WriteLine("Double");
        for (var valor = -1.0; valor <= 1.0; valor += 0.05) WriteLine("{0:N1} => {1:N1}", valor, valor.RoundMidPoint());
    }
}

public static class RoundUtil {
    public static Decimal RoundMidPoint(this Decimal value) => Sign(value) * Ceiling(Abs(value) * 2) / 2;

    public static double RoundMidPoint(this double value) => Sign(value) * Ceiling(Abs(value) * 2) / 2;
}

Behold working in the ideone. And in the .NET Fiddle. Also put on the Github for future reference.

Performance

I did another test to evaluate the performance of the algorithms. In it we can notice that my solution is much faster. I still did a test, not posted, without solving the problem of the negatives, which would be fairer, and the difference is bigger, it’s more than twice as long. I made several combinations and left only the most relevant ones. I hope I have not committed injustices. On my machine the results were consistently:

  • Decimal Rounding: 131 ms
  • Double Rounding: 138 ms
  • Decimal mgibsonbr Rounding: 240 ms
  • Double carlosrafaelgn Rounding: 247 ms

Code:

using System;
using static System.Console;
using static System.Math;
using System.Diagnostics;
                    
public class Program {
    public static void Main() {
        var tempo = new Stopwatch();
        WriteLine("Decimal");
        tempo.Start();
        for (var valor = -10000M; valor <= 10000M; valor += 0.05M) valor.RoundMidPoint();
        tempo.Stop();
        WriteLine("Arredondando em ms: {0}", tempo.ElapsedMilliseconds);
        WriteLine("Double");
        tempo.Start();
        for (var valor = -10000.0; valor <= 10000.0; valor += 0.05) valor.RoundMidPoint();
        tempo.Stop();
        WriteLine("Arredondando em ms: {0}", tempo.ElapsedMilliseconds);
        WriteLine("Decimal Alternativo");
        tempo.Start();
        for (var valor = -10000M; valor <= 10000M; valor += 0.05M) valor.RoundMidPointAlt();
        tempo.Stop();
        WriteLine("Arredondando em ms: {0}", tempo.ElapsedMilliseconds);
        WriteLine("Double Alternativo");
        tempo.Start();
        for (var valor = -10000.0; valor <= 10000.0; valor += 0.05) valor.RoundMidPointAlt();
        tempo.Stop();
        WriteLine("Arredondando em ms: {0}", tempo.ElapsedMilliseconds);
    }
}

public static class RoundUtil {
    public static Decimal RoundMidPoint(this Decimal value) => Sign(value) * Ceiling(Abs(value) * 2) / 2;
    
    public static double RoundMidPoint(this double value) => Sign(value) * Ceiling(Abs(value) * 2) / 2;
    
    public static Decimal RoundMidPointAlt(this Decimal value) {
        int intPart = (int)value;
        decimal decimalPart = value - intPart;
        if (0 < decimalPart && decimalPart <= 0.5M) return intPart + 0.5M;
        else if (0.5M < decimalPart) return intPart + 1;
        else return intPart;
    }

    public static double RoundMidPointAlt(this double value) {
        int intPart = (int)value;
        double decimalPart = value - intPart;
        if (0 < decimalPart && decimalPart <= 0.5) return intPart + 0.5;
        else if (0.5 < decimalPart) return intPart + 1;
        else return intPart;
    }
}

Behold working in the ideone. And in the .NET Fiddle. Also put on the Github for future reference.

A cast is not a magic operation, a conversion is necessary and something more complex than a Ceiling is held in it.

The performance may not be so important, but I see no reason, in this case, to avoid something faster. After all it is simpler.

Another interesting observation is that the Decimal runs slightly faster than the double in all situations. There are programmers who think that double is solution to performance problems for numerical calculations. Not in any situation.

Suggestions for future improvements

If necessary, it is possible to allow the configuration of the midpoint and round to 0,4 or 0,7 instead of 0,5, for example. I will not go into detail because some precise definitions are required to treat it properly.

It is also possible to set how many decimal places you want to round. I did not run a test but roughly this would be very easy:

public static Decimal RoundMidPoint(this Decimal value, int decimais) => Sign(value) * Ceiling(Abs(value) * (2 * Pow(10, decimais - 1)) / (2 * Pow(10, decimais - 1);

You can think about it in other ways, I haven’t thought about it much. It might be better to put Steps and not the amount of decimals that should round. Anyway, it’s just an initial idea to develop. There is probably something wrong but go to the initial example:

public static Decimal RoundStep(this Decimal value, Decimal step) => Sign(value) * Ceiling(Abs(value) / step) * step;

Surely you can find a better name for the method RoundMidPoint, accept suggestions.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.