On Thu, 8 Oct 2020 at 16:59, Random832 <random...@fastmail.com> wrote:
>
> I was making a "convert Fraction to Decimal, exactly if possible" function 
> and ran into a wall: it's not possible to do some of the necessary operations 
> with exact precision in decimal:
>
> - multiplication
> - division where the result can be represented exactly [the divisor is an 
> integer whose prime factors are only two and five, or a rational number whose 
> numerator qualifies]
>
> I assume there's some way around it that I haven't spent enough time to 
> figure out [create a temporary context with sufficint digits for 
> multiplication, and work out the reciprocal power of 10 by hand to use this 
> multiplication to implement division], but I feel like these exact operations 
> should be supported in the standard library.

It should be possible to do this by bounding the number of digits
required for an exact operation. We can count the number of digits in
the mantissa of a Decimal like this:

In [100]: num_digits = lambda d: len(d.as_tuple().digits)

In [101]: num_digits(Decimal('12.34'))
Out[101]: 4

If d3 = d1 * d2 then num_digits(d3) <= num_digits(d1) + num_digits(d2)
so we can easily bound the number of digits needed for an exact
multiplication. We can create a context for a number of digits and use
it for multiplication like so:

In [106]: ctx = getcontext().copy()

In [107]: ctx.prec = 4

In [108]: ctx.multiply(Decimal('991'), Decimal('12'))
Out[108]: Decimal('1.189E+4')

So then the result without rounding is:

In [109]: d2 = Decimal('991')

In [110]: d3 = Decimal('12')

In [111]: ctx.prec = num_digits(d2) + num_digits(d3)

In [112]: ctx.multiply(Decimal('991'), Decimal('12'))
Out[112]: Decimal('11892')

For division it is a little more complicated. You say the division is
exact so for some nonnegative integer k and integer n the divisor is
either:

a) 2**k * 10**n
b) 5**k * 10**n

Dividing/multiplying by 10 does not require any increase in precision
but dividing by 2 or 5 requires at most 1 extra digit so:

In [114]: d = Decimal('123.4')

In [115]: ctx.prec = num_digits(d) + 1

In [116]: ctx.divide(d, 2)
Out[116]: Decimal('61.7')

Of course to make that usable you'll need to calculate k.

Also you'll want to set the Inexact trap:

In [119]: ctx.traps[Inexact] = True

In [120]: ctx.divide(d, 3)
---------------------------------------------------------------------------
Inexact                                   Traceback (most recent call last)
<ipython-input-120-9e7694c7c9ce> in <module>
----> 1 ctx.divide(d, 3)

Inexact: [<class 'decimal.Inexact'>]

For smallish Decimals it will probably be faster to convert to
Rational but for sufficiently large exponents Rational will be very
slow.

--
Oscar
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/K7NHDVO34TNGFQUJQ2CFNWGKMFPYPDNE/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to