classification
Title: Round default argument for "ndigits"
Type: Stage:
Components: Documentation, Interpreter Core Versions: Python 3.5
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: docs@python Nosy List: JBernardo, docs@python, mark.dickinson, python-dev, r.david.murray, terry.reedy, vajrasky
Priority: normal Keywords: patch

Created on 2013-12-09 04:24 by JBernardo, last changed 2015-04-15 20:16 by steve.dower. This issue is now closed.

Files
File name Uploaded Description Edit
fix_round_with_zero_ndigits.patch vajrasky, 2013-12-09 06:52 review
fix_doc_round_ndigits.patch vajrasky, 2013-12-09 08:58 review
fix_doc_round_ndigits_v2.patch vajrasky, 2013-12-09 14:55 review
fix_doc_ndigits_round_and_add_None_ndigits.patch vajrasky, 2013-12-10 04:42 review
Messages (17)
msg205647 - (view) Author: João Bernardo (JBernardo) Date: 2013-12-09 04:24
From the docs for built-in function "round":
   "If ndigits is omitted, it defaults to zero"
   (http://docs.python.org/3/library/functions.html#round)

But, the only way to get an integer from `round` is by not having the second argument (ndigits):

    >>> round(3.5)
    4
    >>> round(3.5, 1)
    3.5
    >>> round(3.5, 0)
    4.0
    >>> round(3.5, -1)
    0.0
    >>> round(3.5, None)
    Traceback (most recent call last):
      File "<pyshell#6>", line 1, in <module>
        round(3.5, None)
    TypeError: 'NoneType' object cannot be interpreted as an integer


Either the docs are wrong or the behavior is wrong. I think it's easier to fix the former...

But also there should be a way to make round return an integer (e.g. passing `None` as 2nd argument)
msg205650 - (view) Author: Vajrasky Kok (vajrasky) * Date: 2013-12-09 06:52
Here is the preliminary patch.

After patch:

round(1.23, 0) => 1 not 1.0

round(4.67, 0) => 5 not 5.0
msg205651 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2013-12-09 08:30
> After patch:
> round(1.23, 0) => 1 not 1.0
> round(4.67, 0) => 5 not 5.0

Please no!  Two-argument round should continue to return a float in all cases.

The docs should be fixed.
msg205652 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2013-12-09 08:32
> But also there should be a way to make round return an integer

I don't understand.  There's already a way to make round return an integer: don't pass a second argument.
msg205653 - (view) Author: Vajrasky Kok (vajrasky) * Date: 2013-12-09 08:56
Okay, here is the patch to fix the doc.
msg205682 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2013-12-09 12:04
Thanks.  It's inaccurate to say that a float is returned in general, though:  for most builtin numeric types, what's returned has the same type as its input.  So rounding a Decimal to two places gives a Decimal on output, etc.  (That's already explained in the next paragraph.)
msg205683 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2013-12-09 12:06
How about just removing the mention of 'defaults to zero', and say something like: "if ndigits is omitted, returns the nearest int to its input"
msg205695 - (view) Author: João Bernardo (JBernardo) Date: 2013-12-09 14:14
> I don't understand.  There's already a way to make round return an integer: don't pass a second argument.

If this function were to be written in Python, it would be something like:

    def round(number, ndigits=0):
        ...

or 

    def round(number, ndigits=None):
        ...


But in C you can forge the signature to whatever you want and parse the arguments accordingly. In Python there's always a way to get the default behavior by passing the default argument, but in C it may not exist (in this case `PyObject *o_ndigits = NULL;`)

So, I propose the default value being `None`, so this behavior can be achieved using a second argument.
msg205699 - (view) Author: Vajrasky Kok (vajrasky) * Date: 2013-12-09 14:55
Here is the updated doc fix.

Anyway, why not round(1.2) -> 1.0 in the first place? Just curious.
msg205703 - (view) Author: João Bernardo (JBernardo) Date: 2013-12-09 15:25
> Anyway, why not round(1.2) -> 1.0 in the first place? Just curious.

It was the behavior on Python 2.x, but somehow when they changed the rounding method to nearest even number this happened... I think it's too late to change back the return type.
msg205704 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2013-12-09 15:31
Do you have any real-world motivating use case for None?  Not just theoretical consistency with what a Python version of the function would look like.  (I'm not saying we shouldn't consider supporting None as a low priority change, I'm just trying to figure out where you'd ever need it in the real world.)
msg205706 - (view) Author: João Bernardo (JBernardo) Date: 2013-12-09 15:44
Not really. Just consistency:

For the same reason  

    ' foo '.strip(None)

works... To avoid special casing the function call when you already have a variable to hold the argument.
msg205708 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2013-12-09 16:06
Right, but None in that case has real world utility, since you might have the the value in a variable.  But you are hardly going to hold int-or-not in a variable, especially a variable that is really about the number of places in the float result...
msg205729 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2013-12-09 19:07
> Anyway, why not round(1.2) -> 1.0 in the first place? Just curious.

All this changed as part of PEP 3141.  I wasn't watching Python 3 development closely back then, but I *think* at least part of the motivation was to provide a way to get away from the use of `int` to truncate a float to its integer part:  the argument goes that a simple type conversion shouldn't throw away information, and that if you want a transformation from float to int that throws away information you should ask for it explicitly.  So `math.trunc` was born as the preferred way to truncate a float to an int, and `math.floor`, `math.ceil` and `round` became alternative float -> int conversion methods.  That entailed those functions returning ints.

<off-topic> In the case of `math.floor` and `math.ceil` at least, I think this is regrettable.  There are plenty of places where you just want a float -> float floor or ceiling, and Python no longer has a cheap operation for that available:  floor as a float-to-float operation is cheap;  floor as a float-to-long-integer operation is significantly more costly.

In the case of `round`, we still have `round(x, 0)` available as a cheap float->float conversion, so it's less of a problem.  And I hardly ever use `trunc`, so I don't care about that case. </off-topic>
msg205770 - (view) Author: Vajrasky Kok (vajrasky) * Date: 2013-12-10 04:42
In case we want to add consistency with None ndigits, here is the patch adding support for None value for ndigits parameter.

This one looks like a low-risk addition but since Python 3.4 is in beta phase....
msg206162 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2013-12-14 02:18
The docstring is better than the current doc as it says that the default precision is 0, without calling that the default for ndigits.

''' round(number[, ndigits]) -> number
    
    Round a number to a given precision in decimal digits (default 0 digits).
    This returns an int when called with one argument, otherwise the
    same type as the number. ndigits may be negative.'''

---
Sidenote: To write round in Python, one could easily write

_sentinel = object
def round(number, ndigits=_sentinel):
  if ndigits is _sentinel: ...

which makes ndigits positional-or-keyword, and almost optional-with-no-default, as _sentinel is close enough to being a default that cannot be passed in. This is a standard idiom. One who was really picky about having no default could use
  def round(number, *args, **kwds): ...
and look for len(args) == 1 xor kwds.keys() == {'ndigits'}.
msg241152 - (view) Author: Roundup Robot (python-dev) Date: 2015-04-15 20:16
New changeset e3cc75b1000b by Steve Dower in branch 'default':
Issue 19933: Provide default argument for ndigits in round. Patch by Vajrasky Kok.
https://hg.python.org/cpython/rev/e3cc75b1000b
History
Date User Action Args
2015-04-15 20:16:44steve.dowersetstatus: open -> closed
resolution: fixed
versions: + Python 3.5, - Python 3.4
2015-04-15 20:16:20python-devsetnosy: + python-dev
messages: + msg241152
2013-12-14 02:18:07terry.reedysetnosy: + terry.reedy
messages: + msg206162
2013-12-10 04:42:34vajraskysetfiles: + fix_doc_ndigits_round_and_add_None_ndigits.patch

messages: + msg205770
2013-12-09 19:07:00mark.dickinsonsetmessages: + msg205729
2013-12-09 16:06:47r.david.murraysetmessages: + msg205708
2013-12-09 15:44:54JBernardosetmessages: + msg205706
2013-12-09 15:31:59r.david.murraysetnosy: + r.david.murray
messages: + msg205704
2013-12-09 15:25:04JBernardosetmessages: + msg205703
2013-12-09 14:55:18vajraskysetfiles: + fix_doc_round_ndigits_v2.patch

messages: + msg205699
2013-12-09 14:14:09JBernardosetmessages: + msg205695
2013-12-09 12:06:37mark.dickinsonsetmessages: + msg205683
2013-12-09 12:04:29mark.dickinsonsetmessages: + msg205682
2013-12-09 08:58:06vajraskysetfiles: + fix_doc_round_ndigits.patch
2013-12-09 08:57:58vajraskysetfiles: - fix_doc_round_ndigits.patch
2013-12-09 08:57:22vajraskysetfiles: + fix_doc_round_ndigits.patch
2013-12-09 08:57:09vajraskysetfiles: - fix_doc_round_ndigits.patch
2013-12-09 08:56:39vajraskysetfiles: + fix_doc_round_ndigits.patch

messages: + msg205653
2013-12-09 08:32:07mark.dickinsonsetmessages: + msg205652
2013-12-09 08:30:47mark.dickinsonsetmessages: + msg205651
2013-12-09 06:53:13vajraskysetnosy: + mark.dickinson
2013-12-09 06:52:30vajraskysetfiles: + fix_round_with_zero_ndigits.patch

nosy: + vajrasky
messages: + msg205650

keywords: + patch
2013-12-09 04:24:20JBernardocreate