Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #51449 > unrolled thread

Unexpected results comparing float to Fraction

Started bySteven D'Aprano <steve+comp.lang.python@pearwood.info>
First post2013-07-29 15:43 +0000
Last post2013-08-01 10:48 +0100
Articles 20 on this page of 27 — 10 participants

Back to article view | Back to comp.lang.python


Contents

  Unexpected results comparing float to Fraction Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-07-29 15:43 +0000
    Re: Unexpected results comparing float to Fraction Ian Kelly <ian.g.kelly@gmail.com> - 2013-07-29 09:50 -0600
    Re: Unexpected results comparing float to Fraction MRAB <python@mrabarnett.plus.com> - 2013-07-29 17:09 +0100
    Re: Unexpected results comparing float to Fraction Chris Angelico <rosuav@gmail.com> - 2013-07-29 17:20 +0100
      Re: Unexpected results comparing float to Fraction Rotwang <sg552@hotmail.co.uk> - 2013-07-29 19:20 +0100
      Re: Unexpected results comparing float to Fraction Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-07-29 19:32 +0000
    Re: Unexpected results comparing float to Fraction MRAB <python@mrabarnett.plus.com> - 2013-07-29 17:48 +0100
      Re: Unexpected results comparing float to Fraction Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-07-29 17:27 +0000
    Re: Unexpected results comparing float to Fraction Ian Kelly <ian.g.kelly@gmail.com> - 2013-07-29 10:40 -0600
      Re: Unexpected results comparing float to Fraction Rotwang <sg552@hotmail.co.uk> - 2013-07-29 19:16 +0100
        Re: Unexpected results comparing float to Fraction Ian Kelly <ian.g.kelly@gmail.com> - 2013-07-29 13:33 -0600
    Re: Unexpected results comparing float to Fraction MRAB <python@mrabarnett.plus.com> - 2013-07-29 18:04 +0100
      Re: Unexpected results comparing float to Fraction Grant Edwards <invalid@invalid.invalid> - 2013-07-29 18:46 +0000
      Re: Unexpected results comparing float to Fraction Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-07-29 20:19 +0000
    Re: Unexpected results comparing float to Fraction Terry Reedy <tjreedy@udel.edu> - 2013-07-29 13:08 -0400
      Re: Unexpected results comparing float to Fraction Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-07-29 17:29 +0000
        Re: Unexpected results comparing float to Fraction Terry Reedy <tjreedy@udel.edu> - 2013-07-29 16:48 -0400
    Re: Unexpected results comparing float to Fraction Chris Angelico <rosuav@gmail.com> - 2013-07-29 18:14 +0100
    Re: Unexpected results comparing float to Fraction Ian Kelly <ian.g.kelly@gmail.com> - 2013-07-29 11:35 -0600
    Re: Unexpected results comparing float to Fraction Serhiy Storchaka <storchaka@gmail.com> - 2013-07-29 22:34 +0300
    Re: Unexpected results comparing float to Fraction Ian Kelly <ian.g.kelly@gmail.com> - 2013-07-29 14:35 -0600
    Unexpected results comparing float to Fraction Oscar Benjamin <oscar.j.benjamin@gmail.com> - 2013-07-30 14:32 +0100
    Re: Unexpected results comparing float to Fraction Tony the Tiger <tony@tiger.invalid> - 2013-07-31 15:23 -0500
      Re: Unexpected results comparing float to Fraction Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-08-01 06:20 +0000
        Re: Unexpected results comparing float to Fraction Chris Angelico <rosuav@gmail.com> - 2013-08-01 07:32 +0100
        Re: Unexpected results comparing float to Fraction Oscar Benjamin <oscar.j.benjamin@gmail.com> - 2013-08-01 10:44 +0100
        Re: Unexpected results comparing float to Fraction Chris Angelico <rosuav@gmail.com> - 2013-08-01 10:48 +0100

Page 1 of 2  [1] 2  Next page →


