Re: [Python-ideas] isinstance(Decimal(), Real) -> False?
Darn right it should return False. Given the principle of least surprise (and my prejudices built up over 40 years as a computer programmer) I would expect that decimal.Decimal data would be stored internally as some form of decimal data, and would store into a database as such. It would be expected to be in a fixed point format. Real, on the other hand, I would expect to be stored as an IEEE double precision floating point number, or something like that. I don't care whether a fixed point decimal number might be defined by a mathematician as "real" -- I care whether it can be processed by an FPU, and whether it will loose precision in large financial calculations.
On Wed, Aug 28, 2013 at 12:30 PM, Vernon D. Cole
Darn right it should return False. Given the principle of least surprise (and my prejudices built up over 40 years as a computer programmer) I would expect that decimal.Decimal data would be stored internally as some form of decimal data, and would store into a database as such. It would be expected to be in a fixed point format. Real, on the other hand, I would expect to be stored as an IEEE double precision floating point number, or something like that. I don't care whether a fixed point decimal number might be defined by a mathematician as "real" -- I care whether it can be processed by an FPU, and whether it will loose precision in large financial calculations.
For the same reason, I could think that isinstance(Decimal, Rational) -> True and issubclass(Rational, Real) -> False. It's more about exact vs. non-exact computations which is orthogonal to number hierarchy. Maybe there should be some ExactNumber abstract base class and some convention that exact shouldn't coerce with non-exact. So Decimal + float should raise an exception even if both would be subclasses of Real (and Decimal even of Rational). Or maybe it would be enough if there were just non-exact variants of Real and Complex since non-exactness if just issue of them.
On 28 August 2013 11:48, Draic Kin
On Wed, Aug 28, 2013 at 12:30 PM, Vernon D. Cole
wrote: Darn right it should return False. Given the principle of least surprise (and my prejudices built up over 40 years as a computer programmer) I would expect that decimal.Decimal data would be stored internally as some form of decimal data, and would store into a database as such. It would be expected to be in a fixed point format. Real, on the other hand, I would expect to be stored as an IEEE double precision floating point number, or something like that. I don't care whether a fixed point decimal number might be defined by a mathematician as "real" -- I care whether it can be processed by an FPU, and whether it will loose precision in large financial calculations.
Decimals are effectively stored internally as integers. They won't be processed by the FPU which operates on binary floating point types. Also the decimal module has traps for people who want to prevent inexact etc. operations.
For the same reason, I could think that isinstance(Decimal, Rational) -> True and issubclass(Rational, Real) -> False. It's more about exact vs. non-exact computations which is orthogonal to number hierarchy. Maybe there should be some ExactNumber abstract base class and some convention that exact shouldn't coerce with non-exact. So Decimal + float should raise an exception even if both would be subclasses of Real (and Decimal even of Rational). Or maybe it would be enough if there were just non-exact variants of Real and Complex since non-exactness if just issue of them.
I agree that an Exact ABC would be good. The PEP has a reference to exact as a superclass of Rational but I guess it's just left over from a previous edit: http://www.python.org/dev/peps/pep-3141/#numeric-classes Oscar
On Wed, Aug 28, 2013 at 11:53 AM, Oscar Benjamin wrote: On Wed, Aug 28, 2013 at 12:30 PM, Vernon D. Cole Darn right it should return False. Given the principle of least surprise (and my prejudices built up over 40 years as a computer programmer) I
would
expect that decimal.Decimal data would be stored internally as some On 28 August 2013 11:48, Draic Kin decimal data, and would store into a database as such. It would be
expected
to be in a fixed point format. Real, on the other hand, I would expect
to
be stored as an IEEE double precision floating point number, or
something
like that.
I don't care whether a fixed point decimal number might be defined by
a
mathematician as "real" -- I care whether it can be processed by an
FPU, and
whether it will loose precision in large financial calculations. Decimals are effectively stored internally as integers. That surprises me, a bit, but given the efficiency of modern 64 bit
processors it's not a bad choice. We've moved on from the day when CPUs
like the Intel 4004 and the IBM 360 expected to do most of their work in
Binary Coded Decimal. They won't be
processed by the FPU which operates on binary floating point types. _Exactly_ my point. Also the decimal module has traps for people who want to prevent
inexact etc. operations. For the same reason, I could think that isinstance(Decimal, Rational) ->
True and issubclass(Rational, Real) -> False. It's more about exact vs.
non-exact computations which is orthogonal to number hierarchy. Maybe
there
should be some ExactNumber abstract base class and some convention that
exact shouldn't coerce with non-exact. So Decimal + float should raise an
exception even if both would be subclasses of Real (and Decimal even of
Rational). Or maybe it would be enough if there were just non-exact
variants
of Real and Complex since non-exactness if just issue of them. That's it. This is a "practicality beats purity" issue. Python types
Real and Complex are not exact because we usually don't need exact. "Close
enough" is enough. I know that 3.14159 is not the true value for Pi, but
it suffices when I am trying to figure out how fast a vehicle will travel
with a given size tire.
Now, consider when I am processing the arguments for an SQL "execute"
method. [*] How do I prepare the values for the underlying db engine? I
use a long list which includes lots of "elif isinstance(value, <some Python
type>):"
The code for "isinstance(value, Real)" is quite straight forward.
The code for "isinstance(value, decimal.Decimal)" requires 18 lines of
incredibly obscure Python.
I really do need to be able to tell them apart.
I agree that an Exact ABC would be good. The PEP has a reference to exact as a superclass of Rational but I guess it's just left over from
a previous edit:
http://www.python.org/dev/peps/pep-3141/#numeric-classes Oscar [*] For this example, I am referring to the code in
http://sf.net/projects/adodbapi of which I am the maintainer.
On Wed, Aug 28, 2013 at 2:06 PM, Vernon D. Cole
On Wed, Aug 28, 2013 at 11:53 AM, Oscar Benjamin < oscar.j.benjamin@gmail.com> wrote:
On Wed, Aug 28, 2013 at 12:30 PM, Vernon D. Cole
wrote:
Darn right it should return False. Given the principle of least
surprise
(and my prejudices built up over 40 years as a computer programmer) I would expect that decimal.Decimal data would be stored internally as some
On 28 August 2013 11:48, Draic Kin
wrote: form of decimal data, and would store into a database as such. It would be expected to be in a fixed point format. Real, on the other hand, I would expect to be stored as an IEEE double precision floating point number, or something like that. I don't care whether a fixed point decimal number might be defined by a mathematician as "real" -- I care whether it can be processed by an FPU, and whether it will loose precision in large financial calculations.
Decimals are effectively stored internally as integers.
That surprises me, a bit, but given the efficiency of modern 64 bit processors it's not a bad choice. We've moved on from the day when CPUs like the Intel 4004 and the IBM 360 expected to do most of their work in Binary Coded Decimal.
They won't be processed by the FPU which operates on binary floating point types.
_Exactly_ my point.
Also the decimal module has traps for people who want to prevent inexact etc. operations.
For the same reason, I could think that isinstance(Decimal, Rational) -> True and issubclass(Rational, Real) -> False. It's more about exact vs. non-exact computations which is orthogonal to number hierarchy. Maybe there should be some ExactNumber abstract base class and some convention that exact shouldn't coerce with non-exact. So Decimal + float should raise an exception even if both would be subclasses of Real (and Decimal even of Rational). Or maybe it would be enough if there were just non-exact variants of Real and Complex since non-exactness if just issue of them.
That's it. This is a "practicality beats purity" issue. Python types Real and Complex are not exact because we usually don't need exact. "Close enough" is enough. I know that 3.14159 is not the true value for Pi, but it suffices when I am trying to figure out how fast a vehicle will travel with a given size tire.
Python types Real and Complex are abstract base classes, they have no implementation so are neither exact not non-exact, Python types float and complex are non-exact.
Now, consider when I am processing the arguments for an SQL "execute" method. [*] How do I prepare the values for the underlying db engine? I use a long list which includes lots of "elif isinstance(value, <some Python type>):"
The code for "isinstance(value, Real)" is quite straight forward.
The code for "isinstance(value, decimal.Decimal)" requires 18 lines of incredibly obscure Python.
I really do need to be able to tell them apart.
Couldn't you tell them apart by isinstance(value, decimal.Decimal) vs. isinstance(value, float)?
I stand corrected. In my mind "Real" and "float" were always synonymous.
(Probably a result of over exposure to FORTRAN, or Alzheimer's.)
On Wed, Aug 28, 2013 at 1:25 PM, Draic Kin
On Wed, Aug 28, 2013 at 2:06 PM, Vernon D. Cole
wrote: On Wed, Aug 28, 2013 at 11:53 AM, Oscar Benjamin < oscar.j.benjamin@gmail.com> wrote:
On Wed, Aug 28, 2013 at 12:30 PM, Vernon D. Cole < vernondcole@gmail.com> wrote:
Darn right it should return False. Given the principle of least
surprise
(and my prejudices built up over 40 years as a computer programmer) I would expect that decimal.Decimal data would be stored internally as some
On 28 August 2013 11:48, Draic Kin
wrote: form of decimal data, and would store into a database as such. It would be expected to be in a fixed point format. Real, on the other hand, I would expect to be stored as an IEEE double precision floating point number, or something like that. I don't care whether a fixed point decimal number might be defined by a mathematician as "real" -- I care whether it can be processed by an FPU, and whether it will loose precision in large financial calculations.
Decimals are effectively stored internally as integers.
That surprises me, a bit, but given the efficiency of modern 64 bit processors it's not a bad choice. We've moved on from the day when CPUs like the Intel 4004 and the IBM 360 expected to do most of their work in Binary Coded Decimal.
They won't be processed by the FPU which operates on binary floating point types.
_Exactly_ my point.
Also the decimal module has traps for people who want to prevent inexact etc. operations.
For the same reason, I could think that isinstance(Decimal, Rational) -> True and issubclass(Rational, Real) -> False. It's more about exact vs. non-exact computations which is orthogonal to number hierarchy. Maybe there should be some ExactNumber abstract base class and some convention that exact shouldn't coerce with non-exact. So Decimal + float should raise an exception even if both would be subclasses of Real (and Decimal even of Rational). Or maybe it would be enough if there were just non-exact variants of Real and Complex since non-exactness if just issue of them.
That's it. This is a "practicality beats purity" issue. Python types Real and Complex are not exact because we usually don't need exact. "Close enough" is enough. I know that 3.14159 is not the true value for Pi, but it suffices when I am trying to figure out how fast a vehicle will travel with a given size tire.
Python types Real and Complex are abstract base classes, they have no implementation so are neither exact not non-exact, Python types float and complex are non-exact.
Now, consider when I am processing the arguments for an SQL "execute" method. [*] How do I prepare the values for the underlying db engine? I use a long list which includes lots of "elif isinstance(value, <some Python type>):"
The code for "isinstance(value, Real)" is quite straight forward.
The code for "isinstance(value, decimal.Decimal)" requires 18 lines of incredibly obscure Python.
I really do need to be able to tell them apart.
Couldn't you tell them apart by isinstance(value, decimal.Decimal) vs. isinstance(value, float)?
On 28 August 2013 13:06, Vernon D. Cole
Now, consider when I am processing the arguments for an SQL "execute" method. [*] How do I prepare the values for the underlying db engine? I use a long list which includes lots of "elif isinstance(value, <some Python type>):"
The code for "isinstance(value, Real)" is quite straight forward.
The code for "isinstance(value, decimal.Decimal)" requires 18 lines of incredibly obscure Python.
I really do need to be able to tell them apart.
Can you not just reverse the order of those two tests? i.e.: elif isinstance(value, decimal.Decimal): # obscure decimal code elif isinstance(value, Real): # code for real or what about elif isinstance(value, decimal.Decimal) and not isinstance(value, Real): # obscure decimal code or even elif isinstance(value, float): # code for float elif isinstance(value, decimal.Decimal): # obscure decimal code What other types are you hoping to catch by testing against numbers.Real instead of float? What guarantees does the Real ABC give you in this situation that Decimal does not (hence requiring all the obscure code)? BTW it's not a big deal for database code but for intensive numerical code it's worth noting that testing against numbers.Real is significantly slower than testing against float: $ py -3.3 -m timeit -s 'from numbers import Real' 'isinstance(1.0, Real)' 1000000 loops, best of 3: 1.93 usec per loop $ py -3.3 -m timeit -s 'from numbers import Real' 'isinstance(1.0, float)' 1000000 loops, best of 3: 0.217 usec per loop You can use a tuple to speed it up for float: $ py -3.3 -m timeit -s 'from numbers import Real' 'isinstance(1.0, (float, Real))' 1000000 loops, best of 3: 0.277 usec per loop Given that $ py -3.3 -m timeit -s 'a = 123.45; b=987.654' 'a+b' 10000000 loops, best of 3: 0.0718 usec per loop it seems that isinstance(x, float) is roughly the same as 4 arithmetic operations and isinstance(x, Real) is about 40 arithmetic operations. Oscar
On 28/08/13 22:06, Vernon D. Cole wrote:
Now, consider when I am processing the arguments for an SQL "execute" method. [*] How do I prepare the values for the underlying db engine? I use a long list which includes lots of "elif isinstance(value, <some Python type>):"
The code for "isinstance(value, Real)" is quite straight forward.
number.Real is not merely a synonym for built-in float. For example, should somebody decide to implement a floating point number class based on base 3 rather than 2 or 10 (perhaps they are trying to emulate some old Soviet ternary-based computer), it would be a subclass of Real.
The code for "isinstance(value, decimal.Decimal)" requires 18 lines of incredibly obscure Python.
I really do need to be able to tell them apart.
You can always tell them apart. Decimal instances are instances of Decimal. Non-Decimal instances are not. -- Steven
On 28/08/13 20:48, Draic Kin wrote:
For the same reason, I could think that isinstance(Decimal, Rational) -> True
If Decimal were a subclass of Rational, so should float. The only fundamental difference between the two is that one uses base 10 floating point numbers and the other uses base 2.
and issubclass(Rational, Real) -> False. It's more about exact vs. non-exact computations which is orthogonal to number hierarchy.
The numeric tower is precisely about the numeric hierarchy of Number > Complex > Real > Rational > Integral, and since they are all *abstract* base classes, exact and inexact doesn't come into it. Concrete classes can be inexact or exact, or one could implement separate Exact and Inexact towers. In practice, it's hard to think of a concrete way to implement exact real numbers. Maybe a symbolic maths application like Mathematica comes close?
Maybe there should be some ExactNumber abstract base class and some convention that exact shouldn't coerce with non-exact. So Decimal + float should raise an exception even if both would be subclasses of Real (and Decimal even of Rational). Or maybe it would be enough if there were just non-exact variants of Real and Complex since non-exactness if just issue of them.
Personally, I would implement inexact/exact as an attribute on the type: if type(x).exact: ... sort of thing. -- Steven
On Thu, Aug 29, 2013 at 4:02 AM, Steven D'Aprano
On 28/08/13 20:48, Draic Kin wrote:
For the same reason, I could think that isinstance(Decimal, Rational) -> True
If Decimal were a subclass of Rational, so should float. The only fundamental difference between the two is that one uses base 10 floating point numbers and the other uses base 2.
Another difference is, that precision of float is fixly limited. I actually thought that Decimal is unlimited the same way as int, however decimal.MAX_PREC is pretty big number. But you're right Decimal shouldn't be subclass of Rational. However the original question was why it is not subclass of Real.
and issubclass(Rational, Real) -> False. It's more about exact vs.
non-exact computations which is orthogonal to number hierarchy.
The numeric tower is precisely about the numeric hierarchy of Number > Complex > Real > Rational > Integral, and since they are all *abstract* base classes, exact and inexact doesn't come into it. Concrete classes can be inexact or exact, or one could implement separate Exact and Inexact towers.
In practice, it's hard to think of a concrete way to implement exact real
numbers. Maybe a symbolic maths application like Mathematica comes close?
Maybe there
should be some ExactNumber abstract base class and some convention that exact shouldn't coerce with non-exact. So Decimal + float should raise an exception even if both would be subclasses of Real (and Decimal even of Rational). Or maybe it would be enough if there were just non-exact variants of Real and Complex since non-exactness if just issue of them.
Personally, I would implement inexact/exact as an attribute on the type:
if type(x).exact: ...
sort of thing.
There were some points (possible against the idea of issubclass(Decimal, Real) -> True) like Decimal doesn't coerce to float, you cannot convert Fraction to Decimal easily, you cannot sum Fraction + Decimal. Maybe some of the rationale for the behavior is the matter of exact vs. non-exact. So for that reason the exactness rationale could be made explicit by adding some indicator of exacness and base some coercion cases on this indicator.
On 29 August 2013 09:13, Draic Kin
On Thu, Aug 29, 2013 at 4:02 AM, Steven D'Aprano
wrote: On 28/08/13 20:48, Draic Kin wrote:
For the same reason, I could think that isinstance(Decimal, Rational) -> True
If Decimal were a subclass of Rational, so should float. The only fundamental difference between the two is that one uses base 10 floating point numbers and the other uses base 2.
Another difference is, that precision of float is fixly limited. I actually thought that Decimal is unlimited the same way as int, however decimal.MAX_PREC is pretty big number. But you're right Decimal shouldn't be subclass of Rational. However the original question was why it is not subclass of Real.
The precision of Decimal in arithmetic is fixed and usually much smaller than MAX_PREC which is simply the maximum value it can be set to. The default is 28 decimal digits of precision: $ python3 Python 3.3.2 (v3.3.2:d047928ae3f6, May 16 2013, 00:03:43) [MSC v.1600 32 bit (Intel)] on win32 Type "help", "copyright", "credits" or "license" for more information.
from decimal import Decimal import decimal print(decimal.getcontext()) Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, DivisionByZero, Overflow]) decimal.Decimal(1) / 3 Decimal('0.3333333333333333333333333333') decimal.MAX_PREC 425000000
The context says prec=28 and that Decimal(1)/3 gives a result with 28 threes. Decimals are exact in the sense that conversion to Decimal from int, str or float is guaranteed to be exact. Note that this is not required by the standards on which it is based. The standards suggest that conversion from a supported integer type or from a string should be exact *if possible*. The reason for the "if possible" caveat is that not every implementation of the standards would be able to create arbitrary precision Decimals and in fact many would be limited to a smaller precision than 28 (e.g. decimal hardware in hand calculators etc.). The standards only require that inexact conversions should set the Inexact flag and - if the Inexact trap is set - raise the Inexact exception. It's important to be clear about the distinction between the precision of a Decimal *instance* and the precision of the current *arithmetic context*. While it is possible to exactly convert an int/str/float to a Decimal with a precision that is higher than the current context, any arithmetic operations will be rounded to the context precision according to the context rounding mode (there are 8 different rounding modes and precision is any positive integer). This arithmetic rounding is actually *required* by the IEEE-854 standard unlike the exact conversion from arbitrary precision integers etc. Specifically the standard requires that the result be (effectively) computed exactly and then rounded according to context. This means that individual binary arithmetic operations can behave as if they have a precision that is higher than the current context but as soon as you try to e.g. sum 3 numbers you should assume that you're effectively working with context precision. An example:
d1 = decimal.Decimal('1'*40 + '2') d2 = decimal.Decimal('-'+ '1'*41) d1 Decimal('11111111111111111111111111111111111111112') d2 Decimal('-11111111111111111111111111111111111111111') d1 + d2 # Computed exact and then rounded (no rounding occurs) Decimal('1') (+d1) + (+d2) # Rounded, computed and rounded again Decimal('0E+13') d1 + 0 + d2 # What happens here? Decimal('-1111111111111')
For this reason sum(Decimals) is just as inaccurate as sum(floats) and Decimals need a decimalsum function just like float's fsum. My first attempt at such a function was the following (all code below was modified for posting and is untested): # My simplification of the algorithms from # "Algorithms for Arbitrary Precision Floating Point Arithmetic" # by Douglas M. Priest 1991. # http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.55.3546 # # This function is my own modification of Raymond Hettinger's recipe to use # the more general sum_err function from the Priest paper and perform the # final summation with Fractions def fixedwidthsum(iterable): "Full precision summation for fixed-width floating point types" partials = [] iterator = iter(iterable) isfinite = math.isfinite for x in iterable: # Handle NaN/Inf if not isfinite(x): return sum(iterator, x) i = 0 for y in partials: if abs(x) < abs(y): x, y = y, x hi, lo = sum_err(x, y) # The key modification if lo: partials[i] = lo i += 1 x = hi partials[i:] = [x] # Also modified: used fractions to add the partials if not partials: return 0 elif isinstance(partials[0], Decimal): # This is needed because Decimal(Fraction) doesn't work fresult = sum(map(Fraction, partials)) # Assumes Python 3.3 return Decimal(fresult.numerator) / Decimal(fresult.denominator) else: return type(partials[0])(sum(map(Fraction, partials))) def sum_err(a, b): if abs(a) < abs(b): a, b = b, a c = a + b; e = c - a # Standard Kahan # The line below is needed unless the arithmetic is # faithful-binary, properly-truncating or correctly-chopping g = c - e; h = g - a; f = b - h d = f - e # For Kahan replace f with b # The two lines below are needed unless the arithmetic # uses round-to-nearest or proper-truncation if d + e != f: c, d = a, b return c, d The functions above can exactly sum any fixed precision floating point type of any radix (including decimal) under any sensible rounding mode including all the rounding modes in the decimal module. The problem with it though is that Decimals are almost but not quite a fixed precision type: it is possible to create Decimals whose precision exceeds that of the arithmetic context (as in the examples I showed above). If the instance is precision does not exceed twice the arithmetic context then we can decompose the decimal into two numbers each of which has a precision within the current context e.g.: def expand_two(d): if d == +d: # Does d equal itself after rounding return [d] else: return [+d, d-(+d)]
decimal.getcontext().prec=4 d1 = decimal.Decimal('1234567') Decimal('1234567') [+d1, d1-(+d1)] [Decimal('1.235E+6'), Decimal('-433')]
However once we go to more than twice the context precision there's no duck-typey way to do it: We need to know the instance precision and the context precision and we're better off ripping out the internal Decimal representation than trying to use arithmetic: def expand_full(d): if d == +d: return [d] prec = decimal.getcontext().prec sign, digits, exponent = d.as_tuple() expansion = [] while digits: digits, lodigits = digits[:-prec], digits[-prec:] expansion.append(decimal.Decimal((sign, lodigits, exponent))) exponent += prec return expansion And that should do it. The above functions are jumping through the same kind of hoops that fsum does precisely because Decimals are a floating point type (not exact) based on the IEEE-854 "Standard for radix-independent *floating-point* arithmetic". Oscar
On 29 August 2013 09:13, Draic Kin
On Thu, Aug 29, 2013 at 4:02 AM, Steven D'Aprano
wrote: On 28/08/13 20:48, Draic Kin wrote:
and issubclass(Rational, Real) -> False. It's more about exact vs. non-exact computations which is orthogonal to number hierarchy.
The numeric tower is precisely about the numeric hierarchy of Number > Complex > Real > Rational > Integral, and since they are all *abstract* base classes, exact and inexact doesn't come into it. Concrete classes can be inexact or exact, or one could implement separate Exact and Inexact towers.
In practice, it's hard to think of a concrete way to implement exact real numbers. Maybe a symbolic maths application like Mathematica comes close?
Yes, Mathematica and more pertinently sympy implement exact real numbers. All fixed- or floating-point number formats are restricted to representing rational numbers. The obvious examples of exact irrational numbers are things like pi, e, sqrt(2) etc. Sympy can represent these exactly and guarantee that sqrt(n)**2 is exactly n for any integer n.
Maybe there should be some ExactNumber abstract base class and some convention that exact shouldn't coerce with non-exact. So Decimal + float should raise an exception even if both would be subclasses of Real (and Decimal even of Rational). Or maybe it would be enough if there were just non-exact variants of Real and Complex since non-exactness if just issue of them.
Personally, I would implement inexact/exact as an attribute on the type:
if type(x).exact: ...
sort of thing.
Exactness is more complicated than that. Whether or not operation(a, b) is exact depends on: 1) the operation 2) the types of a AND b 3) the values of a and b For example if both operands are ints then results are exact for addition, subtraction, multiplication, and sometimes for division. Exponentiation is exact where the exponent is a non-negative integer but not if the exponent is negative. Fractions are exact for division also (except by 0) and for exponentiation where the exponent is any integer but not if the exponent is a non-integer valued Fraction (even if exact results are possible). int(num) is not exact unless num is an integer. Fraction(num) is always exact (or an error). The only senses in which Decimals are exact are that Decimal(str, Decimal(int) and Decimal(float) are exact and - if you set the Inexact trap - you can get an error any time something inexact would have otherwise occurred. (Well you could say that decimals are "exactly rounded" but that's not what we mean by exact here).
There were some points (possible against the idea of issubclass(Decimal, Real) -> True) like Decimal doesn't coerce to float,
Thinking about it now I would rather that float/Decimal operations coerce to Decimal and a FloatOperation error to be raised if set (by someone who doesn't want to mix Decimals and floats).
you cannot convert Fraction to Decimal easily, you cannot sum Fraction + Decimal.
These are just missing features IMO. Presumably if Decimal had been integrated into the numbers hierarchy these would have been added.
Maybe some of the rationale for the behavior is the matter of exact vs. non-exact.
Decimals are not exact!
So for that reason the exactness rationale could be made explicit by adding some indicator of exacness and base some coercion cases on this indicator.
The coercion may be exact but the subsequent arithmetic operations are not. Preventing mixed Fraction/Decimal arithmetic does not save you rounding error:
d = decimal.Decimal('.1234') f = fractions.Fraction('1/3') d Decimal('0.1234') f Fraction(1, 3) d * f Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for *: 'decimal.Decimal' and 'Fraction'
Well it's a good thing that type coercion saved us from being able to get rounding errors. Hang on...
d / 3 Decimal('0.04113333333333333333333333333')
That's the exact same rounding error we would have got with the Fraction! Any Decimal whose precision exceeds the current context precision will not have exact arithmetic operations (for any operation):
d = decimal.Decimal(11**30) (d/3)*3 == d False
Oscar
On Thu, Aug 29, 2013 at 3:19 PM, Oscar Benjamin
On Thu, Aug 29, 2013 at 4:02 AM, Steven D'Aprano
wrote: On 28/08/13 20:48, Draic Kin wrote:
and issubclass(Rational, Real) -> False. It's more about exact vs. non-exact computations which is orthogonal to number hierarchy.
The numeric tower is precisely about the numeric hierarchy of Number > Complex > Real > Rational > Integral, and since they are all *abstract*
On 29 August 2013 09:13, Draic Kin
wrote: base classes, exact and inexact doesn't come into it. Concrete classes can be inexact or exact, or one could implement separate Exact and Inexact towers.
In practice, it's hard to think of a concrete way to implement exact real numbers. Maybe a symbolic maths application like Mathematica comes close?
Yes, Mathematica and more pertinently sympy implement exact real numbers. All fixed- or floating-point number formats are restricted to representing rational numbers. The obvious examples of exact irrational numbers are things like pi, e, sqrt(2) etc. Sympy can represent these exactly and guarantee that sqrt(n)**2 is exactly n for any integer n.
Maybe there should be some ExactNumber abstract base class and some convention that exact shouldn't coerce with non-exact. So Decimal + float should raise an exception even if both would be subclasses of Real (and Decimal even of Rational). Or maybe it would be enough if there were just non-exact variants of Real and Complex since non-exactness if just issue of them.
Personally, I would implement inexact/exact as an attribute on the type:
if type(x).exact: ...
sort of thing.
Exactness is more complicated than that. Whether or not operation(a, b) is exact depends on: 1) the operation 2) the types of a AND b 3) the values of a and b
For example if both operands are ints then results are exact for addition, subtraction, multiplication, and sometimes for division. Exponentiation is exact where the exponent is a non-negative integer but not if the exponent is negative. Fractions are exact for division also (except by 0) and for exponentiation where the exponent is any integer but not if the exponent is a non-integer valued Fraction (even if exact results are possible). int(num) is not exact unless num is an integer. Fraction(num) is always exact (or an error). The only senses in which Decimals are exact are that Decimal(str, Decimal(int) and Decimal(float) are exact and - if you set the Inexact trap - you can get an error any time something inexact would have otherwise occurred. (Well you could say that decimals are "exactly rounded" but that's not what we mean by exact here).
There were some points (possible against the idea of issubclass(Decimal, Real) -> True) like Decimal doesn't coerce to float,
Thinking about it now I would rather that float/Decimal operations coerce to Decimal and a FloatOperation error to be raised if set (by someone who doesn't want to mix Decimals and floats).
you cannot convert Fraction to Decimal easily, you cannot sum Fraction + Decimal.
These are just missing features IMO. Presumably if Decimal had been integrated into the numbers hierarchy these would have been added.
To answer your original question, according to comments in sources of decimal.py and numbers.py, Decimal shouldn't be subclass of Real since it doesn't interoperate with floats and different subclasses of Real should interoperate. From my point of view, if floats weren't more common that decimals, one could turn the same argument around: Decimal subclasses Real but float doesn't since it doesn't interoperate with Decimal. Maybe they should interoperate and as you pointed out, Decimal is more robust in handling errors so maybe float + Decimal should yield Decimal. Then Decimal could be integrated to the number hierarchy.
Maybe there is still this problem: what would Decimal + complex return?
Maybe some of
the rationale for the behavior is the matter of exact vs. non-exact.
Decimals are not exact!
Got it now :-). I was thinking of Decimals as if they had always MAX_PREC set.
So for that reason the exactness rationale could be made explicit by adding some indicator of exacness and base some coercion cases on this indicator.
The coercion may be exact but the subsequent arithmetic operations are not. Preventing mixed Fraction/Decimal arithmetic does not save you rounding error:
d = decimal.Decimal('.1234') f = fractions.Fraction('1/3') d Decimal('0.1234') f Fraction(1, 3) d * f Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for *: 'decimal.Decimal' and 'Fraction'
Well it's a good thing that type coercion saved us from being able to get rounding errors. Hang on...
d / 3 Decimal('0.04113333333333333333333333333')
That's the exact same rounding error we would have got with the Fraction!
Any Decimal whose precision exceeds the current context precision will not have exact arithmetic operations (for any operation):
d = decimal.Decimal(11**30) (d/3)*3 == d False
Oscar
On 29 August 2013 15:06, Draic Kin
To answer your original question, according to comments in sources of decimal.py and numbers.py, Decimal shouldn't be subclass of Real since it doesn't interoperate with floats and different subclasses of Real should interoperate. From my point of view, if floats weren't more common that decimals, one could turn the same argument around: Decimal subclasses Real but float doesn't since it doesn't interoperate with Decimal. Maybe they should interoperate and as you pointed out, Decimal is more robust in handling errors so maybe float + Decimal should yield Decimal. Then Decimal could be integrated to the number hierarchy.
At least the explicit Decimal(Fraction) should work (setting the Inexact flag as necessary). Decimal * Fraction etc. should also work. It's not trivial to ensure that e.g. Decimal*Fraction gives a correctly rounded Decimal result.
Maybe there is still this problem: what would Decimal + complex return?
I guess it would have to be a complex. It would be good if there were a ComplexDecimal but it's not so important as getting the real numbers right. Oscar
On Thu, Aug 29, 2013 at 3:02 AM, Steven D'Aprano
On 28/08/13 20:48, Draic Kin wrote:
For the same reason, I could think that isinstance(Decimal, Rational) -> True
If Decimal were a subclass of Rational, so should float. The only fundamental difference between the two is that one uses base 10 floating point numbers and the other uses base 2.
Exactly, yes! The "Decimal is a fixed-point type" and "Decimal is exact" myths seem to be unfortunately common. Mark
On 30 August 2013 16:03, Mark Dickinson
On Thu, Aug 29, 2013 at 3:02 AM, Steven D'Aprano
wrote: On 28/08/13 20:48, Draic Kin wrote:
For the same reason, I could think that isinstance(Decimal, Rational) -> True
If Decimal were a subclass of Rational, so should float. The only fundamental difference between the two is that one uses base 10 floating point numbers and the other uses base 2.
Exactly, yes! The "Decimal is a fixed-point type" and "Decimal is exact" myths seem to be unfortunately common.
I should say that this actually arises from the properties of the decimal module that are *not* from the standards. For example the idea that conversion to Decimal from string or integer is exact is documented in the decimal module but the standards do not require this. The only part that is standard is really that inexact conversions should trigger the Inexact exception. Similarly the IEEE-854 standard requires single precision, double precision and optionally two implementation dependent extended precisions. The Decimal Arithmetic Specification only explicitly requires a basic context with 9-digit precision. There's nowhere that says it should support absurdly high precision (not that that's a bad thing). Also about conversion from string to decimal the arithmetic specification says: ''' A numeric string to finite number conversion is always exact unless there is an underflow or overflow (see below) or the number of digits in the decimal-part of the string is greater than the precision in the context. In this latter case the coefficient will be rounded (shortened) to exactly precision digits, using the rounding algorithm, and the exponent is increased by the number of digits removed. The rounded and other flags may be set, as if an arithmetic operation had taken place (see below). ''' http://speleotrove.com/decimal/daconvs.html My interpretation of the above is that Decimal(str) should round according to the precision of the current context rather than return an exact result as the decimal module does. Conversion to/from binary float or fractions or arithmetic interoperation with other types are not defined in the standards. It seems that some people really don't want to mix Decimal and float (not that it does actually protect you from rounding error) which is reasonable. However the FloatOperation trap (which is not part of the standard) already exists for this case. Similarly for Fractions in the cases where mixing Fractions and Decimals would lead to an inexact result the Inexact trap exists for people who would want to control this. I think that many aspects of the current behaviour are useful but it should not be confused with being a strict interpretation of any standard. For example the exact int/float to Decimal conversion enables me to easily and efficiently compute the exact sum of any mix of ints, floats and Decimals: def decimalsum(iterable): '''Exact sum of Decimal/int/float mix; Result is *unrounded*''' # We need our own context and we can't just set it once because # the loop could be over a generator/iterator/coroutine ctx = getcontext().copy() ctx.traps[Inexact] = True one = Decimal(1) total = Decimal(0) for x in iterable: if not isinstance(x, Decimal): if isinstance(x, (int, float)): x = Decimal(x) else: raise TypeError # Handle NaN/Inf if not x.is_finite(): return sum(map(Decimal, iterable), x) # Increase the precision until we get an exact result. # Accepting Fractions could turn this into an infinite loop... while True: try: total = total.fma(one, x, ctx) break except Inexact: ctx.prec *= 2 # Using +total on the line below rounds to context. return total # Unrounded! But this really illustrates how you are supposed to get exact results with decimals which is by controlling the context precision and manipulating the Inexact trap. If conversion from int/float were not guaranteed to be exact I would just need to move the conversion above into the while loop. As long as you're doing any arithmetic exact conversion does not guarantee exact results (and if you're not doing arithmetic then what's the point of Decimal?). Oscar
On Wed, Aug 28, 2013, at 6:30, Vernon D. Cole wrote:
Darn right it should return False. Given the principle of least surprise (and my prejudices built up over 40 years as a computer programmer) I would expect that decimal.Decimal data would be stored internally as some form of decimal data, and would store into a database as such. It would be expected to be in a fixed point format. Real, on the other hand, I would expect to be stored as an IEEE double precision floating point number, or something like that. I don't care whether a fixed point decimal number might be defined by a mathematician as "real" -- I care whether it can be processed by an FPU, and whether it will loose precision in large financial calculations.
That's not what Real means, and it's not, for example, true of Fraction. The type you're thinking of is called "float", and the fact that Fortran calls it "real" does not obligate numbers.Real to have the same meaning, no more than the fact that C uses "float" to refer to single precision obligates us to use that term. There's a difference between "principle of least surprise" and arbitrarily importing terminology from an unrelated language. Also, Decimal is a floating point format - a _decimal_, arbitrary-precision, floating point format rather than a binary one. Fixed point means there's a hard limit to the precision (e.g. can never represent anything finer than 1/100000) and a hard limit to the range.
On 28/08/13 20:30, Vernon D. Cole wrote:
Darn right it should return False. Given the principle of least surprise (and my prejudices built up over 40 years as a computer programmer) I would expect that decimal.Decimal data would be stored internally as some form of decimal data, and would store into a database as such.
"Some form of decimal data" -- so you're saying "Decimals are decimals". Well, that certainly clarifies matters :-) Out of curiosity, those 40 years as a computer programmer, how much heavy-duty computational mathematics have you done? If you've spent 30 years writing business apps in COBOL and 10 years writing web apps in PHP, I wouldn't expect your prejudices about numeric computations to be trustworthy. (I know mine aren't, and I've spent years dabbling with numeric maths. The only reason I'm not surprised by computational mathematics is because I've learned not to trust my assumptions.)
It would be expected to be in a fixed point format.
Decimal is a floating point format: http://docs.python.org/2/library/decimal.html so if you are assuming that Decimal is a fixed point number, your prejudices are wrong. (Although I think the claims about exactness are misleading. There are plenty of numbers which cannot be represented exactly as decimals, just as they cannot be represented exactly as binary floats. 1/3 is the obvious example. Both float and Decimal suffer from the same inexactness issues, it's just that they sometimes suffer from them for different values.)
Real, on the other hand, I would expect to be stored as an IEEE double precision floating point number, or something like that. I don't care whether a fixed point decimal number might be defined by a mathematician as "real" -- I care whether it can be processed by an FPU, and whether it will loose precision in large financial calculations.
Membership of number.Real has little to do with what mathematicians consider real numbers. No floating point or fixed point implementation obeys the rules of real number arithmetic. But it's the traditional name, I expect due to the precedent set by Fortran. -- Steven
participants (6)
-
Draic Kin
-
Mark Dickinson
-
Oscar Benjamin
-
random832@fastmail.us
-
Steven D'Aprano
-
Vernon D. Cole