isinstance(Decimal(), Real) -> False?

I came across the following today: $ python3.3 Python 3.3.0 (default, Sep 29 2012, 17:14:58) [GCC 4.7.2] on linux Type "help", "copyright", "credits" or "license" for more information.
import numbers import decimal d = decimal.Decimal() isinstance(d, numbers.Number) True isinstance(d, numbers.Complex) False isinstance(d, numbers.Real) False isinstance(d, numbers.Rational) False isinstance(d, numbers.Integral) False
That seems plainly absurd to me. Decimals are quite clearly real numbers. I then found the following in PEP-3141 [1]: """ The Decimal Type After consultation with its authors it has been decided that the Decimal type should not at this time be made part of the numeric tower. """ What was the rationale for this decision and does it still apply? Oscar References: [1] http://www.python.org/dev/peps/pep-3141/#the-decimal-type

On 28 August 2013 11:47, Oscar Benjamin <oscar.j.benjamin@gmail.com> wrote:
I came across the following today:
$ python3.3 Python 3.3.0 (default, Sep 29 2012, 17:14:58) [GCC 4.7.2] on linux Type "help", "copyright", "credits" or "license" for more information.
import numbers import decimal d = decimal.Decimal() isinstance(d, numbers.Number) True isinstance(d, numbers.Complex) False isinstance(d, numbers.Real) False isinstance(d, numbers.Rational) False isinstance(d, numbers.Integral) False
That seems plainly absurd to me. Decimals are quite clearly real numbers. I then found the following in PEP-3141 [1]: """ The Decimal Type
After consultation with its authors it has been decided that the Decimal type should not at this time be made part of the numeric tower. """
What was the rationale for this decision and does it still apply?
If I recall correctly, it was the fact that isinstance(d, Real) implies isinstance(d, Complex), yet there's no way to do complex arithmetic with Decimal real and imaginary components. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

How can isinstance(d, Real) imply isinstance(d, Complex)? Can you elaborate a little more? It makes no sense to me, since the set of real numbers is a subset of the complex one. On Wed, Aug 28, 2013 at 9:02 AM, Nick Coghlan <ncoghlan@gmail.com> wrote:
On 28 August 2013 11:47, Oscar Benjamin <oscar.j.benjamin@gmail.com> wrote:
I came across the following today:
$ python3.3 Python 3.3.0 (default, Sep 29 2012, 17:14:58) [GCC 4.7.2] on linux Type "help", "copyright", "credits" or "license" for more information.
import numbers import decimal d = decimal.Decimal() isinstance(d, numbers.Number) True isinstance(d, numbers.Complex) False isinstance(d, numbers.Real) False isinstance(d, numbers.Rational) False isinstance(d, numbers.Integral) False
That seems plainly absurd to me. Decimals are quite clearly real numbers. I then found the following in PEP-3141 [1]: """ The Decimal Type
After consultation with its authors it has been decided that the Decimal type should not at this time be made part of the numeric tower. """
What was the rationale for this decision and does it still apply?
If I recall correctly, it was the fact that isinstance(d, Real) implies isinstance(d, Complex), yet there's no way to do complex arithmetic with Decimal real and imaginary components.
Cheers, Nick.
-- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia _______________________________________________ Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas
-- Michele Lacchia

On Wed, Aug 28, 2013 at 10:25:42AM +0200, Michele Lacchia wrote:
How can isinstance(d, Real) imply isinstance(d, Complex)? Can you elaborate a little more? It makes no sense to me, since the set of real numbers is a subset of the complex one.
You've just explained it. Since real numbers are a subset of complex numbers, every real number is a complex number with the imaginary part set to zero. py> import numbers py> isinstance(42.0, numbers.Complex) True py> (42.0).imag 0.0 This only applies with abstract base classes like numbers.Complex, not concrete classes like complex. py> isinstance(42.0, complex) False -- Steven