#51449 — Unexpected results comparing float to Fraction

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2013-07-29 15:43 +0000
SubjectUnexpected results comparing float to Fraction
Message-ID<51f68d9c$0$30000$c3e8da3$5496439d@news.astraweb.com>
Comparing floats to Fractions gives unexpected results:

# Python 3.3
py> from fractions import Fraction
py> 1/3 == Fraction(1, 3)
False

but:

py> 1/3 == float(Fraction(1, 3))
True


I expected that float-to-Fraction comparisons would convert the Fraction 
to a float, but apparently they do the opposite: they convert the float 
to a Fraction:

py> Fraction(1/3)
Fraction(6004799503160661, 18014398509481984)


Am I the only one who is surprised by this? Is there a general rule for 
which way numeric coercions should go when doing such comparisons?


-- 
Steven

[toc] | [next] | [standalone]


#51450

FromIan Kelly <ian.g.kelly@gmail.com>
Date2013-07-29 09:50 -0600
Message-ID<mailman.5237.1375113077.3114.python-list@python.org>
In reply to#51449
On Mon, Jul 29, 2013 at 9:43 AM, Steven D'Aprano
<steve+comp.lang.python@pearwood.info> wrote:
> Comparing floats to Fractions gives unexpected results:
>
> # Python 3.3
> py> from fractions import Fraction
> py> 1/3 == Fraction(1, 3)
> False
>
> but:
>
> py> 1/3 == float(Fraction(1, 3))
> True
>
>
> I expected that float-to-Fraction comparisons would convert the Fraction
> to a float, but apparently they do the opposite: they convert the float
> to a Fraction:
>
> py> Fraction(1/3)
> Fraction(6004799503160661, 18014398509481984)
>
>
> Am I the only one who is surprised by this? Is there a general rule for
> which way numeric coercions should go when doing such comparisons?

Any float can be precisely represented as a Fraction.  Not so in the
other direction.  So from that standpoint it makes sense to me to cast
to Fraction when comparing.

[toc] | [prev] | [next] | [standalone]


#51452

FromMRAB <python@mrabarnett.plus.com>
Date2013-07-29 17:09 +0100
Message-ID<mailman.5239.1375114343.3114.python-list@python.org>
In reply to#51449
On 29/07/2013 16:43, Steven D'Aprano wrote:
> Comparing floats to Fractions gives unexpected results:
>
> # Python 3.3
> py> from fractions import Fraction
> py> 1/3 == Fraction(1, 3)
> False
>
> but:
>
> py> 1/3 == float(Fraction(1, 3))
> True
>
>
> I expected that float-to-Fraction comparisons would convert the Fraction
> to a float, but apparently they do the opposite: they convert the float
> to a Fraction:
>
> py> Fraction(1/3)
> Fraction(6004799503160661, 18014398509481984)
>
>
> Am I the only one who is surprised by this? Is there a general rule for
> which way numeric coercions should go when doing such comparisons?
>
I'm surprised that Fraction(1/3) != Fraction(1, 3); after all, floats
are approximate anyway, and the float value 1/3 is more likely to be
Fraction(1, 3) than Fraction(6004799503160661, 18014398509481984).

[toc] | [prev] | [next] | [standalone]


#51455

FromChris Angelico <rosuav@gmail.com>
Date2013-07-29 17:20 +0100
Message-ID<mailman.5240.1375115193.3114.python-list@python.org>
In reply to#51449
On Mon, Jul 29, 2013 at 5:09 PM, MRAB <python@mrabarnett.plus.com> wrote:
> I'm surprised that Fraction(1/3) != Fraction(1, 3); after all, floats
> are approximate anyway, and the float value 1/3 is more likely to be
> Fraction(1, 3) than Fraction(6004799503160661, 18014398509481984).

At what point should it become Fraction(1, 3)?

