以工程格式打印数字

25

我正在尝试使用Python以工程格式打印数字,但似乎无法使其工作。语法看起来很简单,但它就是无法工作。

>>> import decimal 
>>> x = decimal.Decimal(1000000)
>>> print x
1000000
>>>> print x.to_eng_string() 
1000000

我弄不明白为什么会这样。这两个值不相等(一个是字符串,另一个是整数)。在 decimal 中设置各种上下文似乎也没有帮助。有什么线索或想法吗?


我在这里找到了解决方案"%.4g" % x。 - lehalle
9个回答

25

要使其工作,您必须首先将小数标准化:

>>> x = decimal.Decimal ('10000000')

>>> x.normalize()
Decimal('1E+7')

>>> x.normalize().to_eng_string()
'10E+6'

这样的原因可以通过深入源代码来发现。

如果您查看 Python 2.7.3 源代码树中的 to_eng_string() (Lib/decimal.py,从此处下载gzipped源tar ball),它只是使用 eng 设置为true 调用了 __str__

然后您可以看到,它决定了小数点左侧有多少位数字:

leftdigits = self._exp + len(self._int)

下表显示这两个变量的值:

                         ._exp       ._int         len   leftdigits
                         -----       ---------     ---   ----------
Decimal (1000000)            0       '1000000'       7            7
Decimal ('1E+6')             6       '1'             1            7

接下来的代码如下:

if self._exp <= 0 and leftdigits > -6:
    # no exponent required
    dotplace = leftdigits
elif not eng:
    # usual scientific notation: 1 digit on left of the point
    dotplace = 1
elif self._int == '0':
    # engineering notation, zero
    dotplace = (leftdigits + 1) % 3 - 1
else:
    # engineering notation, nonzero
    dotplace = (leftdigits - 1) % 3 + 1

你可以看到,除非在特定范围内已经有一个指数(self._exp > 0 or leftdigits <= -6),否则在字符串表示中不会给它加上指数。


进一步的调查显示了这种行为的原因。查看代码本身,你会发现它基于General Decimal Arithmetic Specification(PDF文档请点击这里)。

如果你在该文档中搜索to-scientific-stringto-engineering-string就是基于它),它的某些部分(由我改写,并加粗)如下:

“to-scientific-string”操作将数字转换为字符串,如果需要指数,则使用科学计数法。此操作不受上下文影响。

如果数字是有限数字,则:

首先,系数使用数字0到9转换为十进制字符串,没有前导零(除非其值为零,在这种情况下使用单个0字符)。

接下来,计算调整后的指数;这是指数加上转换系数的长度减1。也就是说,exponent+(clength-1),其中clength是系数的十进制数字长度。

如果指数小于或等于零且调整后的指数大于或等于-6,则该数字将被转换为不使用指数表示法的字符形式。在这种情况下,如果指数为零,则不添加小数点。否则(指数将为负),小数点将插入到小数点右侧的字符数中。必要时在已转换的系数左侧添加“0”字符。如果在插入此字符之后小数点之前没有任何字符,则会加上传统的“0”字符前缀。

换句话说,它所做的正是标准告诉它要做的事情。


为什么要检查那个?我请求的是特定的东西。然后它决定我不想要那个,只想要我给它的东西?有时候 Python 让我感到困惑... - jmurrayufo
2
@jmurrayufo,请查看更新。基本上,它这样做是因为标准规定了这样做。 - paxdiablo

17

我知道这是一个旧的帖子,但它确实接近搜索“python工程符号”的顶部。

我是一名工程师,喜欢“工程101”工程单位。我甚至不喜欢像0.1uF这样的标识,我希望它写成100nF。我尝试使用Decimal类,但并不喜欢它在可能值范围内的行为,因此我编写了一个名为engineering_notation的软件包,可以通过pip安装。

pip install engineering_notation

从Python内部:

>>> from engineering_notation import EngNumber
>>> EngNumber('1000000')
1M
>>> EngNumber(1000000)
1M
>>> EngNumber(1000000.0)
1M
>>> EngNumber('0.1u')
100n
>>> EngNumber('1000m')
1

这个软件包还支持比较和其他简单的数值操作。

https://github.com/slightlynybbled/engineering_notation


6

可以使用内置的printf格式化字符串来解决。

假设(向前)您现在使用的是内置的 Python 3,因为所有格式化相关的事情都得到了极大的简化和规范化(我建议将此速查表添加到书签中)。

In [1]: myfloat = 0.000000001
In [2]: print(f'{myfloat:g}')
1e-09

在 Python 2 中仍然可行的旧语法为:

In [17]: '%g' % myfloat
Out[17]: '1e-09'

另一种漂亮的格式如下:

In [3]: print(f'{myfloat:E}')
1.000000E-09

你可以在其中对输出进行一些控制:

In [4]: print(f'{myfloat:.3E}')
1.000E-09

这是科学计数法,而不是工程计数法。在工程计数法中,指数始终是3的倍数。 - Waxrat
我之前没有注意到这个区别。@Waxrat,你允许我将其添加到我的答案中吗? - meduz

4

我觉得你需要自己动手:

from math import log10
def eng_str(x):
    y = abs(x)
    exponent = int(log10(y))
    engr_exponent = exponent - exponent%3
    z = y/10**engr_exponent
    sign = '-' if x < 0 else ''
    return sign+str(z)+'e'+str(engr_exponent)