Damn, it seems that my mind was filled by some kind of fog... Ehm, I now get it, sorry. I really don't know what was passing in my mind before. On Wed, Aug 28, 2013 at 10:38 AM, Steven D'Aprano <steve@pearwood.info>wrote:
On Wed, Aug 28, 2013 at 10:25:42AM +0200, Michele Lacchia wrote:
How can isinstance(d, Real) imply isinstance(d, Complex)? Can you elaborate a little more? It makes no sense to me, since the set of real numbers is a subset of the complex one.
You've just explained it. Since real numbers are a subset of complex numbers, every real number is a complex number with the imaginary part set to zero.
py> import numbers py> isinstance(42.0, numbers.Complex) True py> (42.0).imag 0.0
This only applies with abstract base classes like numbers.Complex, not concrete classes like complex.
py> isinstance(42.0, complex) False
-- Steven _______________________________________________ Python-ideas mailing list Python-ideas@python.org http://mail.python.org/mailman/listinfo/python-ideas
-- Michele Lacchia

On Wed, Aug 28, 2013, at 4:25, Michele Lacchia wrote:
How can isinstance(d, Real) imply isinstance(d, Complex)? Can you elaborate a little more? It makes no sense to me, since the set of real numbers is a subset of the complex one.
I'm not sure what your question is, since that's precisely why it does imply it.

On 28 August 2013 08:02, Nick Coghlan <ncoghlan@gmail.com> wrote:
On 28 August 2013 11:47, Oscar Benjamin <oscar.j.benjamin@gmail.com> wrote:
That seems plainly absurd to me. Decimals are quite clearly real numbers. I then found the following in PEP-3141 [1]: """ The Decimal Type
After consultation with its authors it has been decided that the Decimal type should not at this time be made part of the numeric tower. """
What was the rationale for this decision and does it still apply?
If I recall correctly, it was the fact that isinstance(d, Real) implies isinstance(d, Complex), yet there's no way to do complex arithmetic with Decimal real and imaginary components.
There's also no way to arithmetic with int/Fraction real and imaginary components (e.g. gaussian integers etc.) but these are still instances of Complex.
From the PEP ''' Complex defines the operations that work on the builtin complex type.
In short, those are: conversion to complex, bool(), .real, .imag, +, -, *, /, **, abs(), .conjugate(), ==, and !=. ''' Complex(decimal) works, Decimal defines all the above operations and has .real and .imag attributes: $ python3.3 Python 3.3.0 (default, Sep 29 2012, 17:14:58) [GCC 4.7.2] on linux Type "help", "copyright", "credits" or "license" for more information.
from decimal import Decimal d = Decimal('4') complex(d) (4+0j) bool(d) True d.real Decimal('4') d.imag Decimal('0') d + d Decimal('8') d - d Decimal('0') d * d Decimal('16') d / d Decimal('1') d ** d Decimal('256') abs(d) Decimal('4') d.conjugate() Decimal('4') d == d True d != d False
Oscar

