Using fractions instead of floats

T

Terry Reedy

|| I know from __future__ import division changes the behaivour to return
| floats instead of ints, but what I meant is to be able to implement a
| function (or class/method) which would return what _I_ want/decide.

When you define your own class, you can make any operator (on instances of
that class) mean anything you want. But to use such methonds, once
defined, you first have to make instances of that class using
classname(init data) or whatever.

There is no way to hijack int op int though.

tjr
 
M

mensanator

The 0.6 above is a floating point number, mathematically very close to
0.6 but definitely not equal to it, since 0.6 can't be represented
exactly as a float.

Oh, you mean something like this, right?
0.59999999999999998

So, the rational should have 59999999999999998
in the neumerator and 100000000000000000 in the
denominator? But it doesn't
mpq(3,5)

Why do you suppose that is? For that matter, why
does
'0.6'

give me an EXACT representation? Didn't you just
say it couldn't be represented exactly?

Which is correct,
'0.59999999999999998'

?

How does gmpy make the conversion from float to rational?
 
R

Rhamphoryncus

I was doing some programming in Python, and the idea came to my mind:
using fractions instead of floats when doing 2/5.

The core problem with rationals (other than the inability to handle
irrationals) is their tendency to require more and more memory as
calculations progress. This means they get mysteriously slower and
slower.

http://mail.python.org/pipermail/python-list/2002-October/166630.html

My own "pet numeric type" is fixed point. You get as much precision
as you specify. Alas, 2/5 would likely result in 0. ;)
 
R

Robert Kern

Oh, you mean something like this, right?

0.59999999999999998

So, the rational should have 59999999999999998
in the neumerator and 100000000000000000 in the
denominator?

Actually (5404319552844595 / 2**53) would be best.
But it doesn't

mpq(3,5)

Why do you suppose that is?

For the same reason that str() does. See below.
For that matter, why
does

'0.6'

give me an EXACT representation?

It doesn't. It just rounds at a lower number of decimal places than is necessary
to faithfully represent the number. str() is intended to give friendly results,
not strictly correct ones. In this case it happens that float(str(0.6)) == 0.6,
but this is not guaranteed. For most numbers that a user or programmer might
enter, there will only be a relatively small amount of precision necessary, and
float(str(x)) == x will tend to hold. That's why it does this.
Didn't you just
say it couldn't be represented exactly?
Yup.

Which is correct,

'0.59999999999999998'

?

The latter.
How does gmpy make the conversion from float to rational?

gmpy has a configurable transformation between floats and the internal
representation. I believe the default goes through str().

--
Robert Kern

"I have come to believe that the whole world is an enigma, a harmless enigma
that is made terrible by our own mad attempt to interpret it as though it had
an underlying truth."
-- Umberto Eco
 
N

Neil Cerutti

Scheme has prefix numeric operators, so that 1/2 is
unambiguously (for the interpreter and the user) a litteral for
'the fraction 1/2'. You can't avoid the confusion in python, as
binary operators are infix. Of course, we could create a new
kind of litteral. Let's see, / and // are already operators,
so why not use /// ? ;)

But you wouldn't actually need a literal rational represention.

But I'm just playing Devil's Advocate here; I like rationals in
Scheme and Lisp, but I don't see a need for them in Python.

On the other hand, Python has had complex numbers a long time,
and it doesn't need those more than rationals, does it? My guess
is that it got complex numbers but not rationals because
rationals just aren't very efficient.

But as a programmer, I'm mostly just a data-twiddler, and don't
generally need either of those numeric type in my day-to-day
work. So I'm not the guy to ask. ;)
 
A

Arnaud Delobelle

But you wouldn't actually need a literal rational represention.

In which case rationals are better off in a module than in the
language!
But I'm just playing Devil's Advocate here; I like rationals in
Scheme and Lisp, but I don't see a need for them in Python.
On the other hand, Python has had complex numbers a long time,
and it doesn't need those more than rationals, does it? My guess
is that it got complex numbers but not rationals because
rationals just aren't very efficient.

Another guess could be that real numbers being closed under the four
arithmetic operations, there is no danger to accidentally step into
complex numbers. OTOH floats and rationals are two (conflicting) ways
of extending integers.
But as a programmer, I'm mostly just a data-twiddler, and don't
generally need either of those numeric type in my day-to-day
work. So I'm not the guy to ask. ;)

I use rationals a lot, and I don't want them in the language :)
 
N

Neil Cerutti

In which case rationals are better off in a module than in the
language!

I was supposing that 1/2 would result in a rational number, which
would require language support, whether it's a literal rational
number or a division of two integer constants.
Another guess could be that real numbers being closed under the
four arithmetic operations, there is no danger to accidentally
step into complex numbers. OTOH floats and rationals are two
(conflicting) ways of extending integers.