>>> Fraction(0.3)
Fraction(5404319552844595, 18014398509481984)
>>> Fraction(0.33)
Fraction(5944751508129055, 18014398509481984)
>>> Fraction(0.333)
Fraction(5998794703657501, 18014398509481984)
>>> Fraction(0.3333333)
Fraction(6004798902680711, 18014398509481984)
>>> Fraction(0.3333333333)
Fraction(6004799502560181, 18014398509481984)
>>> Fraction(0.3333333333333)
Fraction(6004799503160061, 18014398509481984)
>>> Fraction(0.33333333333333333)
Fraction(6004799503160661, 18014398509481984)

Rounding off like that is a job for a cool library function (one of
which was mentioned on this list a little while ago, I believe), but
not IMO for the Fraction constructor.

ChrisA

[toc] | [prev] | [next] | [standalone]


#51471

FromRotwang <sg552@hotmail.co.uk>
Date2013-07-29 19:20 +0100
Message-ID<kt6bdd$u6d$1@dont-email.me>
In reply to#51455
On 29/07/2013 17:20, Chris Angelico wrote:
> On Mon, Jul 29, 2013 at 5:09 PM, MRAB <python@mrabarnett.plus.com> wrote:
>> I'm surprised that Fraction(1/3) != Fraction(1, 3); after all, floats
>> are approximate anyway, and the float value 1/3 is more likely to be
>> Fraction(1, 3) than Fraction(6004799503160661, 18014398509481984).
>
> At what point should it become Fraction(1, 3)?
>
>>>> Fraction(0.3)
> Fraction(5404319552844595, 18014398509481984)
>>>> Fraction(0.33)
> Fraction(5944751508129055, 18014398509481984)
>>>> Fraction(0.333)
> Fraction(5998794703657501, 18014398509481984)
>>>> Fraction(0.3333333)
> Fraction(6004798902680711, 18014398509481984)
>>>> Fraction(0.3333333333)
> Fraction(6004799502560181, 18014398509481984)
>>>> Fraction(0.3333333333333)
> Fraction(6004799503160061, 18014398509481984)
>>>> Fraction(0.33333333333333333)
> Fraction(6004799503160661, 18014398509481984)
>
> Rounding off like that is a job for a cool library function (one of
> which was mentioned on this list a little while ago, I believe), but
> not IMO for the Fraction constructor.

How about this?

 >>> from fractions import Fraction
 >>> help(Fraction.limit_denominator)
Help on function limit_denominator in module fractions:

limit_denominator(self, max_denominator=1000000)
     Closest Fraction to self with denominator at most max_denominator.

     >>> Fraction('3.141592653589793').limit_denominator(10)
     Fraction(22, 7)
     >>> Fraction('3.141592653589793').limit_denominator(100)
     Fraction(311, 99)
     >>> Fraction(4321, 8765).limit_denominator(10000)
     Fraction(4321, 8765)

 >>> Fraction(1/3).limit_denominator()
Fraction(1, 3)

[toc] | [prev] | [next] | [standalone]


#51477

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2013-07-29 19:32 +0000
Message-ID<51f6c356$0$30000$c3e8da3$5496439d@news.astraweb.com>
In reply to#51455
On Mon, 29 Jul 2013 17:20:27 +0100, Chris Angelico wrote:

>>>> Fraction(0.3)
> Fraction(5404319552844595, 18014398509481984)
>>>> Fraction(0.33)
> Fraction(5944751508129055, 18014398509481984)
[...]
>>>> Fraction(0.33333333333333333)
> Fraction(6004799503160661, 18014398509481984)
> 
> Rounding off like that is a job for a cool library function (one of
> which was mentioned on this list a little while ago, I believe), but not
> IMO for the Fraction constructor.

Or:

py> Fraction(1/3).limit_denominator(100)
Fraction(1, 3)


I think it would be useful for Fraction.from_float() to accept an 
optional second argument, the maximum denominator:

Fraction.from_float(x, den=None)
=> nearest fraction to float 1/3, with denominator no greater than den



-- 
Steven

[toc] | [prev] | [next] | [standalone]


#51460