On 28 August 2013 11:15, Oscar Benjamin <oscar.j.benjamin@gmail.com> wrote:
There's also no way to arithmetic with int/Fraction real and imaginary components (e.g. gaussian integers etc.) but these are still instances of Complex.
The difference is that there is no implicit conversion from Decimal to float:
from decimal import Decimal as D D('1.5') + 2.3 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for +: 'Decimal' and 'float' from fractions import Fraction as F F(1,2) + 2.3 2.8
IIRC, that's the reason Decimal is treated specially here - maybe it's not a sufficiently good reason (I don't have an opinion on that) but from what little I recall of the discussions around the time, it's the key distinguishing feature of Decimal in these matters... Paul

On 28 August 2013 11:22, Paul Moore <p.f.moore@gmail.com> wrote:
On 28 August 2013 11:15, Oscar Benjamin <oscar.j.benjamin@gmail.com> wrote:
There's also no way to arithmetic with int/Fraction real and imaginary components (e.g. gaussian integers etc.) but these are still instances of Complex.
The difference is that there is no implicit conversion from Decimal to float:
from decimal import Decimal as D D('1.5') + 2.3 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for +: 'Decimal' and 'float' from fractions import Fraction as F F(1,2) + 2.3 2.8
IIRC, that's the reason Decimal is treated specially here - maybe it's not a sufficiently good reason (I don't have an opinion on that) but from what little I recall of the discussions around the time, it's the key distinguishing feature of Decimal in these matters...
Why shouldn't there be implicit conversion in Decimal arithmetic? There already is for all the other numeric types. Also explicit conversion seems to blocked in some cases. This one in particular bothers me (since it's often desirable to get a decimal representation of a fraction): $ python3.3 Python 3.3.0 (default, Sep 29 2012, 17:14:58) [GCC 4.7.2] on linux Type "help", "copyright", "credits" or "license" for more information.
from decimal import Decimal as D from fractions import Fraction as F D(F(1, 2)) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: conversion from Fraction to Decimal is not supported
Oscar

On Wed, Aug 28, 2013 at 12:49 PM, Oscar Benjamin <oscar.j.benjamin@gmail.com
wrote:
On 28 August 2013 11:22, Paul Moore <p.f.moore@gmail.com> wrote:
On 28 August 2013 11:15, Oscar Benjamin <oscar.j.benjamin@gmail.com> wrote:
There's also no way to arithmetic with int/Fraction real and imaginary components (e.g. gaussian integers etc.) but these are still instances of Complex.
The difference is that there is no implicit conversion from Decimal to float:
from decimal import Decimal as D D('1.5') + 2.3 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for +: 'Decimal' and 'float' from fractions import Fraction as F F(1,2) + 2.3 2.8
IIRC, that's the reason Decimal is treated specially here - maybe it's not a sufficiently good reason (I don't have an opinion on that) but from what little I recall of the discussions around the time, it's the key distinguishing feature of Decimal in these matters...
Why shouldn't there be implicit conversion in Decimal arithmetic? There already is for all the other numeric types. Also explicit conversion seems to blocked in some cases. This one in particular bothers me (since it's often desirable to get a decimal representation of a fraction):
$ python3.3 Python 3.3.0 (default, Sep 29 2012, 17:14:58) [GCC 4.7.2] on linux Type "help", "copyright", "credits" or "license" for more information.
from decimal import Decimal as D from fractions import Fraction as F D(F(1, 2)) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: conversion from Fraction to Decimal is not supported
I would think that it's because you can express a fraction as finite decimal expansion iff the prime decomposition of denominator contains only 2s and 5s, since conceptualy decimal is just a fraction with power of 10 in denominator. So Decimal is less expressible than Fraction.

On 28 August 2013 12:14, Draic Kin <drekin@gmail.com> wrote:
Why shouldn't there be implicit conversion in Decimal arithmetic? There already is for all the other numeric types. Also explicit conversion seems to blocked in some cases. This one in particular bothers me (since it's often desirable to get a decimal representation of a fraction):
$ python3.3 Python 3.3.0 (default, Sep 29 2012, 17:14:58) [GCC 4.7.2] on linux Type "help", "copyright", "credits" or "license" for more information.
from decimal import Decimal as D from fractions import Fraction as F D(F(1, 2)) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: conversion from Fraction to Decimal is not supported
I would think that it's because you can express a fraction as finite decimal expansion iff the prime decomposition of denominator contains only 2s and 5s, since conceptualy decimal is just a fraction with power of 10 in denominator. So Decimal is less expressible than Fraction.
The same is true of float but float(Fraction) happily works and so does float(int), complex(int), float(Decimal) and Decimal(float) (depending on the context Decimal can be either a subset or a superset of float). A recent example where I wanted to do this was: def sum_exact(nums): T = type(nums[0]) return T(sum(map(Fraction, nums))) The above sum function can happily sum anything that is convertible to Fraction (which includes Decimals in Python 3.3). However Decimal(Fraction) fails so you need something like: def sum_exact(nums): T = type(nums[0]) if issubclass(T, Decimal): return T.from_decimal(...) else: ... This just seems unnecessary to me. Oscar

