1

How can you scale a Decimal, but keep its original precision? E.g.,

>>> from decimal import Decimal
>>> Decimal('0.1230')
Decimal('0.1230') # Precision is 4.

Multiplication does not preserve the original precision:

>>> Decimal('0.1230') * 100
Decimal('12.3000') #  Precision is 6.
>>> Decimal('0.1230') * Decimal('100')
Decimal('12.3000') # Precision is 6.

The .shift() method only keeps the same number of decimal places when shifting by a negative amount:

>>> Decimal('0.1230').shift(2)
Decimal('12.3000') # Precision is 6.
>>> Decimal('0.1230').shift(-2)
Decimal('0.0012') # Precision is 2.
Uyghur Lives Matter
  • 18,820
  • 42
  • 108
  • 144

3 Answers3

1

Use setcontext:

>>> mycontext=decimal.Context(prec=4)
>>> decimal.setcontext(mycontext)
>>> decimal.Decimal('0.1230')*decimal.Decimal('100')
Decimal('12.30')

If you want something that feels more local, use localcontext and a context:

import decimal as dec

with dec.localcontext() as ctx:
    ctx.prec=4
    d1=dec.Decimal('0.1230')*100

d2=dec.Decimal('0.1230')*100

>>> d1, d2
12.30 12.3000

If you want something where the lesser precision representation (with no decimal integers use a default) you can do something like:

def muld(s1, s2, dprec=40):
    with dec.localcontext() as ctx:
        d1=dec.Decimal(s1)
        d2=dec.Decimal(s2)
        p1=dprec if '.' not in s1 else len(d1.as_tuple().digits)
        p2=dprec if '.' not in s2 else len(d2.as_tuple().digits)
        ctx.prec=min(p1, p2)
        return d1*d2

>>> muld('0.1230', '100')
12.30
>>> muld('0.1230', '1.0')   
0.12
dawg
  • 98,345
  • 23
  • 131
  • 206
0

From the answer, https://stackoverflow.com/a/33948596/47078, you could do something like the following. I'm not sure, though, if what you want is possible without additional code: namely to have a Decimal that preserves precision automatically.

>>> import decimal
>>> f = decimal.Context(prec=4).create_decimal('0.1230')
>>> f
Decimal('0.1230')
>>> decimal.Context(prec=4).create_decimal(f * 100)
Decimal('12.30')

You may want to write your own class or helper methods to make it look nicer.

>>> Decimal4 = decimal.Context(prec=4).create_decimal
>>> Decimal4('0.1230')
Decimal('0.1230')
>>> Decimal4(Decimal4('0.1230') * 100)
Decimal('12.30')

Looks like (from decimal.Context.create_decimal) you can also do it this way:

# This is copied verbatim from the Python documentation
>>> getcontext().prec = 3
>>> Decimal('3.4445') + Decimal('1.0023')
Decimal('4.45')
>>> Decimal('3.4445') + Decimal(0) + Decimal('1.0023')

Perhaps you could create a method to replace Decimal() that creates the Decimal object, and updates the global precision if the current precision is less than the current.

>>> def make_decimal(value):
...     number = decimal.Decimal(value)
...     prec = len(number.as_tuple().digits)
...     if prec < decimal.getcontext().prec:
...         decimal.getcontext().prec = prec
...     return number
...
>>> make_decimal('0.1230')
Decimal('0.1230')
>>> make_decimal('0.1230') * 100
Decimal('12.30')

You would then want a reset_decimal_precision method or an optional parameter to make_decimal to do the same so you could start new calculations. The downside is that it limits you to only one global precision at a time. I think the best solution might be to subclass Decimal() and make it keep track of precision.

Community
  • 1
  • 1
Harvey
  • 5,703
  • 1
  • 32
  • 41
  • I was hoping to avoid explicitly specifying the precision, but maybe this is the only solution. I would end up with something like: `number = decimal.Decimal('0.1230'); decimal.Context(prec=number.as_tuple().digits).create_decimal(number * 100)` – Uyghur Lives Matter Oct 07 '16 at 15:00
0

The .scaleb() method will scale a decimal number while preserving its precision. From the documentation:

scaleb(other[, context])

Return the first operand with exponent adjusted by the second.

The key part is that the exponent gets adjusted and thus maintains precision.

>>> Decimal('0.1230').scaleb(2)
Decimal('12.30') # Precision is 4.
>>> Decimal('0.1230').scaleb(-2)
Decimal('0.001230') # Precision is 4.
Uyghur Lives Matter
  • 18,820
  • 42
  • 108
  • 144