Open fchapoton opened 5 years ago
Branch: u/chapoton/25827
Not sure what to do. This is not curing the disease. It seems that python3 builtin "round" is looking for __round__
, unlike python2.
failing doctests
I've implemented a few __round__
in my python3 branch. I'll see about making a ticket for those.
@embray, where is your latest python3 branch ? could you please provide a branch for here, with the implemented __round__
?
Ok, I'll try to get back to you about that soon.
I think we need to think about exactly how to implement Sage's round()
built-in as well. Currently, some types in Sage have a .round()
method which takes no arguments, and only rounds to an integer--in particular it always rounds up, it seems.
I find this a bit odd, but maybe there's a good reason. I wonder, because I find it surprising that RealNumber.round()
does not take into account the MPFR rounding mode of the parent field. Why is that? Is it just for consistency's sake--that all real and floating-point numbers round up the same way?
I implemented a RealNumber.__round__()
which takes into account the parent field's rounding mode, and works quite nicely, though it's incompatible with Sage's round()
built-in which just calls RealNumber.round()
for integer results. So when rounding to the nearest int you get one rounding behavior, but in other cases you get a different rounding behavior.
So two questions:
Is there any reason to have a RealNumber.__round__()
that respects the parent field's rounding mode? It seems to make sense, but maybe it contradicts other assumptions in Sage that I'm not aware of.
Should we add an argument to .round()
methods and make them equivalent to .__round__()
? Or do we add a separate .__round__()
, and make .round()
just a special case equivalent to calling __round__()
with no arguments?
So far in sage, almost no round method takes a second argument:
git grep " round(self" src/sage
src/sage/matrix/matrix_double_dense.pyx: def round(self, ndigits=0):
src/sage/rings/complex_arb.pyx: def round(self):
src/sage/rings/number_field/number_field_element.pyx: def round(self):
src/sage/rings/number_field/number_field_element_quadratic.pyx: def round(self):
src/sage/rings/qqbar.py: def round(self):
src/sage/rings/real_arb.pyx: def round(self):
src/sage/rings/real_double.pyx: def round(self):
src/sage/rings/real_mpfi.pyx: def round(self):
src/sage/rings/real_mpfr.pyx: def round(self):
src/sage/symbolic/expression.pyx: def round(self):
Branch pushed to git repo; I updated commit sha1. New commits:
27cfe85 | py3: introducing `__round__` methods as alias for existing .round |
ok, here is a new branch, just adding some aliases __round__
redirecting to round
. I propose to simply do that for the moment, in order to advance the move towards python3.
EDIT: still missing would be __round__
for Integer class
I would really rather think about this more carefully, and perhaps rework how Sage's built-in round()
function works (or maybe even get rid of it entirely, at least on Python 3, if the new __round__
special makes it obsolete. Not clear. And thoughts on my last comment?
So far in sage, almost no round method takes a second argument:
That's because it's really meant to mean round-to-the-nearest-integer, which is simpler than what Python's built-in round does, which can return a float truncated to N digits, or an integer. Just making __round__
point to some of these class's old round
methods is broken.
There's also now __trunc__
, __floor__
, and __ceil__
and I'm not sure if we want to implement them or not: https://docs.python.org/3.6/reference/datamodel.html#object.round but perhaps that can be left as a separate issue.
There's also now
__trunc__
,__floor__
, and__ceil__
and I'm not sure if we want to implement them or not: https://docs.python.org/3.6/reference/datamodel.html#object.round but perhaps that can be left as a separate issue.
Those do not appear in the python3 log.
FWIW here's my implementation for MPFR reals:
diff --git a/src/sage/rings/real_mpfr.pyx b/src/sage/rings/real_mpfr.pyx
index 9b90c88..a82c4eb 100644
--- a/src/sage/rings/real_mpfr.pyx
+++ b/src/sage/rings/real_mpfr.pyx
@@ -3040,6 +3040,38 @@ cdef class RealNumber(sage.structure.element.RingElement)
"""
return mpfr_get_d(self.value, (<RealField_class>self._parent).rnd)
+ def __round__(self, ndigits=0):
+ """
+ Implement support for Python 3's `round` builtin.
+
+ This is mostly equivalent to simply calling ``round(float(x),
+ ndigits)`` where ``x`` is a `RealNumber`. The difference is that it is
+ still returns an arbitrary-precision `RealNumber` of precision
+ equivalent to ``self`` (or an `Integer` if ``ndigits=0``). It also
+ uses the rounding mode specified on the parent field.
+
+ EXAMPLES::
+
+ TODO
+ """
+ cdef Integer z
+ cdef RealNumber r
+ cdef char* s
+
+ if ndigits < 0:
+ return (<RealField_class>self._parent).zero()
+ elif ndigits == 0:
+ z = PY_NEW(Integer)
+ mpfr_get_z(z.value, self.value, (<RealField_class>self._parent).rnd
+ return z
+ else:
+ rnd = (<RealField_class>self._parent).rnd
+ mpfr_asprintf(&s, "%.*R*f", <int>ndigits, rnd, self.value)
+ r = self._new()
+ mpfr_set_str(r.value, s, 10, rnd)
+ mpfr_free_str(s)
+ return r
+
But I'm not 100% sure if it makes sense to do things this way or not (and it needs examples). However, it's broken with Sage's round()
global built-in, because that tries to call a class's .round()
method first if it exists. And in fact RealNumber
does have an existing .round()
method that's different and doesn't take into account the field's rounding mode as my above implementation does.
If anything .round()
should be the same as .__round__(n=0)
; that or .round()
grows an optional extra argument.
Perhaps it is a question that needs to be brought up on sage-devel, if we don't know the answers.
For example, why does Sage have its own round()
built-in in the first place (at least part of the answer to that question seems to be exactly that something like __round__
did not exist in the first place)? Do we still need it, or at least, do we need it with Python 3? Do we like the semantics of Python's round or would we rather replace it with something else? To what extent do we want to support those semantics of different class's .round()
methods? Etc...
I have made #26412 for the same thing in integer.pyx
Changed branch from u/chapoton/25827 to public/ticket/25827
new tentative based on Erik's suggested patch
New commits:
8c5187c | py3: adding `__round__` to real_mpfr.pyx |
bot is morally green..
I'm happy with the __round__
implementation of course, since I wrote it. But part of why I never submitted it in the first place is that I'm not totally confident with this course of action.
In particular, why does RealNumber.round
ignore the rounding mode of the parent field? Is there some good reason for that? If so, then why would RealNumber.__round__
not ignore it?
There's also a problem that this __round__
is ignored anyways (as you can see by writing the tests to use the round()
global instead of calling __round__()
directly). The problem is that Sage's round()
just ends up calling RealNumber.round()
instead of RealNumber.__round__()
and thus ignores the rounding mode, so this code effectively never gets used.
This is why I keep saying we maybe need to rethink the behavior of Sage's built-in round()
. I'm starting to think that for Python 3 we might opt to just remove it entirely, or make it an alias for the Python built-in round()
, since now we can override its behavior on different types by supplying __round__()
, whereas previously we had to use this hack to override rounding on different types.
I also think that the test for this should demonstrate the fact that parent's rounding mode is considered. For example, demonstrate that with RNDN
2.5 is rounded to 2, while with RNDU
it is rounded to 3.
Would it be ok to make the current round
method an alias for the new __round__
method (just in this file for this kind of real numbers) ?
Try it and see. I believe it would work--look at the definition of round
in sage.misc.functional
.
It still doesn't answer my question though:
In particular, why does
RealNumber.round
ignore the rounding mode of the parent field? Is there some good reason for that? If so, then why wouldRealNumber.__round__
not ignore it?
I think that what you propose makes sense, but it would be a change in behavior (see the round(2.5)
case) which I wouldn't want to make lightly unless nobody can provide a good explanation for the current behavior. This is what I keep trying to tell you. If neither of us know a reason for the current behavior then we need to find out who does know and ask them.
Replying to @embray:
In particular, why does
RealNumber.round
ignore the rounding mode of the parent field? Is there some good reason for that? If so, then why wouldRealNumber.__round__
not ignore it?
My understanding is that round()
, floor()
, ceil()
, and trunc()
are for rounding reals to integers, each in the way (≈ with the rounding mode) indicated by the name of the method. The parent's rounding mode only tells you how to round the results of inexact operations in that parent, and has no role to play here. That rounding to a certain precision can also be done with a function called round()
is an accident, the two are unrelated.
Replying to @mezzarobba:
Replying to @embray:
In particular, why does
RealNumber.round
ignore the rounding mode of the parent field? Is there some good reason for that? If so, then why wouldRealNumber.__round__
not ignore it?My understanding is that
round()
,floor()
,ceil()
, andtrunc()
are for rounding reals to integers, each in the way (≈ with the rounding mode) indicated by the name of the method. The parent's rounding mode only tells you how to round the results of inexact operations in that parent, and has no role to play here. That rounding to a certain precision can also be done with a function calledround()
is an accident, the two are unrelated.
Could you please be more specific? Are you talking about the Python stdlib or Sage? The fact is that the round()
function in Python (and by extension in Sage) does allow rounding to N decimal digits.
I appreciate the effort to bring clarity to this question but to me this comment only muddies the waters.
Replying to @embray:
Could you please be more specific? Are you talking about the Python stdlib or Sage?
Both, I guess. What I'm trying to say (and I think it answers your question, but I haven't read the rest of the ticket, so perhaps I'm misunderstanding the issue) is that:
round()
functions (both the builtin and the one in sage.functional
) round to a given precision. From my point of view, making them honor the parent's rounding mode when their argument already is a RealNumber
would be an improvement.round()
methods of RealNumber
and several other element classes, however, are unrelated. They are for rounding to the nearest integer; and rounding up or down or whatever else is done with other methods.Okay, thank you. That's a bit clearer. Though I don't quite understand "rounding up or down or whatever else is done with other methods". Given a RealNumber(2.5)
should it round up or down (typically up I know, but why?)
And should the round()
function ever call an element's .round()
method?
Replying to @embray:
Okay, thank you. That's a bit clearer. Though I don't quite understand "rounding up or down or whatever else is done with other methods". Given a
RealNumber(2.5)
should it round up or down (typically up I know, but why?)
I missed that part of your question, sorry. Even when the rounding mode is “to nearest”, there are two (three?) competing conventions (up, or perhaps away from zero, in everyday life, vs to even). FWIW:
round()
rounds halfway cases away from zero regardless of the rounding mode.
So I'd say it is best not take the rounding mode into account to decide what to do with ties, and we should round the absolute value up. But I have no strong opinion on the matter, really. That's just my understanding of the consensus.And should the
round()
function ever call an element's.round()
method?
As far as I understand, no.
It would be good if somebody more knowledgeable than me would now take over this ticket and manage to push it into success. This problem with round
is now one of the major source of doctest failures in py3-sage.
I think it would be good to bring it up, yet again, on the mailing list.
Ticket retargeted after milestone closed (if you don't believe this ticket is appropriate for the Sage 8.8 release please retarget manually)
Tickets still needing working or clarification should be moved to the next release milestone at the soonest (please feel free to revert if you think the ticket is close to being resolved).
Ticket retargeted after milestone closed
Moving tickets to milestone sage-9.2 based on a review of last modification date, branch status, and severity.
Sage development has entered the release candidate phase for 9.3. Setting a new milestone for this ticket based on a cursory review of ticket status, priority, and last modification date.
Setting a new milestone for this ticket based on a cursory review.
Description changed:
---
+++
@@ -1 +1,7 @@
+https://docs.python.org/3/reference/datamodel.html?highlight=__round__#object.__round__
+We should define these special methods so that the built-in `round` and `math.trunc`, `math.floor`, `math.ceil` can operate on Sage numbers.
+
+This can help to eliminate imports of `floor` and `ceil` from `sage.functions` throughout the Sage library, which pulls in all of `sage.symbolic`.
+
+
Description changed:
---
+++
@@ -4,4 +4,10 @@
This can help to eliminate imports of `floor` and `ceil` from `sage.functions` throughout the Sage library, which pulls in all of `sage.symbolic`.
+Related: In the global namespace, we have:
+- `round` = `sage.misc.round`,
+- `floor` = `sage.functions.other.floor`,
+- `ceil` = `sage.functions.other.floor`,
+` `trunc` - undefined
+
Description changed:
---
+++
@@ -8,6 +8,6 @@
- `round` = `sage.misc.round`,
- `floor` = `sage.functions.other.floor`,
- `ceil` = `sage.functions.other.floor`,
-` `trunc` - undefined
+- `trunc` - undefined
Description changed:
---
+++
@@ -5,7 +5,7 @@
This can help to eliminate imports of `floor` and `ceil` from `sage.functions` throughout the Sage library, which pulls in all of `sage.symbolic`.
Related: In the global namespace, we have:
-- `round` = `sage.misc.round`,
+- `round` = `sage.misc.functional.round`,
- `floor` = `sage.functions.other.floor`,
- `ceil` = `sage.functions.other.floor`,
- `trunc` - undefined
Description changed:
---
+++
@@ -7,7 +7,7 @@
Related: In the global namespace, we have:
- `round` = `sage.misc.functional.round`,
- `floor` = `sage.functions.other.floor`,
-- `ceil` = `sage.functions.other.floor`,
+- `ceil` = `sage.functions.other.ceil`,
- `trunc` - undefined
https://docs.python.org/3/reference/datamodel.html?highlight=__round__#object.__round__
We should define these special methods so that the built-in
round
andmath.trunc
,math.floor
,math.ceil
can operate on Sage numbers.This can help to eliminate imports of
floor
andceil
fromsage.functions
throughout the Sage library, which pulls in all ofsage.symbolic
.Related: In the global namespace, we have:
round
=sage.misc.functional.round
,floor
=sage.functions.other.floor
,ceil
=sage.functions.other.ceil
,trunc
- undefinedCC: @jdemeyer @tscrim
Component: python3
Author: Frédéric Chapoton
Branch/Commit: public/ticket/25827 @
53280c5
Issue created by migration from https://trac.sagemath.org/ticket/25827