On Wed, Aug 28, 2013 at 1:35 PM, Oscar Benjamin <oscar.j.benjamin@gmail.com>wrote:
On 28 August 2013 12:14, Draic Kin <drekin@gmail.com> wrote:
Why shouldn't there be implicit conversion in Decimal arithmetic? There already is for all the other numeric types. Also explicit conversion seems to blocked in some cases. This one in particular bothers me (since it's often desirable to get a decimal representation of a fraction):
$ python3.3 Python 3.3.0 (default, Sep 29 2012, 17:14:58) [GCC 4.7.2] on linux Type "help", "copyright", "credits" or "license" for more information.
from decimal import Decimal as D from fractions import Fraction as F D(F(1, 2)) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: conversion from Fraction to Decimal is not supported
I would think that it's because you can express a fraction as finite decimal expansion iff the prime decomposition of denominator contains only 2s and 5s, since conceptualy decimal is just a fraction with power of 10 in denominator. So Decimal is less expressible than Fraction.
The same is true of float but float(Fraction) happily works and so does float(int), complex(int), float(Decimal) and Decimal(float) (depending on the context Decimal can be either a subset or a superset of float). A recent example where I wanted to do this was:
def sum_exact(nums): T = type(nums[0]) return T(sum(map(Fraction, nums)))
The above sum function can happily sum anything that is convertible to Fraction (which includes Decimals in Python 3.3). However Decimal(Fraction) fails so you need something like:
def sum_exact(nums): T = type(nums[0]) if issubclass(T, Decimal): return T.from_decimal(...) else: ...
This just seems unnecessary to me.
Maybe it's because float and complex don't care for exactness. On the other hand Decimal always represent exactly the value of int and float but it cannot represent exactly the value of Fraction(1, 3). But if would be nice if one could make a decimal of given precision from fraction or make exact decimal representaion of a fraction if it is possible and raise exception otherwise.

On 28 August 2013 13:32, Draic Kin <drekin@gmail.com> wrote:
On Wed, Aug 28, 2013 at 1:35 PM, Oscar Benjamin <oscar.j.benjamin@gmail.com> wrote:
The same is true of float but float(Fraction) happily works and so does float(int), complex(int), float(Decimal) and Decimal(float) (depending on the context Decimal can be either a subset or a superset of float). A recent example where I wanted to do this was:
def sum_exact(nums): T = type(nums[0]) return T(sum(map(Fraction, nums)))
The above sum function can happily sum anything that is convertible to Fraction (which includes Decimals in Python 3.3). However Decimal(Fraction) fails so you need something like:
def sum_exact(nums): T = type(nums[0]) if issubclass(T, Decimal): return T.from_decimal(...) else: ...
This just seems unnecessary to me.
Maybe it's because float and complex don't care for exactness. On the other hand Decimal always represent exactly the value of int and float but it cannot represent exactly the value of Fraction(1, 3). But if would be nice if one could make a decimal of given precision from fraction or make exact decimal representaion of a fraction if it is possible and raise exception otherwise.
But that's why the decimal module has the Inexact etc. traps. Here's a table of coercions that are possible in 3.3. Key (i:int, F:Fraction, D:Decimal, f:float, c:complex). If Tr is the type of the row and Tc is the type of the column then X indicates that Tr(Tc) is always possible and exact and R indicates that rounding may occur: i F D f c --+--------------- i | X R R R F | X X X X D | X X X f | R R R X c | R R R X X There's a little hole in the middle there that makes the real types not quite inter-operable. It's easy to implement the appropriate conversion: def decimal_from_rational(r): # Result will be correctly rounded according to the current context. # Raises any of the signals Clamped, InvalidOperation, DivisionByZero, # Inexact, Rounded, Subnormal, Overflow, or Underflow as appropriate. return Decimal(r.numerator) / Decimal(r.denominator) But it's much more useful if that conversion takes place in Decimal.__new__. Oscar

On Wed, Aug 28, 2013 at 8:02 AM, Nick Coghlan <ncoghlan@gmail.com> wrote:
The Decimal Type
After consultation with its authors it has been decided that the Decimal type should not at this time be made part of the numeric tower. """
What was the rationale for this decision and does it still apply?
If I recall correctly, it was the fact that isinstance(d, Real) implies isinstance(d, Complex), yet there's no way to do complex arithmetic with Decimal real and imaginary components.
In the past, there's also been a general desire to keep the decimal module API closely focused on the specification, and to avoid adding too much functionality outside the spec; I suspect that that was the main motivation. I don't recall Complex entering the discussion, but it might well have done. It may be time to revisit the discussion. I'd like to see the Decimal type being more closely integrated with the rest of the language in the future (and adding the C version of Decimal was the first step along that path). -- Mark
participants (8)
-
Draic Kin
-
Mark Dickinson
-
Michele Lacchia
-
Nick Coghlan
-
Oscar Benjamin
-
Paul Moore
-
random832@fastmail.us
-
Steven D'Aprano