FromMRAB <python@mrabarnett.plus.com>
Date2013-07-29 17:48 +0100
Message-ID<mailman.5243.1375116500.3114.python-list@python.org>
In reply to#51449
On 29/07/2013 17:20, Chris Angelico wrote:
> On Mon, Jul 29, 2013 at 5:09 PM, MRAB <python@mrabarnett.plus.com> wrote:
>> I'm surprised that Fraction(1/3) != Fraction(1, 3); after all, floats
>> are approximate anyway, and the float value 1/3 is more likely to be
>> Fraction(1, 3) than Fraction(6004799503160661, 18014398509481984).
>
> At what point should it become Fraction(1, 3)?
>
When the error drops below a certain threshold.

>>>> Fraction(0.3)
> Fraction(5404319552844595, 18014398509481984)
>>>> Fraction(0.33)
> Fraction(5944751508129055, 18014398509481984)
>>>> Fraction(0.333)
> Fraction(5998794703657501, 18014398509481984)
>>>> Fraction(0.3333333)
> Fraction(6004798902680711, 18014398509481984)
>>>> Fraction(0.3333333333)
> Fraction(6004799502560181, 18014398509481984)
>>>> Fraction(0.3333333333333)
> Fraction(6004799503160061, 18014398509481984)
>>>> Fraction(0.33333333333333333)
> Fraction(6004799503160661, 18014398509481984)
>
> Rounding off like that is a job for a cool library function (one of
> which was mentioned on this list a little while ago, I believe), but
> not IMO for the Fraction constructor.
>

[toc] | [prev] | [next] | [standalone]


#51467

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2013-07-29 17:27 +0000
Message-ID<51f6a5ed$0$30000$c3e8da3$5496439d@news.astraweb.com>
In reply to#51460
On Mon, 29 Jul 2013 17:48:21 +0100, MRAB wrote:

> On 29/07/2013 17:20, Chris Angelico wrote:
>> On Mon, Jul 29, 2013 at 5:09 PM, MRAB <python@mrabarnett.plus.com>
>> wrote:
>>> I'm surprised that Fraction(1/3) != Fraction(1, 3); after all, floats
>>> are approximate anyway, and the float value 1/3 is more likely to be
>>> Fraction(1, 3) than Fraction(6004799503160661, 18014398509481984).
>>
>> At what point should it become Fraction(1, 3)?
>>
> When the error drops below a certain threshold.

Good plan! I pick a threshold of 42.7. Anyone got a better threshold?

*wink*



-- 
Steven

[toc] | [prev] | [next] | [standalone]


#51461

FromIan Kelly <ian.g.kelly@gmail.com>
Date2013-07-29 10:40 -0600
Message-ID<mailman.5244.1375116584.3114.python-list@python.org>
In reply to#51449
On Mon, Jul 29, 2013 at 10:20 AM, Chris Angelico <rosuav@gmail.com> wrote:
> On Mon, Jul 29, 2013 at 5:09 PM, MRAB <python@mrabarnett.plus.com> wrote:
>> I'm surprised that Fraction(1/3) != Fraction(1, 3); after all, floats
>> are approximate anyway, and the float value 1/3 is more likely to be
>> Fraction(1, 3) than Fraction(6004799503160661, 18014398509481984).
>
> At what point should it become Fraction(1, 3)?

At the point where the float is exactly equal to the value you get
from the floating-point division 1/3.  If it's some other float then
the user didn't get there by entering 1/3, so it's not worth trying to
pretend that they did.

We do a similar rounding when formatting floats to strings, but in
that case one only has to worry about divisors that are powers of 10.
I imagine it's going to take more time to find the correct fraction
when any pair of relatively prime integers can be a candidate
numerator and denominator.  Additionally, the string rounding only
occurs when the float is being formatted for display; we certainly
don't do it as the result of numeric operations where it could result
in loss of precision.

[toc] | [prev] | [next] | [standalone]


#51470

