Rounding numbers in C# being the decimal place 0 or 5

I need to subtract numbers such that they have only one decimal place worth 0 or 5.

For example

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

Does anyone know a simple way to do this?

Author: Maniero, 2014-05-17

3 answers

Given the specificity of the rule, I suggest separating the entire perte 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
 6
Author: mgibsonbr, 2014-05-17 18:49:04

To do this type of rounding, assuming its value is in a variable float called x, one can do the following:

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

If double:

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

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

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

 6
Author: carlosrafaelgn, 2014-05-17 18:51:26

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 didn't create any other problems in mine.

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 types Decimal and double. If you need it, it is very easy to create for float. Note that I am using the Roslyn compiler (C # 6, See More info at What is the correct way to call C # versions?). If you need to use an older compiler, just delete the last two using and call the static methods with the class name. If you prefer to use without extension method to not 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;
}

See working on ideone. And no .NET Fiddle. Also I put on 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 negatives, which it would be fairer, and the difference is greater, enough to be more than twice the time. I made several combinations and left only the most relevant ones. I hope I didn't commit injustice. 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;
    }
}

See working on ideone. And no .NET Fiddle. Also I put on GitHub for future reference .

A cast is not a magic operation, a conversion is required and something more complex than a Ceiling is performed on it.

Performance may not be as important, but I see no reason in this case to avoid something faster. Plus everything is simpler.

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

Suggestions for future improvements

If necessary, allow the configuration of midpoint and subtract to 0.4 or 0.7 instead of 0.5, for example. I will not go into detail because some precise definitions are needed to treat it properly.

You can also let configure how many decimal places you want to round. I did not do a test but basically 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 of this in other ways, I didn't think much about it. It may be better to put steps and not the amount of decimals that should subtract. Anyway, it's just an initial idea to develop. Probably has something wrong but goes in initial example:

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

You can certainly find a better name for the method RoundMidPoint, I accept suggestions.

 3
Author: Maniero, 2020-09-04 15:13:18