You would have to adopt a few simple conversion rules, a la
Scheme. Inexact numbers are contagious, for example. It was
pretty shocking for a Scheme programmer to see the gmpy package
break that rule. ;)
I use rationals a lot, and I don't want them in the language :)

Fair enough.
 
M

mensanator

Actually (5404319552844595 / 2**53) would be best.


For the same reason that str() does. See below.


It doesn't. It just rounds at a lower number of decimal places than is necessary
to faithfully represent the number. str() is intended to give friendly results,
not strictly correct ones. In this case it happens that float(str(0.6)) == 0.6,
but this is not guaranteed. For most numbers that a user or programmer might
enter, there will only be a relatively small amount of precision necessary, and
float(str(x)) == x will tend to hold. That's why it does this.


The latter.


gmpy has a configurable transformation between floats and the internal
representation. I believe the default goes through str().

How do you do that? Is it configurable at run time or something that
has to be done when compiled?

But it is still wrong to say "0.6 is definitely not the same as 3/5".
One can say 0.6 doesn't have an exact float representation and that
inexact representation is not the same as 3/5. And I suppose one can
be surprised that when this inexact representation is coerced to a
rational the result is now exact.

Would that be a fairer way of putting it?
 
R

richyjsm

But it is still wrong to say "0.6 is definitely not the same as 3/5".

Out of context, I'd certainly agree. But from the context, I assumed
it was clear that the 0.6 was a Python float. I probably should have
made this clearer. My statement should probably have been something
like: a literal 0.6 in Python code does not have the same mathematical
value as the rational (or real) number 3/5.

Richard
 
M

mensanator

Out of context, I'd certainly agree. But from the context, I assumed
it was clear that the 0.6 was a Python float. I probably should have
made this clearer. My statement should probably have been something
like: a literal 0.6 in Python code does not have the same mathematical
value as the rational (or real) number 3/5.

Ok, so maybe I should have said: the literal 0.6 isn't what gets added
to mpq(1,3) to form the sum. What actually is added is the 0.6 coerced
to an mpq which is then added to mpq(1,3). So what 0.6 is as a float
is
relevent only to what it gets coerced to.
 
R

Robert Kern

How do you do that? Is it configurable at run time or something that
has to be done when compiled?

Run-time. Use gmpy.set_fcoform(). There is documentation:

http://gmpy.googlecode.com/svn/trunk/doc/gmpydoc.txt

However, it appears I was wrong about the default. The default will simply
extract the C double and call mpf_set_d() on it to convert it to the appropriate
internal mpf format. This faithfully gives:

In [26]: gmpy.mpf(0.6)
Out[26]: mpf('5.9999999999999998e-1')

The real conversion to mpq rational numbers takes place in using gmpy.f2q():

