0

I am developing an application in python, with some parts written in c# (for speed up), and I am baffled why c# floating point rounding behaves kind of "opposite" to python when using "round" function and string floating point formatting functions, here's an example:

Python (2.7)

>>> round(6.25, 1)
6.3
>>> "%.1f"%6.25
6.2

C#

>>> Math.Round(6.25,1)
6.2
>>> (6.25).ToString("F1")
6.3

Does anybody understand why the behavior is seemingly "reversed" between Python vs c#? Is there a way to round a "double" floating point value to N decimal digits to produce guaranteed same string output between Python and C#?

glexey
  • 811
  • 7
  • 25

3 Answers3

4

This is due to the midpoint resolution of the two methods you are calling.

Python's round(number[, ndigits])

Return the floating point value number rounded to ndigits digits after the decimal point. If ndigits is omitted, it defaults to zero. The result is a floating point number. Values are rounded to the closest multiple of 10 to the power minus ndigits; if two multiples are equally close, rounding is done away from 0 (so, for example, round(0.5) is 1.0 and round(-0.5) is -1.0).

C#'s Math.Round(double, int)

Rounds a double-precision floating-point value to a specified number of fractional digits, and rounds midpoint values to the nearest even number.

In other words:

  • Python rounds away from zero (6.25 becomes 6.3 because 6.3 is further from 0 than 6.2)
  • C# rounds to the nearest even number so 6.25 becomes 6.2 because 6.2 is the nearest even number.

You can fix this by using one of the overloads to Math.Round which takes a MidpointRounding enum value such as Round(double, int, MidpointRounding) , e.g.:

Math.Round(6.25, 1,  MidpointRounding.AwayFromZero);

which will do the same thing as the Python rounding.

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
2

Referring to Banker's Rounding.
It is an implementation detail on purpose. If you wish to retain the 'always round 0.5 up' method or whichever other way of rounding you desire, you can do so by doing the following:

import decimal
#The rounding you are looking for
decimal.Decimal('3.5').quantize(decimal.Decimal('1'), rounding=decimal.ROUND_HALF_UP)
>>> Decimal('4')
decimal.Decimal('2.5').quantize(decimal.Decimal('1'), rounding=decimal.ROUND_HALF_UP)
>>> Decimal('3')


#Other kinds of rounding
decimal.Decimal('2.5').quantize(decimal.Decimal('1'), rounding=decimal.ROUND_HALF_EVEN)
>>> Decimal('2')

decimal.Decimal('3.5').quantize(decimal.Decimal('1'), rounding=decimal.ROUND_HALF_DOWN)
>>> Decimal('3')
ycx
  • 3,155
  • 3
  • 14
  • 26
  • Thanks for the link on python2 vs python3 differences, I didn't realize they implement round() differently, and my question only applies to 2.7 then. Looks like python3 round() is same as c#. – glexey Jan 03 '19 at 18:59
  • @glexey You're welcome. I thought that I might as well provide you all the documentation you require on this subject since its an implementation detail that is fairly specific – ycx Jan 03 '19 at 19:10
2

Math.Round uses banker's rounding by default, which rounds to the nearest even value (e.g. Math.Round(6.25,1) -> 6.2, as you've seen, and Math.Round(6.35,1) -> 6.4). To change this, use specify the MidpointRounding value, like Math.Round(6.25, 1, MidpointRounding.AwayFromZero).

As for the other issue, double.ToString() results in rounding. Refer to C# Double - ToString() formatting with two decimal places but no rounding for solutions to this issue.

N.D.C.
  • 1,601
  • 10
  • 13