FromRotwang <sg552@hotmail.co.uk>
Date2013-07-29 19:16 +0100
Message-ID<kt6b6a$s9p$1@dont-email.me>
In reply to#51461
On 29/07/2013 17:40, Ian Kelly wrote:
> On Mon, Jul 29, 2013 at 10:20 AM, Chris Angelico <rosuav@gmail.com> wrote:
>> On Mon, Jul 29, 2013 at 5:09 PM, MRAB <python@mrabarnett.plus.com> wrote:
>>> I'm surprised that Fraction(1/3) != Fraction(1, 3); after all, floats
>>> are approximate anyway, and the float value 1/3 is more likely to be
>>> Fraction(1, 3) than Fraction(6004799503160661, 18014398509481984).
>>
>> At what point should it become Fraction(1, 3)?
>
> At the point where the float is exactly equal to the value you get
> from the floating-point division 1/3.

But the interpreter has no way of knowing that the value 1/3 that's been 
passed to the Fraction constructor was obtained from the division 1/3, 
rather than, say, 100000000000000001/300000000000000000 or 
6004799503160661/18014398509481984. How do you propose the constructor 
should decide between the many possible fractions that round to the same 
float, if not by choosing the one that evaluates to it exactly?

Personally the behaviour in the OP is exactly what I would expect.

[toc] | [prev] | [next] | [standalone]


#51479

FromIan Kelly <ian.g.kelly@gmail.com>
Date2013-07-29 13:33 -0600
Message-ID<mailman.5255.1375126463.3114.python-list@python.org>
In reply to#51470
On Mon, Jul 29, 2013 at 12:16 PM, Rotwang <sg552@hotmail.co.uk> wrote:
> On 29/07/2013 17:40, Ian Kelly wrote:
>> At the point where the float is exactly equal to the value you get
>> from the floating-point division 1/3.
>
>
> But the interpreter has no way of knowing that the value 1/3 that's been
> passed to the Fraction constructor was obtained from the division 1/3,
> rather than, say, 100000000000000001/300000000000000000 or
> 6004799503160661/18014398509481984. How do you propose the constructor
> should decide between the many possible fractions that round to the same
> float, if not by choosing the one that evaluates to it exactly?

It should choose the fraction with the least terms that rounds to the
float.  Whether "least" here means least numerator, least denominator,
least sum of the two, or whatever is not terribly important.

[toc] | [prev] | [next] | [standalone]


#51462

FromMRAB <python@mrabarnett.plus.com>
Date2013-07-29 18:04 +0100
Message-ID<mailman.5245.1375117457.3114.python-list@python.org>
In reply to#51449
On 29/07/2013 17:40, Ian Kelly wrote:
> On Mon, Jul 29, 2013 at 10:20 AM, Chris Angelico <rosuav@gmail.com> wrote:
>> On Mon, Jul 29, 2013 at 5:09 PM, MRAB <python@mrabarnett.plus.com> wrote:
>>> I'm surprised that Fraction(1/3) != Fraction(1, 3); after all, floats
>>> are approximate anyway, and the float value 1/3 is more likely to be
>>> Fraction(1, 3) than Fraction(6004799503160661, 18014398509481984).
>>
>> At what point should it become Fraction(1, 3)?
>
> At the point where the float is exactly equal to the value you get
> from the floating-point division 1/3.  If it's some other float then
> the user didn't get there by entering 1/3, so it's not worth trying to
> pretend that they did.
>
I thought that you're not meant to check for equality when using floats.

> We do a similar rounding when formatting floats to strings, but in
> that case one only has to worry about divisors that are powers of 10.
> I imagine it's going to take more time to find the correct fraction
> when any pair of relatively prime integers can be a candidate
> numerator and denominator.  Additionally, the string rounding only
> occurs when the float is being formatted for display; we certainly
> don't do it as the result of numeric operations where it could result
> in loss of precision.
>

[toc] | [prev] | [next] | [standalone]


#51473

