[issue41598] Adding support for rounding modes to builtin round

Mark Dickinson report at bugs.python.org
Tue Aug 25 06:30:06 EDT 2020


Mark Dickinson <dickinsm at gmail.com> added the comment:

> I don't think that this will be a very difficult feature to implement.

Agreed that it shouldn't be hard to implement, if we do the obvious thing (rounding the exact value that the float represents, rather than trying to do some sort of Do What I Mean rounding). But I'm far from convinced that it's a good idea. I think it'll increase user confusion and generate lots of spurious "Python's round is broken" bug reports.

To be clear: supporting rounding modes for rounding from float to integer is fine (i.e., supporting the ndigits=0 case, or the case where ndigits is not supplied at all). The confusion is going to arise with rounding to a particular number of decimal places with a given rounding mode.

Note: there are two parts to this - the first part is figuring out what functionality we want. The second part is figuring out how best to spell it (separate functions versus keyword arguments to round, etc.). I'm mostly ignoring the second part for now and concentrating on the first part. There's a lot to discuss with the second part too (e.g., how does the __round__ protocol change, in a way that doesn't break 3rd party types already using it; how do we specify rounding modes, etc.), but it doesn't seem so useful to have that discussion until the first part is figured out.

Two examples to illustrate, one with a round-to-nearest rounding mode, one with a directed rounding mode:

- What would you expect round(2.675, ndigits=2, mode=ROUND_HALF_UP) to give? I strongly suspect that Marco would expect and want a result of 2.68. But if we follow the existing rules for round, it's going to give 2.67.

- What would you expect round(2.712, ndigits=3, mode=ROUND_UP) to give? The binary float given by the literal 2.712 is just a touch larger than 2.712, so it should round up, giving 2.713. But again, I doubt that's what users will expect or want.  Even worse, the rounding is not idempotent: the float 2.713 is again a little larger than Decimal("2.713"), so it rounds up again: so if we round the value 2.712 twice to 3 digits using ROUND_UP, we'll get 2.714.

Tricks like the power of 10 scaling in the original post aren't really a solution: they just serve to make it even less predictable when the users' expected result will appear and when it won't, and likely give a false sense of security.

If we were to implement this, there's a choice to be made: do we (1) base the rounding on the actual float value and do correct rounding, as round currently does, or do we (2) try to do something magic that guesses what value the user actually meant, rather than rounding the exact value that the round function receives? I _really_ _really_ don't want to go down the DWIM path of option (2), but I suspect that (1) will be mostly useless for users, outside the ndigits=0 use-case. There _are_ valid use-cases for two-argument round on floats (e.g., casual binning), but for the most part round already fills those use-cases.

I'd rather add whatever bells and whistles we need (if any) to make it easier for users who care about this to use Decimal.

----------

_______________________________________
Python tracker <report at bugs.python.org>
<https://bugs.python.org/issue41598>
_______________________________________


More information about the Python-bugs-list mailing list