尽管您可能希望在z部分的格式上更加小心......

尚未经过充分测试,如果发现错误请随时编辑


虽然我不介意这个想法,但我更好奇为什么“decimal”类没有做它应该做的事情,或者我是如何使用它错误的。 - jmurrayufo
@jmurrayufo -- 抱歉,我想我误解了问题 :)。 - mgilson

2

matplotlib也可以使用:

>>> from matplotlib.ticker import EngFormatter
>>> engFormat = EngFormatter()
>>> print(engFormat(1000))
1 k

我让你参考https://matplotlib.org/stable/api/ticker_api.html获取更多详细信息和选项。 另一个更复杂的例子:

>>> from matplotlib.ticker import EngFormatter
>>> engFormat = EngFormatter(unit='Hz',places=2,sep='')
>>> print(engFormat(1000))
1.00kHz

1

QuantiPhy 是另一个可以帮助您的软件包。 QuantiPhy 提供了 Quantity 类,用于将值和单位组合成一个对象。它可以以多种方式格式化数量。通常人们使用 SI 比例因子,但也支持工程形式。安装方法:

pip3 install quantiphy

您可以从数字或字符串创建一个数量对象:
>>> from quantiphy import Quantity
>>> q = Quantity(10000000)
>>> q.render(form='eng')
'10e6'

默认情况下,QuantiPhy 使用国际单位制符号,但您可以将默认形式设置为“eng”:
>>> print(q)
10M
>>> Quantity.set_prefs(form='eng')
>>> print(q)
10e6

通常情况下,您会将单位指定为一个量:

>>> f = Quantity(10_000_000, 'Hz')
>>> print(f)
10e6 Hz

Quantityfloat的子类,因此您可以在任何需要使用float的地方使用Quantity

QuantiPhy是一个功能强大的包,得到了良好的支持和文档。我鼓励您尝试一下。


0

我的解决方案(我也遇到了同样的问题) 这是一个浅显的想法,没有花哨的编程, 快速而简单,但对我来说还可以 :). 它包含了一个快速测试。

#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
    converts float number to string in engineering form
    xxx.xxx [G M k _ m u n p]
    
"""

def F(f):

    if f < 0.0:
        sgn = "-"# = True
        f = -f
    else:
        sgn = "+"
    
    if   f > 999999999999.999999999999:
        return sgn+"max.---"+">" # "overflow error"
    elif f > 999999999.999999999999:
        v = f/1000000000, "G"
    elif f > 999999.999999999999:
        v = f/1000000.0, "M"
    elif f > 999.999999999999:
        v = f/1000.0, "k"
    elif f > 0.999999999999:
        v = f , " "
    elif f > 0.000999999999:
        v = f*1000 , "m"
    elif f > 0.000000999999:
        v = f*1000000.0 , "u"
    elif f > 0.000000000999:
        v = f*1000000000.0 , "n"
    elif f > 0.000000000000999:
        v = f*1000000000000.0 , "p"
    elif f > 0.000000000000000999:
        v = f*1000000000000.0 , "p"
    else:
        return sgn+"min.---"+"<" # "underflow error"
        
    n = v[0]
    if sgn == "-":
        n = - n
        
    # to sIII.FFF s = sign, I = integer digit, F = fractional digit
    
    n = "{:+08.3f}".format(n)
    return n + v[1]

if __name__ == "__main__":
    # test
    s = ""
    f = 0.0000000000000001111111111
    for a in range(55):
        f = f * 3.333
        v = F(f)
        print("{:03d} {:-37.18f}".format(a,f),v)
        v = F(-f)
        print("{:03d} {:-37.18f}".format(a,-f),v)
    

0

类似于 Decimal(s).normalize().to_eng_string(),但后者不使用科学计数法表示 e-3 和 e-6。

from decimal import Decimal

def to_eng(v: float | str | Decimal) -> str:
    """
    Convert a floating point value to engineering notation.
    """
    dec = Decimal(v).normalize().as_tuple()
    sign = '-' if dec.sign else ''
    digits = ''.join(str(digit) for digit in dec.digits)
    exp = dec.exponent

    # number of digits to left of decimal point
    leftdigits = exp + len(digits)

    dotplace = (leftdigits - 1) % 3 + 1

    if dotplace <= 0:
        intpart = '0'
        fracpart = '.' + '0' * -dotplace + digits
    elif dotplace >= len(digits):
        intpart = digits + '0' * (dotplace - len(digits))
        fracpart = ''
    else:
        intpart = digits[:dotplace]
        fracpart = '.' + digits[dotplace:]
    if leftdigits == dotplace:
        exps = ''
    else:
        exps = f'e{leftdigits - dotplace}'

    return sign + intpart + fracpart + exps

-5

现在是2020年!全球爆发了COVID-19疫情。你需要使用f-string字面值来进行这种工作:

在Python3.6.4及以上版本中,您可以这样做

a = 3e-4
print(f"{a:.0E}")

2
请注意,这将返回科学计数法,而不是工程计数法,其中E应为3的倍数。问题并不完全清楚是否可以接受,但在许多用例中非常重要。 - Ant
我在发布我的回答后看到了你的回答,很抱歉有重叠。在计算机科学中,“工程符号”的定义就是在这个答案中给出的 - 或许提问者存在一些混淆,但这个答案是正确的。 - meduz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接