FromGrant Edwards <invalid@invalid.invalid>
Date2013-07-29 18:46 +0000
Message-ID<kt6da9$7sg$1@reader1.panix.com>
In reply to#51462
On 2013-07-29, MRAB <python@mrabarnett.plus.com> wrote:
> On 29/07/2013 17:40, Ian Kelly wrote:
>> On Mon, Jul 29, 2013 at 10:20 AM, Chris Angelico <rosuav@gmail.com> wrote:
>>> On Mon, Jul 29, 2013 at 5:09 PM, MRAB <python@mrabarnett.plus.com> wrote:
>>>> I'm surprised that Fraction(1/3) != Fraction(1, 3); after all, floats
>>>> are approximate anyway, and the float value 1/3 is more likely to be
>>>> Fraction(1, 3) than Fraction(6004799503160661, 18014398509481984).
>>>
>>> At what point should it become Fraction(1, 3)?
>>
>> At the point where the float is exactly equal to the value you get
>> from the floating-point division 1/3.  If it's some other float then
>> the user didn't get there by entering 1/3, so it's not worth trying to
>> pretend that they did.
>
> I thought that you're not meant to check for equality when using floats.

You check for equality if equality is what you want to check.  However
much of the time when people _think_ they want to check for FP
equality, they're wrong.

You'll have to consult with a spiritual advisor to determin what you
are "meant" to do...

-- 
Grant Edwards               grant.b.edwards        Yow! Awright, which one of
                                  at               you hid my PENIS ENVY?
                              gmail.com            

[toc] | [prev] | [next] | [standalone]


#51488

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2013-07-29 20:19 +0000
Message-ID<51f6ce6d$0$30000$c3e8da3$5496439d@news.astraweb.com>
In reply to#51462
On Mon, 29 Jul 2013 18:04:19 +0100, MRAB wrote:

> I thought that you're not meant to check for equality when using floats.

Heavens no. You're only meant to *mostly* not check for equality using 
floats. As Miracle Max might say:

"It just so happens that floats are only MOSTLY inexact. There's a big 
difference between mostly inexact and all inexact. Mostly inexact is 
slightly exact."


Or to be a little more serious, "never check floats for equality" is 
actually pretty lousy advice. As Professor William Kahan puts it, the 
word "never" makes it rank superstition. 

There are three main problems with the general advice "never check floats 
for equality":

1) there are perfectly fine situations where you can check floats 
   for (in)equality without any problem, e.g.:

   if x == 0.0: print "Division by zero"
   else: y = 1/x  # perfectly safe

2) most of the time, those giving this advice don't actually say 
   what you should do instead;

3) and when they do, it's often either flat-out wrong, or at least
   incomplete.


For example, you might have been told to instead check whether your float 
is less than some epsilon:

abs(x - y) < eps

But chances are you've never been given any sort of reliable, 
deterministic algorithm for deciding what value eps should have. (Mostly 
because there is no such algorithm!) Or for deciding when to use absolute 
differences, like the above, and when to use relative differences.

Another issue: if eps is too big, you're taking distinct values and 
treating them as the same, which is bad. And if eps is too small, you're 
actually doing what you said you should never do, only slower.

E.g. suppose you want to check for a float which equals some value M, 
I'll pick M = 2199023255552.0 semi-arbitrarily, but it could be any of 
many numbers. You don't want to use equality, so you pick an epsilon:

eps = 1e-6  # seems reasonable...

and then test values like this:

M = 2199023255552.0
if abs(x - M) <= eps:
    print("close enough")


That is just a longer and slower way of calculating x == M. Why? Because 
the two floats immediately adjacent to M differ by more than your epsilon:

py> M = 2199023255552.0
py> M.hex()
'0x1.0000000000000p+41'
py> float.fromhex('0x1.0000000000001p+41') - M
0.00048828125
py> M - float.fromhex('0x1.fffffffffffffp+40')
0.000244140625

So in this case, any epsilon below 0.000244140625 is just wasting your 
time, since it cannot possibly detect any value other than M itself. And 
that critical cut-off depends on the numbers you are calculating with.

Oh, for what it's worth, I don't pretend to know how to choose an epsilon 
either. I can sometimes recognise a bad epsilon, but not a good one. 
Floats are *hard*.


-- 
Steven

