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 7 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 2 of 2 — ← Prev page 1 [2]


#51493

FromIan Kelly <ian.g.kelly@gmail.com>
Date2013-07-29 14:35 -0600
Message-ID<mailman.5267.1375130131.3114.python-list@python.org>
In reply to#51449

[Multipart message — attachments visible in raw view] — view raw

On Jul 29, 2013 1:37 PM, "Serhiy Storchaka" <storchaka@gmail.com> wrote:
>
> 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.

You might be able to speed it up a bit with numpy and the observation that
the update is a matrix multiplication. But I don't think that you can get
away from it being an iterative algorithm.

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


#51557

FromOscar Benjamin <oscar.j.benjamin@gmail.com>
Date2013-07-30 14:32 +0100
Message-ID<mailman.5307.1375191153.3114.python-list@python.org>
In reply to#51449
On 29 July 2013 17:09, MRAB <python@mrabarnett.plus.com> wrote:
> On 29/07/2013 16:43, Steven D'Aprano wrote:
>>
>> Comparing floats to Fractions gives unexpected results:

You may not have expected these results but as someone who regularly
uses the fractions module I do expect them.

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

Why would you do the above? You're deliberately trying to create a
float with a value that you know is not representable by the float
type. The purpose of Fractions is precisely that they can represent
all rational values, hence avoiding these problems.

When I use Fractions my intention is to perform exact computation. I
am very careful to avoid allowing floating point imprecision to sneak
into my calculations. Mixing floats and fractions in computation is
not IMO a good use of duck-typing.

>> 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 would say that if type A is a strict superset of type B then the
coercion should be to type A. This is the case for float and Fraction
since any float can be represented exactly as a Fraction but the
converse is not true.

> 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).

Refuse the temptation to guess: Fraction(float) should give the exact
value of the float. It should not give one of the countably infinite
number of other possible rational numbers that would (under a
particular rounding scheme and the floating point format in question)
round to the same float. If that is the kind of equality you would
like to test for in some particular situation then you can do so by
coercing to float explicitly.

Calling Fraction(1/3) is a misunderstanding of what the fractions
module is for and how to use it. The point is to guarantee avoiding
floating point errors; this is impossible if you use floating point
computations to initialise Fractions.

Writing Fraction(1, 3) does look a bit ugly so my preferred way to
reduce the boiler-plate in a script that uses lots of Fraction
"literals" is to do:

from fractions import Fraction as F

# 1/3 + 1/9 + 1/27 + ...
limit = F('1/3') / (1 - F('1/3'))

That's not as good as dedicated syntax but with code highlighting it's
still quite readable.


Oscar

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


#51702

FromTony the Tiger <tony@tiger.invalid>
Date2013-07-31 15:23 -0500
Message-ID<-tidnZTes6qk72TMnZ2dnUVZ8kadnZ2d@giganews.com>
In reply to#51449
On Mon, 29 Jul 2013 15:43:24 +0000, Steven D'Aprano wrote:

> Am I the only one who is surprised by this?

Most likely.

Floats aren't precise enough to be equal to a (true) fraction. float(1/3) 
is cut short somewhere by the computer, a (true) fraction of one third is 
not, it goes on forever.


 /Grrr
