On Numbers

M

Mike Meyer

In the discussion of equality, the issue that decimal('3.0') == 3.0 is
False came up as a reason for changing the behavior of ==. The problem
with this is that the proposed change doesn't really fix anything, it
just gives different wrong behavior. The correct fix would seem to be
fixing python's handling of numbers.

It's not clear from the PEP why test for equality always fails. While
the PEP says that interactions with floats are disallowed as "to
tricky", comparisons seem to be an exception. In particular, equality
comparisons seem to always return false, whereas checking for
magnitude seems to work.

What appears to be missing in Python is a coherent model for all
numberic types. Other languages have this, so there's no obvious
reason that Python can't. Or maybe Python does, and I've overlooked
it. In that case, pointers to documentation would be appreciated.
If it doesn't exist, then I think this is something that should be
addressed for Py3K.

I'd like to work on that. The idea would be that all the numeric types
are representations of reals with different properties that make them
appropriate for different uses. The goal would be a model for a
numeric type that captures those properties in a way that allows them
to be combined to give sane behavior without being "to tricky".

To get started on that, I want to consider how other languages with
rich numeric type systems model them. I'm going to look at a couple of
languages in the LISP family. I'd like to solicit the community for
the names of languages (and pointers to references, if possible!) with
a coherent view of a rich set of numeric types.

Thanks,
<mike
 
D

Dan Sommers

... the proposed change doesn't really fix anything, it just gives
different wrong behavior ...
:)

To get started on that, I want to consider how other languages with
rich numeric type systems model them. I'm going to look at a couple of
languages in the LISP family. I'd like to solicit the community for
the names of languages (and pointers to references, if possible!) with
a coherent view of a rich set of numeric types.

Go right to the top, Common Lisp.

Seek out a paper or on-line copy of Guy L. Steele's _Common Lisp The
Language_, 2nd edition. A quick google search says that the "master"
online copy at CMU is not working correctly right now.

Or start at <http://www.lisp.org/HyperSpec/FrontMatter/index.html> and
click to Chapter 12, <http://www.lisp.org/HyperSpec/Body/chap-12.html>.

Regards,
Dan
 
B

Bengt Richter

In the discussion of equality, the issue that decimal('3.0') == 3.0 is
False came up as a reason for changing the behavior of ==. The problem
with this is that the proposed change doesn't really fix anything, it
just gives different wrong behavior. The correct fix would seem to be
fixing python's handling of numbers.

It's not clear from the PEP why test for equality always fails. While
the PEP says that interactions with floats are disallowed as "to
tricky", comparisons seem to be an exception. In particular, equality
comparisons seem to always return false, whereas checking for
magnitude seems to work.

What appears to be missing in Python is a coherent model for all
numberic types. Other languages have this, so there's no obvious
reason that Python can't. Or maybe Python does, and I've overlooked
it. In that case, pointers to documentation would be appreciated.
If it doesn't exist, then I think this is something that should be
addressed for Py3K.

I'd like to work on that. The idea would be that all the numeric types
are representations of reals with different properties that make them
appropriate for different uses. The goal would be a model for a
numeric type that captures those properties in a way that allows them
to be combined to give sane behavior without being "to tricky".

To get started on that, I want to consider how other languages with
rich numeric type systems model them. I'm going to look at a couple of
languages in the LISP family. I'd like to solicit the community for
the names of languages (and pointers to references, if possible!) with
a coherent view of a rich set of numeric types.
I suppose you have already found scheme's rsr5 spec, e.g.,
http://www.schemers.org/Documents/Standards/R5RS/

while thinking about numbers, the issue of equivalence that got us here
is also explored in scheme
http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-9.html#%_sec_6.1
and the section that follows in on numbers
http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-9.html#%_sec_6.2

for common lisP
http://www.lispworks.com/documentation/HyperSpec/Front/index.htm
you can also chase lots of interesting links (some dead though) from
http://www.cetus-links.org/oo_clos.html
cltl2 is at
http://www.cs.cmu.edu/Groups/AI/html/cltl/cltl2.html

Now try to get some work done ;-)

