[Python-ideas] isinstance(Decimal(), Real) -> False?

Oscar Benjamin oscar.j.benjamin at gmail.com
Fri Aug 30 17:45:34 CEST 2013


On 30 August 2013 16:03, Mark Dickinson <dickinsm at gmail.com> wrote:
> On Thu, Aug 29, 2013 at 3:02 AM, Steven D'Aprano <steve at pearwood.info>
> 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


More information about the Python-ideas mailing list