Fix some special cases in Fractions?

Would there be any problem with changing: In [4]: Fraction(1, 1) ** Fraction(2, 3) Out[4]: 1.0 In [5]: Fraction(-1, 1) ** Fraction(2, 3) Out[5]: (-0.4999999999999998+0.8660254037844387j) In [6]: Fraction(0, 1) ** Fraction(2, 3) Out[6]: 0.0 I'd like these to be Fraction(1), Fraction(1), and Fraction(0).

Hi Neil You wrote:
Would there be any problem with changing:
In [4]: Fraction(1, 1) ** Fraction(2, 3) Out[4]: 1.0
In [5]: Fraction(-1, 1) ** Fraction(2, 3) Out[5]: (-0.4999999999999998+0.8660254037844387j)
In [6]: Fraction(0, 1) ** Fraction(2, 3) Out[6]: 0.0
I'd like these to be Fraction(1), Fraction(1), and Fraction(0).
I think this may be a hard problem, that looks easy. I'm used to using a number theory computer algebra system https://pari.math.u-bordeaux.fr/. Here's what it does with your examples. (First I show that it's default number type is whole numbers and fractions.) $ gp GP/PARI CALCULATOR Version 2.5.5 (released) ? 1/2 + 1/2 %1 = 1 ? 2^3 %2 = 8 ? 2^(6/2) %3 = 8 ? 4^(1/2) %4 = 2.0000000000000000000000000000000000000 ? 1^(2/3) %5 = 1.0000000000000000000000000000000000000 ? (-1)^(2/3) %6 = -0.50000000000000000000000000000000000000 + 0.86602540378443864676372317075293618347*I ? (0/1)^(2/3) %7 = 0 This gives the same results as Python's Fraction, except for your example [6]. There, it gives the Fraction(0) you ask for. If the smart mathematicians and computer scientists that wrote gp/pari get the same answers, it suggests to me that improvement would be hard. That said, a case can be made for the value given by [6] being a bug. Or a poorly documented feature. -- best regards Jonathan

On Wed, Aug 29, 2018 at 09:39:05PM -0700, Neil Girdhar wrote:
If anyone else has mentioned the backward-compatibility issue by now, I haven't see it. I believe that would make it a fairly big problem. I expect that there is code out in the wild which (for good or ill) now expects 1**Fraction(2, 3) to return 1.0, rather than Fraction(1), and similarly for the other examples. Changing that could break people's code. Even if we had consensus that this was a good idea, or at least consensus from the maths-folk who care about this sort of thing (I'd like to know what Uncle Timmy and Mark think of this idea), it would still probably need a "__future__" import to activate it, or a deprecation period. Or some other annoyance. Possibly the simpler approach would be to add a subclass that does what you want. UnitRootFraction or something. Then the only argument will be whether such a subclass ought to go into the fractions module, or your own personal toolkit :-) I must admit though, I'm a bit curious as to what you are doing that having 1**Fraction(2,3) return 1.0 is an annoyance, but having 27**Fraction(2,3) return 8.999999999999998 instead of Fraction(9) isn't. -- Steve

I would also like to point out that the current behavior of Fraction is consistent with other parts of the numeric system, e.g. 1/1 produces 1.0 (rather than 1) math.sqrt(4) produces 2.0 (rather than 2) 1j-1j produces 0j (rather than 0.0 or 0) So in general the type of the output is determined by what the operation would in general return for that input type, as opposed to a more specific type which only applies to the specific input value. Stephan Op do 30 aug. 2018 om 15:03 schreef Steven D'Aprano <steve@pearwood.info>:

On Thu, 30 Aug 2018 at 17:36, Stephan Houben <stephanh42@gmail.com> wrote:
An exception is integer exponentiation:
Given two rationals q1 and q2 usually q1 ** q2 will not be a rational number. Integer exponentiation results in an integer for half of all possible integer pairs. To do the same with Fraction(a, b) ** Fraction(c, d) would require verifying that both a and b have exact integer dth roots which is more complicated than simply checking the sign of an integer exponent. The extra complexity would slow things down a bit but then again the fractions module is there for precisely those people who are happy to have substantial slow-down for the sake of exactness.
Also it would also be straight-forward to implement this given the integer maths (iroot etc) functions that were discussed in a recent thread on this list: https://mail.python.org/pipermail/python-ideas/2018-July/051917.html However: Why would you do this operation if you wanted an exact result? I have at some point wanted (for ints or Fractions) a function root(q, n) that gives an exact root or an error. This proposal would mean that q1 ** q2 would be exact occasionally and would return a float the rest of the time though. If you care about exactness/accuracy in the code you write then it is not okay to be unsure whether your variable is float or Fraction. You need to know because it makes a big difference to how you calculate things (it isn't generally possible to write optimal polymorphic code over exact/inexact arithmetic). The proposed behaviour would look nicer in an interactive session but might not be any more useful in situations where you *really* care about exactness. -- Oscar

Hi Neil You wrote:
Would there be any problem with changing:
In [4]: Fraction(1, 1) ** Fraction(2, 3) Out[4]: 1.0
In [5]: Fraction(-1, 1) ** Fraction(2, 3) Out[5]: (-0.4999999999999998+0.8660254037844387j)
In [6]: Fraction(0, 1) ** Fraction(2, 3) Out[6]: 0.0
I'd like these to be Fraction(1), Fraction(1), and Fraction(0).
I think this may be a hard problem, that looks easy. I'm used to using a number theory computer algebra system https://pari.math.u-bordeaux.fr/. Here's what it does with your examples. (First I show that it's default number type is whole numbers and fractions.) $ gp GP/PARI CALCULATOR Version 2.5.5 (released) ? 1/2 + 1/2 %1 = 1 ? 2^3 %2 = 8 ? 2^(6/2) %3 = 8 ? 4^(1/2) %4 = 2.0000000000000000000000000000000000000 ? 1^(2/3) %5 = 1.0000000000000000000000000000000000000 ? (-1)^(2/3) %6 = -0.50000000000000000000000000000000000000 + 0.86602540378443864676372317075293618347*I ? (0/1)^(2/3) %7 = 0 This gives the same results as Python's Fraction, except for your example [6]. There, it gives the Fraction(0) you ask for. If the smart mathematicians and computer scientists that wrote gp/pari get the same answers, it suggests to me that improvement would be hard. That said, a case can be made for the value given by [6] being a bug. Or a poorly documented feature. -- best regards Jonathan

On Wed, Aug 29, 2018 at 09:39:05PM -0700, Neil Girdhar wrote:
If anyone else has mentioned the backward-compatibility issue by now, I haven't see it. I believe that would make it a fairly big problem. I expect that there is code out in the wild which (for good or ill) now expects 1**Fraction(2, 3) to return 1.0, rather than Fraction(1), and similarly for the other examples. Changing that could break people's code. Even if we had consensus that this was a good idea, or at least consensus from the maths-folk who care about this sort of thing (I'd like to know what Uncle Timmy and Mark think of this idea), it would still probably need a "__future__" import to activate it, or a deprecation period. Or some other annoyance. Possibly the simpler approach would be to add a subclass that does what you want. UnitRootFraction or something. Then the only argument will be whether such a subclass ought to go into the fractions module, or your own personal toolkit :-) I must admit though, I'm a bit curious as to what you are doing that having 1**Fraction(2,3) return 1.0 is an annoyance, but having 27**Fraction(2,3) return 8.999999999999998 instead of Fraction(9) isn't. -- Steve

I would also like to point out that the current behavior of Fraction is consistent with other parts of the numeric system, e.g. 1/1 produces 1.0 (rather than 1) math.sqrt(4) produces 2.0 (rather than 2) 1j-1j produces 0j (rather than 0.0 or 0) So in general the type of the output is determined by what the operation would in general return for that input type, as opposed to a more specific type which only applies to the specific input value. Stephan Op do 30 aug. 2018 om 15:03 schreef Steven D'Aprano <steve@pearwood.info>:

On Thu, 30 Aug 2018 at 17:36, Stephan Houben <stephanh42@gmail.com> wrote:
An exception is integer exponentiation:
Given two rationals q1 and q2 usually q1 ** q2 will not be a rational number. Integer exponentiation results in an integer for half of all possible integer pairs. To do the same with Fraction(a, b) ** Fraction(c, d) would require verifying that both a and b have exact integer dth roots which is more complicated than simply checking the sign of an integer exponent. The extra complexity would slow things down a bit but then again the fractions module is there for precisely those people who are happy to have substantial slow-down for the sake of exactness.
Also it would also be straight-forward to implement this given the integer maths (iroot etc) functions that were discussed in a recent thread on this list: https://mail.python.org/pipermail/python-ideas/2018-July/051917.html However: Why would you do this operation if you wanted an exact result? I have at some point wanted (for ints or Fractions) a function root(q, n) that gives an exact root or an error. This proposal would mean that q1 ** q2 would be exact occasionally and would return a float the rest of the time though. If you care about exactness/accuracy in the code you write then it is not okay to be unsure whether your variable is float or Fraction. You need to know because it makes a big difference to how you calculate things (it isn't generally possible to write optimal polymorphic code over exact/inexact arithmetic). The proposed behaviour would look nicer in an interactive session but might not be any more useful in situations where you *really* care about exactness. -- Oscar
participants (5)
-
Jonathan Fine
-
Neil Girdhar
-
Oscar Benjamin
-
Stephan Houben
-
Steven D'Aprano