【问题标题】:drop trailing zeros from decimal从十进制中删除尾随零
【发布时间】:2012-06-28 23:14:30
【问题描述】:

我有一长串小数,我必须根据特定条件按 10、100、1000、..... 1000000 的因子进行调整。当我将它们相乘时,有时我想去掉一个无用的尾随零(尽管并非总是如此)。比如……

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

是否有一个函数可以告诉十进制对象删除这些无关紧要的零?我能想到的唯一方法是转换为字符串并使用正则表达式替换它们。

可能应该提到我使用的是 python 2.6.5

编辑 senderle 的好回答让我意识到我偶尔会得到一个像 250.0 这样的数字,标准化后会产生 2.5E+2。我想在这些情况下,我可以尝试将它们整理出来并转换为 int

【问题讨论】:

  • 那些尾随零被保留,因为它们代表产生值的表达式的最大精度(这被认为对 Decimal 类型很重要)。

标签: python decimal


【解决方案1】:

您可以使用normalize 方法去除额外的精度。

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

为避免去除小数点左侧的零,您可以这样做:

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

或者更简洁,使用user7116建议的quantize

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

您也可以使用to_integral(),如here 所示,但我认为以这种方式使用as_tuple 更能自我记录。

我在几个案例中对这两种情况进行了测试;如果您发现某些内容不起作用,请发表评论。

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

【讨论】:

  • 哦,那行得通。但是我注意到在 250.0 这样的东西上,这会将其转换为 2.5e+02,而不是 250。我意识到我没有在我的 OP 中这么说。有没有办法解决这个问题。
  • 您的另一个选择是:Decimal('5.000').quantize(Decimal('1.'))
  • @user7116 d.quantize(Decimal('1.')) 将删除所有数字,而不仅仅是尾随零。例如,Decimal('5.050').quantize(Decimal('1.')) 返回一个Decimal('5')
【解决方案2】:

可能有更好的方法,但您可以使用.rstrip('0').rstrip('.') 来获得您想要的结果。

以你的数字为例:

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

下面是 gerrit 在 cmets 中指出的问题的解决方法:

>>> s = str(Decimal('1500'))
>>> print s.rstrip('0').rstrip('.') if '.' in s else s
1500

【讨论】:

  • "如果你确实将它们转换为字符串" - OP 必须 在某些时候转换为字符串,因为他正在输出文本!
  • 这是真的。我必须将它们转换为文本才能输出。如果我想将它们保留为小数,Senderle 的答案可以正常工作,但这个适用于我的目的。
  • 这应该包含一个条件if '.' in s,否则它会将1500变成15
  • re.sub('\.0+$', '', s) 更紧凑但据说更慢
  • 这不适用于 Decimal('2.5E10')。它会返回 2.5E1,这显然是错误的。
【解决方案3】:

来自文档中the Decimal FAQ 的回答:

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

【讨论】:

  • 这应该是公认的答案,因为它直接来自文档。
  • 请注意,当整数小数大于上下文精度中的位数时,这将失败。例如,remove_exponent(Decimal('5e28')) 将引发 decimal.InvalidOperation: quantize result has too many digits for current context,尽管默认上下文完全能够表示 5e28。
【解决方案4】:

FAQ (https://docs.python.org/2/library/decimal.html#decimal-faq) 中提到了答案,但没有解释。

要删除小数部分的尾随零,您应该使用normalize

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

但这对于在尖锐部分带有前导零的数字来说是不同的:

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

在这种情况下,我们应该使用 `to_integral':

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

所以我们可以检查是否有分数部分:

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

然后使用适当的方法:

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

试试看:

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

现在我们完成了。

【讨论】:

    【解决方案5】:

    使用格式说明符%g。它似乎删除到尾随零。

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

    没有Decimal 函数也可以工作

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

    【讨论】:

    • %g 仅支持整数和浮点数,因此 Decimal 将被强制转换为浮点数。 Mypy 警告类型错误:Incompatible types in string interpolation (expression has type "Decimal", placeholder has type "Union[int, float]")。 "%g" % Decimal('200000000.123456789') => '2e+06' vs stackoverflow.com/a/18769210/3720597 str(remove_exponent(Decimal('2000000.123456789'))) => '2000000.123456789'
    【解决方案6】:

    我最终这样做了:

    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
    

    注意:将返回值转换为字符串会将您限制为 12 位有效数字

    【讨论】:

    • 转换为浮点数会丢失精度。此外,您的示例将浮点数传递给dropzeros,而不是小数。
    • 这就是我要找的,因为它将数字返回为 float 而不是 string !谢谢!!
    【解决方案7】:

    A-IV's answer的略微修改版

    注意Decimal('0.99999999999999999999999999995').normalize() 将四舍五入为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
    
    

    【讨论】:

      【解决方案8】:

      您可以使用 %g 来实现:

      '%g'%(3.140)

      使用 Python 吗? 2.6: '{0:g}'.format(3.140)

      【讨论】:

        【解决方案9】:

        这应该可行:

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

        【讨论】:

        • 不,Decimal('10') 使用这种方法输出为 '1'。
        • 正确。我会在这里留下我的答案,以便全世界都能看到为什么不这样做:)
        • 这个答案相当于接受的一个,问题相当于它的第5条评论。我认为没有理由不删除它。
        【解决方案10】:

        为了展示不同的可能性,我使用to_tuple() 来达到相同的结果。

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

        【讨论】:

          【解决方案11】:

          为什么不使用 10 的倍数中的模块 10 来检查是否有余数?没有余数意味着你可以强制 int()

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

          x = 2/1
          输出:2

          x = 3/2
          输出:1.5

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-11-24
            • 1970-01-01
            • 2011-02-25
            • 2011-04-28
            • 2014-07-26
            • 2021-08-12
            相关资源
            最近更新 更多