[toc] | [prev] | [next] | [standalone]


#51463

FromTerry Reedy <tjreedy@udel.edu>
Date2013-07-29 13:08 -0400
Message-ID<mailman.5246.1375117736.3114.python-list@python.org>
In reply to#51449
On 7/29/2013 11:50 AM, Ian Kelly wrote:
> On Mon, Jul 29, 2013 at 9:43 AM, Steven D'Aprano
> <steve+comp.lang.python@pearwood.info> wrote:
>> Comparing floats to Fractions gives unexpected results:
>>
>> # Python 3.3
>> py> from fractions import Fraction
>> py> 1/3 == Fraction(1, 3)
>> False
>>
>> but:
>>
>> py> 1/3 == float(Fraction(1, 3))
>> True
>>
>>
>> I expected that float-to-Fraction comparisons would convert the Fraction
>> to a float, but apparently they do the opposite: they convert the float
>> to a Fraction:
>>
>> py> Fraction(1/3)
>> Fraction(6004799503160661, 18014398509481984)
>>
>>
>> Am I the only one who is surprised by this? Is there a general rule for
>> which way numeric coercions should go when doing such comparisons?
>
> Any float can be precisely represented as a Fraction.  Not so in the
> other direction.

In other words, there can be multiple unequal Franctions that have the 
same float value: for instance, Fraction(1,3) and 
Fraction(6004799503160661, 18014398509481984)

 > So from that standpoint it makes sense to me to cast to
 > Fraction when comparing.

Otherwise, == becomes non-transitive

-- 
Terry Jan Reedy

[toc] | [prev] | [next] | [standalone]


#51468

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2013-07-29 17:29 +0000
Message-ID<51f6a68c$0$30000$c3e8da3$5496439d@news.astraweb.com>
In reply to#51463
On Mon, 29 Jul 2013 13:08:20 -0400, Terry Reedy wrote:

> In other words, there can be multiple unequal Franctions that have the
> same float value: for instance, Fraction(1,3) and
> Fraction(6004799503160661, 18014398509481984)
> 
>  > So from that standpoint it makes sense to me to cast to Fraction when
>  > comparing.
> 
> Otherwise, == becomes non-transitive

This is Python, and we can make __eq__ methods that do anything, 
including be non-transitive, non-reflexive, and nonsensical if we like :-)

But I take your point, and that makes sense.


-- 
Steven

[toc] | [prev] | [next] | [standalone]


#51494

FromTerry Reedy <tjreedy@udel.edu>
Date2013-07-29 16:48 -0400
Message-ID<mailman.5268.1375130932.3114.python-list@python.org>
In reply to#51468
On 7/29/2013 1:29 PM, Steven D'Aprano wrote:
> On Mon, 29 Jul 2013 13:08:20 -0400, Terry Reedy wrote:
>
>> In other words, there can be multiple unequal Franctions that have the
>> same float value: for instance, Fraction(1,3) and
>> Fraction(6004799503160661, 18014398509481984)
>>
>>   > So from that standpoint it makes sense to me to cast to Fraction when
>>   > comparing.
>>
>> Otherwise, == becomes non-transitive
>
> This is Python, and we can make __eq__ methods that do anything,
> including be non-transitive, non-reflexive, and nonsensical if we like :-)