In [28]: gmpy.f2q?
Type: builtin_function_or_method
Base Class: <type 'builtin_function_or_method'>
Namespace: Interactive
Docstring:
f2q(x[,err]): returns the 'best' mpq approximating x to
within relative error err (default, x's precision); 'best'
rationals as per Stern-Brocot tree; mpz if denom is 1.
If err<0, error sought is 2.0 ** err.

In [44]: d = gmpy.mpf(0.6)

In [45]: d
Out[45]: mpf('5.9999999999999998e-1')

In [46]: d.getrprec()
Out[46]: 53

In [47]: gmpy.f2q(d)
Out[47]: mpq(3,5)

In [48]: gmpy.f2q(d, -53)
Out[48]: mpq(3,5)

In [49]: gmpy.f2q(d, -54)
Out[49]: mpq(3,5)

In [50]: gmpy.f2q(d, -55)
Out[50]: mpq(6002194384070353L,10003657306783922L)


The procedure followed here finds mpq(3,5) because evaluating "3.0/5.0" in as
binary floating point numbers in double precision gives you the same thing as
evaluating "0.6". So mpq(3,5) is the most parsimonious rational number
*approximating* Python's float("0.6") to within the default double-precision of
binary floating point arithmetic. However, float("0.6") is a rational number
that has the precise value of mpq(6002194384070353L,10003657306783922L).
But it is still wrong to say "0.6 is definitely not the same as 3/5".

In the context in which it was said, I don't think it's wrong though it is
incomplete and confusing if taken out of that context.
One can say 0.6 doesn't have an exact float representation and that
inexact representation is not the same as 3/5. And I suppose one can
be surprised that when this inexact representation is coerced to a
rational the result is now exact.

Would that be a fairer way of putting it?

Yes.

--
Robert Kern

"I have come to believe that the whole world is an enigma, a harmless enigma
that is made terrible by our own mad attempt to interpret it as though it had
an underlying truth."
-- Umberto Eco
 
G

Gabriel Genellina

En Tue, 02 Oct 2007 01:59:35 -0300, (e-mail address removed)
How does gmpy make the conversion from float to rational?

Well, you know, these days valuable software usually comes with something
people call "documentation". Incomprehensible documents in strange formats
talking about esoteric stuff that nobody is able to read (as if someone
wanted to actually read them!).
Hidden in the land of Mordor where the shadows lie, there is an ancient
scroll -a long time forgotten by mankind- where you can read terrible
things like this:

"If an mpf or float argument is passed, the
mpq is built from it with an 'optimal' approach based on a
Stern-Brocot tree." <http://gmpy.googlecode.com/svn/trunk/doc/gmpydoc.txt>

On the motivation for doing such evil things, you can read the explanation
given centuries ago by its creator, by then the High Wizard Alessandro:

"[The algorithm tries] to convert floats to rationals
giving the smallest denominator compatible with the hypothesis that
the float is precise to N bits [...] usually DBL_MANT_BITS [...] normally
53 today"

<http://mail.python.org/pipermail/python-list/2002-October/166974.html>
 
M

mensanator

En Tue, 02 Oct 2007 01:59:35 -0300, (e-mail address removed)
How does gmpy make the conversion from float to rational?

Well, you know, these days valuable software usually comes with something
people call "documentation". Incomprehensible documents in strange formats
talking about esoteric stuff that nobody is able to read (as if someone
wanted to actually read them!).
Hidden in the land of Mordor where the shadows lie, there is an ancient
scroll -a long time forgotten by mankind- where you can read terrible
things like this:

"If an mpf or float argument is passed, the
mpq is built from it with an 'optimal' approach based on a
Stern-Brocot tree." <http://gmpy.googlecode.com/svn/trunk/doc/gmpydoc.txt>

On the motivation for doing such evil things, you can read the explanation
given centuries ago by its creator, by then the High Wizard Alessandro:

"[The algorithm tries] to convert floats to rationals
giving the smallest denominator compatible with the hypothesis that
the float is precise to N bits [...] usually DBL_MANT_BITS [...] normally
53 today"

<http://mail.python.org/pipermail/python-list/2002-October/166974.html>

The best place to hide something is in the
documentation. Which I have read often, but
that's one of the places where my eyes glaze
over.

Thanks anyway. And to Robert Kern also.
 
R

Raymond L. Buvel

Neil said:
You would have to adopt a few simple conversion rules, a la
Scheme. Inexact numbers are contagious, for example. It was
pretty shocking for a Scheme programmer to see the gmpy package
break that rule. ;)

There is another package that supports rationals.

http://pypi.python.org/pypi/clnum/1.2

One of the reasons I created this package is that I didn't like
implicit conversion of inexact numbers to exact numbers. Instead
clnum either raises an exception (with Python floats and complex)
or converts the rational to a compatible inexact type (with mpf
and cmpf).

Other reasons for creating this package are gmpy doesn't support
complex numbers or the functions in the math and cmath modules.

However, gmpy does have many capabilities that are not in clnum so
you need to choose based on the type of problem you want to solve.

Ray Buvel
 
S

Steven D'Aprano

As pointed out by others, implementations of rationals in Python abound.
Whereas there is a canonical representation of floats and ints (and even
longints) in the machine, it is not the case for rationals. Moreover
most programming tasks do not need rationals, so why burden the language
with them? If one needs them, there are perfectly adequate modules to
import (even though I, like many others I suspect, have my own
implementation in pure Python). Finally, arithmetic would become very
confusing if there were three distinct numeric types; it already causes
enough confusion with two!

Or, to put it another way:


As pointed out by others, implementations of sets in Python abound.
Whereas there is a canonical representation of floats and ints in the
machine, it is not the case for sets. Moreover most programming tasks do
not need sets, so why burden the language with them? If one needs them,
there are perfectly adequate modules to import (even though I, like many
others I suspect, have my own implementation in pure Python). Finally,
programming would become very confusing if there were three distinct
sequence types; it already causes enough confusion with two!
 
P

Paul Rubin

There's already ints, longs, floats, complexes, and decimals. What's
the problem with one more?
 
S

Steven D'Aprano

There's already ints, longs, floats, complexes, and decimals. What's
the problem with one more?

Having different data types for different needs isn't a bug, it's a
feature.

Besides, ints and longs are more or less the same data type now, or at
least they are in the process of being integrated.
 
S

Steven D'Aprano

The core problem with rationals (other than the inability to handle
irrationals)

If that's a problem, it is one shared by ints and floats.

is their tendency to require more and more memory as
calculations progress. This means they get mysteriously slower and
slower.

http://mail.python.org/pipermail/python-list/2002-October/166630.html

I've read that thread, and although I see the claim made that rationals
are slow, I don't see the claim that they get "mysteriously" slower as
calculations progress. (And if they did, it would hardly be a mystery if
we know it is because they use more memory.)

I guess that could occur with unlimited precision rationals, in the same
way that it could occur with ever-larger longints, but I don't see how it
could occur with a simpler, limited precision implementation.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,773
Messages
2,569,594
Members
45,121
Latest member
LowellMcGu
Top