BTW, IIRC Ada's numbers include some interesting provisions for representing numbers
in fixed point with specified fractional accuracy
I google and found
http://www.adaic.org/standards/95lrm/html/RM-0-1.html
browsing a bit, scalar types are at
http://www.adaic.org/standards/95lrm/html/RM-3-5.html
integers include unsigned by way of genera modular definition
http://www.adaic.org/standards/95lrm/html/RM-3-5-4.html
and here is the fixed point
http://www.adaic.org/standards/95lrm/html/RM-3-5-9.html
Whoo, I haven't been into that stuff almost since Ada was "green" ;-)
Makes me wonder if generators could usefully have rendezvous('?, es?) ;-)

Regards,
Bengt Richter
 
P

Paul Rubin

Mike Meyer said:
I'd like to work on that. The idea would be that all the numeric types
are representations of reals with different properties that make them
appropriate for different uses.

2+3j?
 
A

Alex Martelli

Paul Rubin said:

Good point, so s/reals/complex numbers/ -- except for this "detail",
Mike's idea do seem well founded.

What *I* would also like would be a basenumber abstract class, like the
existing basestring, such that alternate numerics (like those I supply
in gmpy) could inherit from it in order to assert "yes, I *AM* a
number!" and allow isinstance-based checks rather than ones based on
"try to sum 0 and see if that gives an exception". A very small,
localized, and potentially useful change, IMHO.


Alex
 
D

Dan Bishop

Alex said:
Good point, so s/reals/complex numbers/ -- except for this "detail",
Mike's idea do seem well founded.

What *I* would also like would be a basenumber abstract class, like the
existing basestring, such that alternate numerics (like those I supply
in gmpy) could inherit from it in order to assert "yes, I *AM* a
number!" and allow isinstance-based checks rather than ones based on
"try to sum 0 and see if that gives an exception". A very small,
localized, and potentially useful change, IMHO.

+1

although I personally would prefer

(number)
/ \
(realnumber) complex
| | |
int float |
|
Decimal
 
T

Terry Hancock

although I personally would prefer

(number)
/ \
(realnumber) complex
| | |
int float |
|
Decimal

Mathematically, "real numbers" are a subset of "complex
numbers", though, so the set hierarchy would look like this:

"number"
|
complex
|
"real"
|
"rational"
|
+-----,
| |
float decimal
| |
+-----'
|
int

Noting of course that "real" and "number" are only
abstract concepts -- they can't truly be represented
on the computer. AFAIK, there is no general purpose
"rational" type in Python, but there could be.

I'm bothered by the fact that "int" can be coerced into
either "decimal" or "float". In practice, you should
have to choose one or the other. Practically speaking,
it probably makes more sense to do this:

"number"
|
complex
|
"real"
|
float
|
decimal
|
int

Because, in general, it is safer (no info loss) to
convert "int" to "decimal" where possible.

OTOH, for scientific or other performance-critical
applications, the exactitude of 'decimal' is less
desireable than the known precision and higher speed
of float implementations.

I suspect this is the sort of decision that was viewed
as "too tricky".
 
A

Alex Martelli

Terry Hancock said:
I'm bothered by the fact that "int" can be coerced into
either "decimal" or "float". In practice, you should
have to choose one or the other. Practically speaking,

Why ever?! You're indicating "is a subset of", and int IS a subset of
both (net of floats' issues with maximum sizes, of course) -- why should
you have to choose?
it probably makes more sense to do this:

"number"
|
complex
|
"real"
|
float
|
decimal
|
int

Because, in general, it is safer (no info loss) to
convert "int" to "decimal" where possible.

But decimal is NOTHING LIKE a subset of float; it's a SUPERSET (since 2
is a factor of 10, but not viceversa).
I suspect this is the sort of decision that was viewed
as "too tricky".

So _my_ preference would be to have abstract empty classes 'basenumber'
and 'baseinteger', with everything inheriting directly from 'basenumber'
except that 'int' (and if we want to keep it around 'long') inherits
from 'baseinteger'.

The main reason for existence of baseinteger is simple: a list (or other
sequence) should accept as index any baseinteger (so a package such as
gmpy can easily have instances of gmpy.mpz "be integers", directly
acceptable as list indices, w/o having to carry around uselessly an
instance of int... inheritance from non-abstract classes is NOT just a
question of subsets and supesets, it has implications in implementation
terms, too). It's a rather specific use case, I won't cry much if
'baseinteger' goes away.

The case for 'basenumber' is much stronger, and I'm making it on
python-dev (where I already made it in the past, in the last few years);
look for [basenumber site:python.org] and you'll find the few but
informative posts that went around, mostly about 3 years ago.

My main worry is that in the quest for a "perfect hierarchy" that keeps
proving too tricky, something with real-life usefulness such as
basenumber may fall by the wayside...


Alex
 
T

Tom Anderson

Good point, so s/reals/complex numbers/ -- except for this "detail",
Mike's idea do seem well founded.

1 ** 0.5 ?

I do like the mathematical cleanliness of making ints and floats do the
right thing when the answer would be complex, but as a pragmatic decision,
it might not be the right thing to do. It evidently wasn't thought it was
when python's current number system was designed. I think Tim Peters has
an opinion on this.

tom
 
S

Steven D'Aprano

Tom said:
1 ** 0.5 ?


[scratches head]

I'm not sure what point you are making here.

The square root of 1 is +1 (the negative root being
explicitly rejected). Pure mathematicians, who may be
expected to care whether the root is the integer 1 or
the real number 1, are unlikely to write 1**0.5,
prefering the squareroot symbol.

For the rest of us, including applied mathematicians,
1**0.5 implies floating point, which implies the
correct answer is 1.0.

So I don't really know what point you are making. What
solution(s) for 1**0.5 were you expecting?
 
P

Paul Rubin

Steven D'Aprano said:
So I don't really know what point you are making. What solution(s) for
1**0.5 were you expecting?

I think it was a sly way of saying "plus one or minus one".
 
E

Erik Max Francis

Steven said:
The square root of 1 is +1 (the negative root being
explicitly rejected). Pure mathematicians, who may be
expected to care whether the root is the integer 1 or
the real number 1, are unlikely to write 1**0.5,
prefering the squareroot symbol.

For the rest of us, including applied mathematicians,
1**0.5 implies floating point, which implies the
correct answer is 1.0.

So I don't really know what point you are making. What
solution(s) for 1**0.5 were you expecting?

He's probably getting at the fact that if you're dealing with complex
numbers, square root get a lot more complicated:

http://mathworld.wolfram.com/SquareRoot.html

But still, that doesn't change the fact that x**0.5 as is meant here is
the principal (positive) real square root, and that can be true whether
your hierarchy of numeric types includes a complex type or not.
 
T

Tom Anderson

He's probably getting at the fact that if you're dealing with complex
numbers, square root get a lot more complicated:

http://mathworld.wolfram.com/SquareRoot.html

But still, that doesn't change the fact that x**0.5 as is meant here is
the principal (positive) real square root, and that can be true whether
your hierarchy of numeric types includes a complex type or not.

Er, actually, i meant to write -1, but evidently missed a key, and failed
to check what i'd written.

But excellent discussion there, chaps! All shall have medals!

tom
 
S

Steven D'Aprano

On Tue, 17 Jan 2006 23:34:40 +0000, Tom Anderson wrote:

....
Er, actually, i meant to write -1, but evidently missed a key, and failed
to check what i'd written.

Since exponentiation has higher priority than negation, -1**0.5 is -1.0 in
both Python and ordinary mathematics.

Perhaps you meant to write (-1)**0.5, in which case Python developers have
a decision to make: should it assume real-valued maths unless explicitly
told differently, and hence raise an exception, or coerce the result to
complex?

In this case, Python raises an exception, as it should, unless you
explicitly uses complex numbers. That's the best behaviour for the
majority of people: most people don't even know what complex numbers are,
let alone want to deal with them in their code. Python, after all, is not
Mathematica.
 
E

Erik Max Francis

Steven said:
Perhaps you meant to write (-1)**0.5, in which case Python developers have
a decision to make: should it assume real-valued maths unless explicitly
told differently, and hence raise an exception, or coerce the result to
complex?

In this case, Python raises an exception, as it should, unless you
explicitly uses complex numbers. That's the best behaviour for the
majority of people: most people don't even know what complex numbers are,
let alone want to deal with them in their code. Python, after all, is not
Mathematica.

Note that cmath.sqrt returns the expected complex result for
cmath.sqrt(-1.0).
 
S

Steve Holden

Erik said:
Steven D'Aprano wrote:




Note that cmath.sqrt returns the expected complex result for
cmath.sqrt(-1.0).

Indeed. But even exponentiation can come respectably close:
(6.123233995736766e-17+1j)

regards
Steve
 
E

Erik Max Francis

Steve said:
Indeed. But even exponentiation can come respectably close:

(6.123233995736766e-17+1j)

Oh, no doubt. I was just pointing out that cmath.sqrt is what you want
if you really do want the complex result rather than the principal real one.
 
T

Tom Anderson

Since exponentiation has higher priority than negation, -1**0.5 is -1.0 in
both Python and ordinary mathematics.

Perhaps you meant to write (-1)**0.5,

Yes.

[FX: bangs head on keyboard]

I'm still getting this wrong after all these years.
in which case Python developers have a decision to make: should it
assume real-valued maths unless explicitly told differently, and hence
raise an exception, or coerce the result to complex?
Precisely.

In this case, Python raises an exception, as it should, unless you
explicitly uses complex numbers. That's the best behaviour for the
majority of people: most people don't even know what complex numbers
are, let alone want to deal with them in their code. Python, after all,
is not Mathematica.

I think i agree with you, as a matter of practical value. However, this
does go against the whole numeric unification thing we were discussing.

Hmm. What happens if i say (-1) ** (0.5+0j)? Ah, i get the right answer.
Well, that's handy - it means i don't have to resort to cmath or sprinkle
complex() calls all over the place for complex maths.

tom
 

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
474,278
Messages
2,571,386
Members
48,089
Latest member
H_coding

Latest Threads

Top