How to round to nearest even integer?

17,085

Solution 1

Try this (let's use Math.Round with MidpointRounding.AwayFromZero in order to obtain "next even value" but scaled - 2 factor):

double source = 1123.0;

// 1124.0
double result = Math.Round(source / 2, MidpointRounding.AwayFromZero) * 2;

Demo:

double[] tests = new double[] {
     1.0,
  1123.1,
  1123.0,
  1122.9,
  1122.1,
  1122.0,
  1121.5,
  1121.0,
};

string report = string.Join(Environment.NewLine, tests
  .Select(item => $"{item,6:F1} -> {Math.Round(item / 2, MidpointRounding.AwayFromZero) * 2}"));

Console.Write(report);

Outcome:

   1.0 -> 2     // In case of tie, next even value
1123.1 -> 1124
1123.0 -> 1124  // In case of tie, next even value
1122.9 -> 1122
1122.1 -> 1122
1122.0 -> 1122
1121.5 -> 1122
1121.0 -> 1122  // In case of tie, next even value

Solution 2

One liner:

double RoundToNearestEven(double value) =>
    Math.Truncate(value) + Math.Truncate(value) % 2;

Fiddle

Explanation: if we have an even number with some digits after floating point, we need to just get rid of those digits. If we have an odd number, we need to do the same and then move to the next integer that is guaranteed to be even.

P.S. Thanks to @DmitryBychenko for pointing out that casting double to long is not the brightest idea.

Solution 3

The reason you are getting the result 1123 even when using

Math.Round(1122.5196d, 0, MidpointRounding.ToEven);

is because that's exactly what you have asked the compiler to do. When rounding to even with decimals, be sure to remember that 1123.0 is even.

ie. 1122.51 rounded to even becomes 1123.0 (note that as it is a decimal, it will always keep its decimal place and therefore the .0 here makes this an even number).

Instead, I would write a function to do this, something like:

   private int round_up_to_even(double number_to_round)
    {
        int converted_to_int = Convert.ToInt32(number_to_round);
        if (converted_to_int %2 == 0) { return converted_to_int; }
        double difference = (converted_to_int + 1) - number_to_round;
        if (difference <= 0.5) { return converted_to_int + 1; }
        return converted_to_int - 1;
    }
Share:
17,085

Related videos on Youtube

Álvaro García
Author by

Álvaro García

Updated on June 15, 2022

Comments

  • Álvaro García
    Álvaro García almost 2 years

    My last goal is always to round to the nearest even integer.

    For example, the number 1122.5196 I want as result 1122. I have tried this options:

    Math.Round(1122.5196d, 0, MidpointRounding.ToEven);       // result 1123
    Math.Round(1122.5196d, 0, MidpointRounding.AwayFromZero); // result 1123
    

    At the end, what I would like to get it is always the nearest even integer. For example:

    • 1122.51 --> 1122
    • 1122.9 --> 1122 (because the nearest int is 1123 but it is odd, and 1122 is nearer than 1124)
    • 1123.0 --> 1124 (the next even value, the next higher even value)

    I only work with positive numbers.

    And so on.

    There are some method that do that or I should to implement my own method?

    • Jon Skeet
      Jon Skeet over 5 years
      What would you want the result for an input of 1123.0 to be? (Math.Round would always return 1123.0 for that...)
    • Dmitry Bychenko
      Dmitry Bychenko over 5 years
      double result = Math.Round(source / 2) * 2;
    • PiJei
      PiJei over 5 years
      If you want to just truncate the floating point digits, use Math.Truncate(number)
    • Adrian
      Adrian over 5 years
      He's not trying to truncate anything.. he's trying to round the number to the nearest even integer
    • Álvaro García
      Álvaro García over 5 years
      in the case of 1123.0 1124, the next even value.
    • Fildor
      Fildor over 5 years
      @ÁlvaroGarcía in case of an odd number, distance in both directions is equal ... so in that case the next "higher" number is what you mean by "next" ?
    • Álvaro García
      Álvaro García over 5 years
      @Fildor right, I mean the next higher.
    • Oram
      Oram over 5 years
      What about negative numbers?
    • Fildor
      Fildor over 5 years
      And in negative case? -1123 would that be -1124 ?
    • Álvaro García
      Álvaro García over 5 years
      I am working only with positive numbers.
    • Fildor
      Fildor over 5 years
      Then Dmitry nailed it already, I think.
    • Goose
      Goose over 5 years
      I have been battling with this in Excel very recently. This is known as banker's rounding. Excel spread sheet rounds "normally", the VBA Round function rounds with it.
    • David Dubois
      David Dubois over 5 years
      This is an unusual requirement. Are you sure you're not confusing this with "Banker's rounding"? With Banker's rounding, numbers that do not end in 0.5 are rounded in the standard manner. (6.4=>6; 6.9=>7); when the number is at the midpoint between two integers, round to the nearest even number. (6.5=>6; 7.5=>8 ) The first line of code in your example does exactly this.
  • Dmitry Bychenko
    Dmitry Bychenko over 5 years
    It's ok unless you are given a value beyond long.MaxValue, e.g. value = 1e100;
  • Dmitry Korolev
    Dmitry Korolev over 5 years
    @DmitryBychenko T̶h̶e̶ ̶f̶u̶n̶c̶t̶i̶o̶n̶ ̶a̶c̶c̶e̶p̶t̶s̶ ̶p̶a̶r̶a̶m̶e̶t̶e̶r̶ ̶o̶f̶ ̶t̶y̶p̶e̶ ̶d̶o̶u̶b̶l̶e̶ ̶t̶h̶a̶t̶ ̶m̶a̶y̶ ̶h̶a̶v̶e̶ ̶u̶p̶ ̶t̶o̶ ̶1̶6̶ ̶d̶i̶g̶i̶t̶s̶,̶ ̶a̶n̶d̶ ̶t̶h̶e̶n̶ ̶c̶o̶n̶v̶e̶r̶t̶s̶ ̶i̶t̶ ̶t̶o̶ ̶l̶o̶n̶g̶ ̶t̶h̶a̶t̶ ̶i̶s̶ ̶1̶9̶ ̶d̶i̶g̶i̶t̶s̶ ̶m̶a̶x̶,̶ ̶s̶o̶ ̶i̶t̶ ̶s̶h̶o̶u̶l̶d̶ ̶b̶e̶ ̶s̶a̶f̶e̶.̶
  • Dmitry Bychenko
    Dmitry Bychenko over 5 years
    you'll fail on (long)value: (long) 1e100 will not be a correct long: 1E+100 -> -9223372036854775808
  • Dmitry Bychenko
    Dmitry Bychenko over 5 years
    double RoundToNearestEven(double value) => Math.Truncate(value) + Math.Truncate(value) % 2; if we want to truncate let's do it (without unwanted and spoiling the fun long)
  • mareko
    mareko over 5 years
    MidpointRounding only specifies what happens, when decimal part is exactly 0.5. When it isn't (0.5196), normal rounding rules apply.
  • Jeppe Stig Nielsen
    Jeppe Stig Nielsen over 5 years
    @DmitryBychenko Note that as long as the absolute value of the double is still so small that a double can actually distinguish the even whole numbers from other numbers, the conversion from double to long will not lose magnitude. From 2**53 == 9.007199254740994E+15 and up, every exactly representable double is already an even whole number. Of course, it is still entirely correct that casting double 1E+100 to long will lose the original number and lead to a bad result. Correct implementations, like the one we see now, should just return the same double if over 2**53.
  • Jeppe Stig Nielsen
    Jeppe Stig Nielsen over 5 years
    The case 1123.0 -> 1124 is also (by coincidence) a round to the doubly even possibility (4 divides 1124). The case 1121.0 -> 1122 versus 1121.0 -> 1120 shows the MidpointRounding tie-breaker better.
  • Dmitry Bychenko
    Dmitry Bychenko over 5 years
    @Jeppe Stig Nielsen: Nice test exmple! 1121.0 -> 1122 thank you very much!
  • Chai T. Rex
    Chai T. Rex over 5 years
    It's likely to be faster to use 2.0d * Math.truncate(value / 2.0d). In case of the CSE optimization not firing, there's only one Math.truncate. % for doubles is also much more work than a division and a multiplication (I think it has to compute q - d * Math.truncate(q / d), so an extra truncation and subtraction). In total, I think you have an additional two truncations and an extra subtraction.
  • Ryan
    Ryan over 5 years
    To give a little more context, MidpointRounding.ToEven is useful in financial applications where you don't want random rounding errors to accumulate in a single direction over time. As a simple but contrived example, consider $1.005 + $0.995. (stored internally in units of cents) With MidpointRounding.ToEven, you get $2 rather than $2.01.
  • Peter Cordes
    Peter Cordes over 5 years
    @ChaiT.Rex: If you're going to talk about performance, multiply by 0.5 instead of dividing by 2. (The compiler hopefully does this optimization for you because 1/2 is exactly representable as a binary float, but doing it in the source would make sure). Anyway, Dmitry Bychenko already posted that as an answer, and yes it's much faster because FP remainder is slow.
  • Peter Cordes
    Peter Cordes over 5 years
    Do all C# compilers optimize source / 2 into source * 0.5? 0.5 is exactly representable as a binary double so it's perfectly safe. (FP mul is about 1/3 the latency and 10x the throughput of FP division on modern x86 hardware: Floating point division vs floating point multiplication)
  • Peter Cordes
    Peter Cordes over 5 years
    @Ryan: more usually in scientific / statistical applications like simulations. But it is also called "banker's rounding". (en.wikipedia.org/wiki/Rounding#Round_half_to_even). Anyway, more importantly, it's the default rounding mode for IEEE floating point, and supported efficiently in hardware on x86. (Although so is truncation since x87 was obsoleted by SSE/SSE2.)
  • Mark Dickinson
    Mark Dickinson over 5 years
    This isn't what the OP asked for. From the question: "For example, the number 1122.5196 I want as result 1122." A simple Decimal.Round to zero decimal places will give 1123, not 1122 for this case.