Yes, Python's developers can intentionally introduce bugs, but we try 
not to. The definitions of sets and dicts and containment assume that == 
means equality as mathematically defined. As one time, we had 0 == 0.0 
and 0 == Decimal(0) but 0.0 != Decimal(0) (and so on for all integral 
float values. That 'misfeature' was corrected because of the 'problems' 
it caused. That lesson learned, one of the design requirements for the 
new enum class (metaclass) was that it not re-introduce non-transitivity.

-- 
Terry Jan Reedy

[toc] | [prev] | [next] | [standalone]


#51464

FromChris Angelico <rosuav@gmail.com>
Date2013-07-29 18:14 +0100
Message-ID<mailman.5247.1375118049.3114.python-list@python.org>
In reply to#51449
On Mon, Jul 29, 2013 at 5:40 PM, Ian Kelly <ian.g.kelly@gmail.com> wrote:
> On Mon, Jul 29, 2013 at 10:20 AM, Chris Angelico <rosuav@gmail.com> wrote:
>> On Mon, Jul 29, 2013 at 5:09 PM, MRAB <python@mrabarnett.plus.com> wrote:
>>> I'm surprised that Fraction(1/3) != Fraction(1, 3); after all, floats
>>> are approximate anyway, and the float value 1/3 is more likely to be
>>> Fraction(1, 3) than Fraction(6004799503160661, 18014398509481984).
>>
>> At what point should it become Fraction(1, 3)?
>
> At the point where the float is exactly equal to the value you get
> from the floating-point division 1/3.  If it's some other float then
> the user didn't get there by entering 1/3, so it's not worth trying to
> pretend that they did.
>
> We do a similar rounding when formatting floats to strings, but in
> that case one only has to worry about divisors that are powers of 10.
> I imagine it's going to take more time to find the correct fraction
> when any pair of relatively prime integers can be a candidate
> numerator and denominator.

I imagine it is, and that's where the problem comes in. The true value
is somewhere between (X-0.5)/2**n and (X+0.5)/2**n, or whatever the
actual range is, and finding a "nice" fraction in that range isn't an
instant and direct translation. It's a useful feature, but not IMO
necessary for the constructor.

ChrisA

[toc] | [prev] | [next] | [standalone]


#51469

FromIan Kelly <ian.g.kelly@gmail.com>
Date2013-07-29 11:35 -0600
Message-ID<mailman.5250.1375119362.3114.python-list@python.org>
In reply to#51449
On Mon, Jul 29, 2013 at 11:04 AM, MRAB <python@mrabarnett.plus.com> wrote:
> On 29/07/2013 17:40, Ian Kelly wrote:
>> At the point where the float is exactly equal to the value you get
>> from the floating-point division 1/3.  If it's some other float then
>> the user didn't get there by entering 1/3, so it's not worth trying to
>> pretend that they did.
>>
> I thought that you're not meant to check for equality when using floats.

Equality checking is useful for floats when there is exactly one value
that you want to test for as in this case, as opposed to a range of
approximate values that are considered to be equal within rounding
error.  There is exactly one float that the expression 0.1 evaluates
to, and although it's not equal to the decimal 0.1, it is the only
float that we want to format as "0.1".  The immediately preceding and
following floats are 0.09999999999999999 and 0.10000000000000002, and
they are formatted as such because they're not equal to the float that
you get from 0.1.

[toc] | [prev] | [next] | [standalone]


#51478

FromSerhiy Storchaka <storchaka@gmail.com>
Date2013-07-29 22:34 +0300
Message-ID<mailman.5254.1375126459.3114.python-list@python.org>
In reply to#51449
29.07.13 19:09, MRAB написав(ла):
> I'm surprised that Fraction(1/3) != Fraction(1, 3); after all, floats
> are approximate anyway, and the float value 1/3 is more likely to be
> Fraction(1, 3) than Fraction(6004799503160661, 18014398509481984).

 >>> def approximate_fraction(f):
     prev_numer, numer = 0, 1
     prev_denom, denom = 1, 0
     r = f
     while True:
         i = math.floor(r)
         prev_numer, numer = numer, i * numer + prev_numer
         prev_denom, denom = denom, i * denom + prev_denom
         if i == r or numer / denom == f:
             break
         r = 1 / (r - i)

     return Fraction(numer, denom)

 >>> approximate_fraction(1/3)
Fraction(1, 3)
 >>> approximate_fraction(1e-17)
Fraction(1, 100000000000000000)
 >>> approximate_fraction(math.pi)
Fraction(245850922, 78256779)

I guess the Fraction constructor is more faster than this function.

[toc] | [prev] | [next] | [standalone]


Page 1 of 2  [1] 2  Next page →

Back to top | Article view | comp.lang.python


csiph-web