drop trailing zeros from decimal

Question:

I have a long list of Decimals and that I have to adjust by factors of 10, 100, 1000,….. 1000000 depending on certain conditions. When I multiply them there is sometimes a useless trailing zero (though not always) that I want to get rid of. For example…

from decimal import Decimal

# outputs 25.0,  PROBLEM!  I would like it to output 25
print Decimal('2.5') * 10

# outputs 2567.8000, PROBLEM!  I would like it to output 2567.8
print Decimal('2.5678') * 1000

Is there a function that tells the decimal object to drop these insignificant zeros? The only way I can think of doing this is to convert to a string and replace them using regular expressions.

Should probably mention that I am using python 2.6.5

EDIT
senderle’s fine answer made me realize that I occasionally get a number like 250.0 which when normalized produces 2.5E+2. I guess in these cases I could try to sort them out and convert to a int

Asked By: b10hazard

||

Answers:

You can use the normalize method to remove extra precision.

>>> print decimal.Decimal('5.500')
5.500
>>> print decimal.Decimal('5.500').normalize()
5.5

To avoid stripping zeros to the left of the decimal point, you could do this:

def normalize_fraction(d):
    normalized = d.normalize()
    sign, digits, exponent = normalized.as_tuple()
    if exponent > 0:
        return decimal.Decimal((sign, digits + (0,) * exponent, 0))
    else:
        return normalized

Or more compactly, using quantize as suggested by user7116:

def normalize_fraction(d):
    normalized = d.normalize()
    sign, digit, exponent = normalized.as_tuple()
    return normalized if exponent <= 0 else normalized.quantize(1)

You could also use to_integral() as shown here but I think using as_tuple this way is more self-documenting.

I tested these both against a few cases; please leave a comment if you find something that doesn’t work.

>>> normalize_fraction(decimal.Decimal('55.5'))
Decimal('55.5')
>>> normalize_fraction(decimal.Decimal('55.500'))
Decimal('55.5')
>>> normalize_fraction(decimal.Decimal('55500'))
Decimal('55500')
>>> normalize_fraction(decimal.Decimal('555E2'))
Decimal('55500')
Answered By: senderle

There’s probably a better way of doing this, but you could use .rstrip('0').rstrip('.') to achieve the result that you want.

Using your numbers as an example:

>>> s = str(Decimal('2.5') * 10)
>>> print s.rstrip('0').rstrip('.') if '.' in s else s
25
>>> s = str(Decimal('2.5678') * 1000)
>>> print s.rstrip('0').rstrip('.') if '.' in s else s
2567.8

And here’s the fix for the problem that @gerrit pointed out in the comments:

>>> s = str(Decimal('1500'))
>>> print s.rstrip('0').rstrip('.') if '.' in s else s
1500
Answered By: R Hyde

This should work:

'{:f}'.format(decimal.Decimal('2.5') * 10).rstrip('0').rstrip('.')
Answered By: Jonas Kölker

Answer from the Decimal FAQ in the documentation:

>>> def remove_exponent(d):
...     return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize()

>>> remove_exponent(Decimal('5.00'))
Decimal('5')

>>> remove_exponent(Decimal('5.500'))
Decimal('5.5')

>>> remove_exponent(Decimal('5E+3'))
Decimal('5000')
Answered By: A-IV

I ended up doing this:

import decimal

def dropzeros(number):
    mynum = decimal.Decimal(number).normalize()
    # e.g 22000 --> Decimal('2.2E+4')
    return mynum.__trunc__() if not mynum % 1 else float(mynum)

print dropzeros(22000.000)
22000
print dropzeros(2567.8000)
2567.8

note: casting the return value as a string will limit you to 12 significant digits

Answered By: samstav

Why not use modules 10 from a multiple of 10 to check if there is remainder? No remainder means you can force int()

if (x * 10) % 10 == 0:
    x = int(x)

x = 2/1
Output: 2

x = 3/2
Output: 1.5

Answered By: Fong Kah Chun

Use the format specifier %g. It seems remove to trailing zeros.

>>> "%g" % (Decimal('2.5') * 10)
'25'
>>> "%g" % (Decimal('2.5678') * 1000)
'2567.8'

It also works without the Decimal function

>>> "%g" % (2.5 * 10)
'25'
>>> "%g" % (2.5678 * 1000)
'2567.8'
Answered By: Friedrich

Answer is mentioned in FAQ (https://docs.python.org/2/library/decimal.html#decimal-faq) but does not explain things.

To drop trailing zeros for fraction part you should use normalize:

>>> Decimal('100.2000').normalize()
Decimal('100.2')
>> Decimal('0.2000').normalize()
Decimal('0.2')

But this works different for numbers with leading zeros in sharp part:

>>> Decimal('100.0000').normalize()
Decimal('1E+2')

In this case we should use `to_integral’:

>>> Decimal('100.000').to_integral()
Decimal('100')

So we could check if there’s a fraction part:

>>> Decimal('100.2000') == Decimal('100.2000').to_integral()
False
>>> Decimal('100.0000') == Decimal('100.0000').to_integral()
True

And use appropriate method then:

def remove_exponent(num):
    return num.to_integral() if num == num.to_integral() else num.normalize()

Try it:

>>> remove_exponent(Decimal('100.2000'))
Decimal('100.2')
>>> remove_exponent(Decimal('100.0000'))
Decimal('100')
>>> remove_exponent(Decimal('0.2000'))
Decimal('0.2')

Now we’re done.

Answered By: Artem Skoretskiy

Slightly modified version of A-IV’s answer

NOTE that Decimal('0.99999999999999999999999999995').normalize() will round to Decimal('1')

def trailing(s: str, char="0"):
    return len(s) - len(s.rstrip(char))

def decimal_to_str(value: decimal.Decimal):
    """Convert decimal to str

    * Uses exponential notation when there are more than 4 trailing zeros
    * Handles decimal.InvalidOperation
    """
    # to_integral_value() removes decimals
    if value == value.to_integral_value():
        try:
            value = value.quantize(decimal.Decimal(1))
        except decimal.InvalidOperation:
            pass
        uncast = str(value)
        # use exponential notation if there are more that 4 zeros
        return str(value.normalize()) if trailing(uncast) > 4 else uncast
    else:
        # normalize values with decimal places
        return str(value.normalize())
        # or str(value).rstrip('0') if rounding edgecases are a concern

Answered By: micimize

Just to show a different possibility, I used to_tuple() to achieve the same result.

def my_normalize(dec):
    """
    >>> my_normalize(Decimal("12.500"))
    Decimal('12.5')
    >>> my_normalize(Decimal("-0.12500"))
    Decimal('-0.125')
    >>> my_normalize(Decimal("0.125"))
    Decimal('0.125')
    >>> my_normalize(Decimal("0.00125"))
    Decimal('0.00125')
    >>> my_normalize(Decimal("125.00"))
    Decimal('125')
    >>> my_normalize(Decimal("12500"))
    Decimal('12500')
    >>> my_normalize(Decimal("0.000"))
    Decimal('0')
    """
    if dec is None:
        return None

    sign, digs, exp = dec.as_tuple()
    for i in list(reversed(digs)):
        if exp >= 0 or i != 0:
            break
        exp += 1
        digs = digs[:-1]

    if not digs and exp < 0:
        exp = 0

    return Decimal((sign, digs, exp))
Answered By: cxxl

You could use :g to achieve this:

'{:g}'.format(3.140)

gives

'3.14'
Answered By: Fred
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.