drop trailing zeros from decimal

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

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

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')

Method 2

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

Method 3

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')

Method 4

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.

Method 5

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'

Method 6

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

Method 7

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

Method 8

This should work:

'{:f}'.format(decimal.Decimal('2.5') * 10).rstrip('0').rstrip('.')

Method 9

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))

Method 10

You could use %g to achieve this:

‘%g’%(3.140)

with Python ? 2.6:
‘{0:g}’.format(3.140)

Method 11

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


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x