How to round to nearest even integer?
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;
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;
}
Related videos on Youtube
Álvaro García
Updated on June 15, 2022Comments
-
Á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 result1122
. 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 is1123
but it is odd, and1122
is nearer than1124
)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 over 5 yearsWhat would you want the result for an input of 1123.0 to be? (Math.Round would always return 1123.0 for that...)
-
Dmitry Bychenko over 5 years
double result = Math.Round(source / 2) * 2;
-
PiJei over 5 yearsIf you want to just truncate the floating point digits, use
Math.Truncate(number)
-
Adrian over 5 yearsHe's not trying to truncate anything.. he's trying to round the number to the nearest even integer
-
Álvaro García over 5 yearsin the case of 1123.0 1124, the next even value.
-
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 over 5 years@Fildor right, I mean the next higher.
-
Oram over 5 yearsWhat about negative numbers?
-
Fildor over 5 yearsAnd in negative case? -1123 would that be -1124 ?
-
Álvaro García over 5 yearsI am working only with positive numbers.
-
Fildor over 5 yearsThen Dmitry nailed it already, I think.
-
Goose over 5 yearsI 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 over 5 yearsThis 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 over 5 yearsIt's ok unless you are given a value beyond
long.MaxValue
, e.g.value = 1e100;
-
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 over 5 yearsyou'll fail on
(long)value
:(long) 1e100
will not be a correctlong
:1E+100 -> -9223372036854775808
-
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 funlong
) -
mareko over 5 yearsMidpointRounding only specifies what happens, when decimal part is exactly 0.5. When it isn't (0.5196), normal rounding rules apply.
-
Jeppe Stig Nielsen over 5 years@DmitryBychenko Note that as long as the absolute value of the
double
is still so small that adouble
can actually distinguish the even whole numbers from other numbers, the conversion fromdouble
tolong
will not lose magnitude. From2**53 == 9.007199254740994E+15
and up, every exactly representabledouble
is already an even whole number. Of course, it is still entirely correct that castingdouble
1E+100
tolong
will lose the original number and lead to a bad result. Correct implementations, like the one we see now, should just return the samedouble
if over2**53
. -
Jeppe Stig Nielsen over 5 yearsThe case
1123.0 -> 1124
is also (by coincidence) a round to the doubly even possibility (4 divides 1124). The case1121.0 -> 1122
versus1121.0 -> 1120
shows theMidpointRounding
tie-breaker better. -
Dmitry Bychenko over 5 years@Jeppe Stig Nielsen: Nice test exmple!
1121.0 -> 1122
thank you very much! -
Chai T. Rex over 5 yearsIt'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 oneMath.truncate
.%
fordouble
s is also much more work than a division and a multiplication (I think it has to computeq - 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 over 5 yearsTo 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 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 because1/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 over 5 yearsDo all C# compilers optimize
source / 2
intosource * 0.5
?0.5
is exactly representable as a binarydouble
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 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 over 5 yearsThis 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 give1123
, not1122
for this case.