3

With C# I need an elegant way to round a number with a resolution of 1/8 to a resolution of 1/2 with the following rules:

Round(0) = 0
Round(0.125) = 0
Round(0.25) = 0
Round(0.375) = 0.5
Round(0.5) = 0.5
Round(0.625) = 0.5
Round(0.75) = 0.5
Round(0.875) = 1
Round(1) = 1

Of course that I'm not limited to only numbers between 0 and 1. Is there a good way to do it without too many ifs?

Soner Gönül
  • 97,193
  • 102
  • 206
  • 364
CodeMonkey
  • 11,196
  • 30
  • 112
  • 203

3 Answers3

1

The following code snippet demonstrates what you want. It uses a technique of multiplying by two (so that what we round to then becomes a whole number) and taking off a tiny amount. We subtract the 0.001M because we want 0.25 to round to 0 and 0.75 to round to 0.5. Normally when rounding after multiplying by two they would be rounded up. Takign the small amount off ensures that we are rounding 0.499 and 1.499 to get 0 and 1 which then give the right result when divided by two. The two midpoint rounding techniques offered, away form zero and to even will not do what we want here.

public decimal MyRound(decimal input)
{
    return Math.Round(input*2-0.001M, MidpointRounding.AwayFromZero)/2;
}

void Main()
{
    var testvalues = new decimal[]{0M,0.125M,0.25M,0.375M,0.5M,0.625M,0.75M,0.875M,1M};
    foreach (var value in testvalues)
    {
        Console.WriteLine(String.Format("{0} rounds to {1}",value, MyRound(value)));
    }
}


Output: 
0 rounds to 0
0.125 rounds to 0
0.25 rounds to 0
0.375 rounds to 0.5
0.5 rounds to 0.5
0.625 rounds to 0.5
0.75 rounds to 0.5
0.875 rounds to 1
1 rounds to 1

I should note that I used decimal just because I had it in my brain that this was what you were using (I think because my brain had "decimal places" in it. The technique will work just as well for doubles...

Chris
  • 27,210
  • 6
  • 71
  • 92
1
public double Round(double input)
{
    var rounded = Math.Round(input*2-0.0001)/2;
    return rounded;
}
Mikael Engver
  • 4,634
  • 4
  • 46
  • 53
0

If overflow will not be an issue, then you can round to the nearest 1/8 by first multiplying by 8; rounding to the nearest integer; and dividing back by 8 to get the original scale. This works for any rational fraction.

If overflow will be an issue you can take advantage of the fact that 8 is a power of two, and manipulate the bits. Let's number the bits from left (least significant) as 0 to right (most significant) as n-1, where n will be 16 or 32 or 64 depending on your integer size. With that terminology, first store bit 2 (the 4's bit). Then bit-shift your number right by 3, and if the 4's bit was set add 1. This technique can be used for any fraction that is an exact fractional power of 2.

Update: As noted below, I originally misread the question. However the technique described above still works, although the details vary slightly.

Steps:

  1. Multiply by 8 and convert to an integer type.
  2. Bit-shift first right and then left by 2 bits, and round by the 4's bit.
  3. Convert back to the original floating-point type.

Or, dig into the binary representation of the floating-point type and manipulate the mantissa's bits directly.

Pieter Geerkens
  • 11,775
  • 2
  • 32
  • 52