Vincent Delecroix wrote:
> That does not prevent us to normalize for let say: __repr__() ,
> numerator(), denominator(). Is there any reasonable rule to choose
> when to and when not to normalize? For QQ itself, GMP does normalize
> all the time.
Just to be certain that we are talking about the same thing: Currently,
elements of fraction fields are always reduced (except over inexact
rings) in the sense of dividing out by the gcd of the numerator and the
denominator. But no further normalization is done, and in particular,
when the numerator and denominator are polynomials, their contents or
leading coefficients are not normalized.
There is a ticket needing review that proposes to normalize the leading
coefficient of the denominator to one:
https://trac.sagemath.org/ticket/16268
Actually, I first tried implementing a version that would systematically
clear denominators inside the numerator and denominator of the fraction,
but I didn't manage to make it reasonably fast, even with a dedicated
class for polynomials over fraction fields (with a single denominator,
similar to the representation of polynomials over ℚ). While I'm not
saying it can't be done, in the short term, the version at #16268 is a
lot simpler and already fixes a number of issues!
Regarding your question: Having a variant of numerator()/denominator()
that works recursively would definitely be useful, and yes, it may be
nice to use it in __repr__(). At the same time, I think we do want to
keep programmatic access to the “true” numerator and denominator as
defined in the data structure. In case you want to experiment with the
idea, here is a (perhaps a bit naive, improvements welcome!)
implementation of something similar that I needed for something I'm
working on. It is intended to be used with #16268 and #23909.
def _my_lcm(elts): # non monic
l = ZZ.one()
for p in elts:
l *= (p//p.gcd(l))
return l
def _clear_denominators_1(elt, dom):
num, den = elt.numerator(), elt.denominator()
base = dom.base_ring()
if isinstance(base, FractionField_generic):
numnum, numden = clear_denominators(num, base)
dennum, denden = clear_denominators(den, base)
newdom = dom.change_ring(base.ring())
numnum = newdom(numnum)
dennum = newdom(dennum)
gnum = numnum.gcd(dennum)
numnum, dennum = numnum//gnum, dennum//gnum
gden = numden.gcd(denden)
numden, denden = numden//gden, denden//gden
return (numnum, numden, dennum, denden)
else:
return (num, dom.one(), den, dom.one())
def clear_denominators(elts, dom=None):
r"""
Recursively clear denominators in a list (or other iterable) of
elements.
Typically intended for elements of fields like QQ(x)(y).
"""
if not elts:
return elts, dom.one()
if dom is None:
dom = elts[0].parent()
if isinstance(dom, FractionField_generic):
ring = dom.ring()
split = [_clear_denominators_1(elt, ring) for elt in elts]
lcmnum = _my_lcm((dennum for _, _, dennum, _ in split))
lcmden = _my_lcm((numden for _, numden, _, _ in split))
num = [(denden*(lcmden//numden))*(numnum*(lcmnum//dennum))
for (numnum, numden, dennum, denden) in split]
den = lcmden*lcmnum
g = gcd(num + [den]) # XXX: can we be more specific here?
num = [p//g for p in num]
den = den//g
else:
num, den = elts, dom.one()
# assert all(b/den == a for a, b in zip(elts, num))
# assert gcd(num + [den]).is_one()
return num, den
--
Marc