-- 
          ___                  ___
 (\_--_/)  | _ ._    _|_|_  _   |o _  _ ._
 ( 9  9 )  |(_)| |\/  |_| |(/_  ||(_|(/_|
 stripes are forever - as overripe ferrets

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


#51716

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2013-08-01 06:20 +0000
Message-ID<51f9fe27$0$30000$c3e8da3$5496439d@news.astraweb.com>
In reply to#51702
On Wed, 31 Jul 2013 15:23:21 -0500, Tony the Tiger wrote:

> On Mon, 29 Jul 2013 15:43:24 +0000, Steven D'Aprano wrote:
> 
>> Am I the only one who is surprised by this?
> 
> Most likely.
> 
> Floats aren't precise enough to be equal to a (true) fraction.
> float(1/3) is cut short somewhere by the computer, a (true) fraction of
> one third is not, it goes on forever.

I know this, and that's not what surprised me. What surprised me was that 
Fraction converts the float to a fraction, then compares. It surprises me 
because in other operations, Fractions down-cast to float.

Adding a float to a Fraction converts the Fraction to the nearest float, 
then adds:

py> 1/3 + Fraction(1, 3)
0.6666666666666666

but comparing a float to a Fraction does the conversion the other way, 
the float is up-cast to an exact Fraction, then compared.


-- 
Steven

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


#51719

FromChris Angelico <rosuav@gmail.com>
Date2013-08-01 07:32 +0100
Message-ID<mailman.66.1375338779.1251.python-list@python.org>
In reply to#51716
On Thu, Aug 1, 2013 at 7:20 AM, Steven D'Aprano
<steve+comp.lang.python@pearwood.info> wrote:
> I know this, and that's not what surprised me. What surprised me was that
> Fraction converts the float to a fraction, then compares. It surprises me
> because in other operations, Fractions down-cast to float.
>
> Adding a float to a Fraction converts the Fraction to the nearest float,
> then adds:
>
> py> 1/3 + Fraction(1, 3)
> 0.6666666666666666

Hmm. This is the one that surprises me. That would be like the
addition of a float and an int resulting in an int (at least in C; in
Python, where floats have limited range and ints have arbitrary
precision, the matter's not quite so clear-cut). Perhaps this needs to
be changed?

ChrisA

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


#51724

FromOscar Benjamin <oscar.j.benjamin@gmail.com>
Date2013-08-01 10:44 +0100
Message-ID<mailman.72.1375350274.1251.python-list@python.org>
In reply to#51716
On 1 August 2013 07:32, Chris Angelico <rosuav@gmail.com> wrote:
> On Thu, Aug 1, 2013 at 7:20 AM, Steven D'Aprano
> <steve+comp.lang.python@pearwood.info> wrote:
>> I know this, and that's not what surprised me. What surprised me was that
>> Fraction converts the float to a fraction, then compares. It surprises me
>> because in other operations, Fractions down-cast to float.
>>
>> Adding a float to a Fraction converts the Fraction to the nearest float,
>> then adds:
>>
>> py> 1/3 + Fraction(1, 3)
>> 0.6666666666666666
>
> Hmm. This is the one that surprises me. That would be like the
> addition of a float and an int resulting in an int (at least in C; in
> Python, where floats have limited range and ints have arbitrary
> precision, the matter's not quite so clear-cut). Perhaps this needs to
> be changed?

The Python numeric tower is here:
http://docs.python.org/3/library/numbers.html#module-numbers

Essentially it says that
    Integral < Rational < Real < Complex
and that numeric coercions in mixed type arithmetic should go from
left to right which makes sense mathematically in terms of the
subset/superset relationships between the numeric fields.

When you recast this in terms of Python's builtin/stdlib types it becomes
    int < Fraction < {float, Decimal} < complex
and taking account of boundedness and imprecision we find that the
only subset/superset relationships that are actually valid are
    int < Fraction
and
    float < complex
In fact Fraction is a superset of both float and Decimal (ignoring
inf/nan/-0 etc.). int is not a subset of float, Decimal or complex.
float is a superset of none of the types. Decimal is a superset of
float but the tower places them on the same level.

The real dividing line between {int, Fraction} and {float, Decimal,
complex} is about (in)exactness. The numeric tower ensures the
property that inexactness is contagious which I think is a good thing.
This is not explicitly documented anywhere. PEP 3141 makes a dangling
reference to an Exact ABC as a superclass of Rational but this is
unimplemented anywhere AFAICT:
http://www.python.org/dev/peps/pep-3141/

The reason contagious inexactness is a good thing is the same as
having contagious quite NaNs. It makes it possible to rule out inexact
computations playing a role in the final computed result. In my
previous post I asked what the use case is for mixing floats and
Rationals in computation. I have always considered this to be
something that I wanted to avoid and I'm glad that contagious
inexactness helps me to avoid mixing floats into exact computations.


Oscar

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


#51725

FromChris Angelico <rosuav@gmail.com>
Date2013-08-01 10:48 +0100
Message-ID<mailman.73.1375350529.1251.python-list@python.org>
In reply to#51716
On Thu, Aug 1, 2013 at 10:44 AM, Oscar Benjamin
<oscar.j.benjamin@gmail.com> wrote:
> The real dividing line between {int, Fraction} and {float, Decimal,
> complex} is about (in)exactness. The numeric tower ensures the
> property that inexactness is contagious which I think is a good thing.

*nods slowly*

That does make sense, albeit a little oddly. So when you're sorting
out different integer sizes (C's short/int/long, Py2's int/long), you
go to the "better" one, but when working with inexact types, you go to
the "worse" one. But I can see the logic in it.

ChrisA

[toc] | [prev] | [standalone]


Page 2 of 2 — ← Prev page 1 [2]

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


csiph-web