Python库:用于转换国际单位前缀的库

24

我正在寻找一个带有支持在不同SI前缀之间转换数字的Python库,例如千分之一至皮秒、纳秒至吉赫等等。你有什么推荐吗?


1
可能是Python中的单位转换的重复问题。 - GWW
2
@GWW:不完全是,那个问题是想要转换单位,而这个问题是关于前缀的。 - Junuxx
1
@Zed:你想做什么并不是很清楚。例如,输入的格式是什么?字符串?数字和字符串?数字和前缀索引?如果你能给出一个具体的例子,可能会更有帮助。 - Junuxx
3
由于前缀的数量相当有限,使用简单的字典可能更好(例如 {'giga':1e9, 'kilo':1e3, 'milli':1e-3, ...})。 - Junuxx
8个回答

18
我将一个简单的函数 (原始C版本由Jukka“Yucca”Korpela编写) 移植到Python中,用于按照SI标准格式化数字。我经常使用它,例如在绘图中设置刻度标签等。

您可以通过以下方式安装:

pip install si-prefix

源代码可以在 GitHub 上获取。

使用示例:

from si_prefix import si_format

print si_format(.5)
# 500.0m  (default precision is 1)

print si_format(.01331, precision=2)
# 13.31m

print si_format(1331, precision=2)
# 1.33k

print si_format(1331, precision=0)
# 1k

这似乎没有一种方法可以反转(从 SI 字符串转换为数字)。 - K.S.
1
@K.S.,请查看si_prefix.si_parse()函数(定义请参见此处)。该函数是在版本0.4中添加的。 - naitsirhc

7

QuantiPhy 是一个新的软件包,可以将数字转换为SI比例因子,并从中进行转换。与重量更大且专注于单位而非比例因子的Unit和Magnitude等单位软件包相比,它通常是更好的选择。

QuantiPhy提供了Quantity,这是一个将数字与其度量单位结合(单位可选)的对象。在创建数量时,您可以使用SI单位前缀。一旦您拥有了Quantity,您可以在表达式中使用它,它会作为浮点数。或者您可以将其转换为字符串,在这种情况下,默认使用SI单位前缀。

>>> from quantiphy import Quantity

# convert strings to quantities
>>> duration = Quantity('0.12 ks')
>>> print(duration)
120 s

# convert to other units when rendering to a string
>>> print(duration.render(scale='min'))
2 min

# quantities act like floats in expressions
>>> rate = 1/duration
>>> print(rate)
0.008333333333333333

# convert floats to quantities
>>> rate = Quantity(rate, 'Hz')
>>> print(rate)
8.3333 mHz

# can be used in format strings
>>> print(f'Duration = {duration:<12.3} Rate = {rate}')
Duration = 120 s        Rate = 8.3333 mHz

默认情况下,QuantiPhy在渲染成字符串时使用自然前缀,这可能是您想要的。但是,您可以通过缩放强制它以特定前缀进行渲染:
>>> mass = Quantity('1000 g')
>>> print(mass)
1 kg

>>> print(mass.render(show_si=False))
1e3 g

>>> print(mass.render(show_si=False, scale=(1e-12, 'pg')))
1e9 pg

在这种情况下,您必须关闭国际单位前缀,以避免出现多个前缀:“1 npg”。
一个更自然的例子可能是您正在转换单位:
>>> l = Quantity('2um')                                                      
>>> print(l.render(scale='Å'))                                               
20 kÅ                                                                        

>>> print(f'{l:sÅ}')                                                         
20 kÅ

最后一个例子表明,您可以在类型后的格式字符串中放置所需的单位,转换将自动完成。

6

字典

如果您不想使用下面列出的任何第三方库,实际上可以自己实现解析功能。

使用字典将前缀与其值匹配。我已经为您完成了:

_prefix = {'y': 1e-24,  # yocto
           'z': 1e-21,  # zepto
           'a': 1e-18,  # atto
           'f': 1e-15,  # femto
           'p': 1e-12,  # pico
           'n': 1e-9,   # nano
           'u': 1e-6,   # micro
           'm': 1e-3,   # mili
           'c': 1e-2,   # centi
           'd': 1e-1,   # deci
           'k': 1e3,    # kilo
           'M': 1e6,    # mega
           'G': 1e9,    # giga
           'T': 1e12,   # tera
           'P': 1e15,   # peta
           'E': 1e18,   # exa
           'Z': 1e21,   # zetta
           'Y': 1e24,   # yotta
    }

接下来您可以使用正则表达式(如我在这里的回答中所述)来搜索或解析输入,并使用字典获取相应的值。


Unum

Unum是一个做得很好并且文档详细的库。

优点:

  • 允许您定义任意单位(只要它们是基本单位的组合,Magnitude仅支持用户定义单位)。

缺点:

  • 处理前缀不够好
  • 将所有单位定义放到命名空间中(最终您的命名空间中会出现命名为MS等的变量)

Magnitude

您还可以使用另一个库Magnitude。它支持您提到的所有SI单位前缀类型,而且它也会处理解析。引用自该网站:

物理量就是带有单位的数字,比如10 km/h。单位用字符串表示。它们可以是任何SI单位,以及一堆非SI单位、比特、美元和它们的任意组合。它们可以包括标准的SI前缀。
...
所有标准前缀都可以理解,从yocto到yotta,从kibi到exbi。


正则表达式似乎不是检测使用哪个前缀的最佳方式。使用log(abs(value))不是可以给出你的值的大小,以便选择一个前缀吗? - Jonathan Hartley

4

我不确定这是最好的答案,但在我的情况下它是有效的。欢迎验证我的解决方案。这是我第一次使用Python,欢迎提出建设性批评...以及积极的反馈 :D
以下是我的代码:

class Units:
def __init__(self):
    global si;
    si = {
          -18 : {'multiplier' : 10 ** 18, 'prefix' : 'a'},
          -17 : {'multiplier' : 10 ** 18, 'prefix' : 'a'},
          -16 : {'multiplier' : 10 ** 18, 'prefix' : 'a'},
          -15 : {'multiplier' : 10 ** 15, 'prefix' : 'f'},
          -14 : {'multiplier' : 10 ** 15, 'prefix' : 'f'},
          -13 : {'multiplier' : 10 ** 15, 'prefix' : 'f'},
          -12 : {'multiplier' : 10 ** 12, 'prefix' : 'p'},
          -11 : {'multiplier' : 10 ** 12, 'prefix' : 'p'},
          -10 : {'multiplier' : 10 ** 12, 'prefix' : 'p'},
          -9 : {'multiplier' : 10 ** 9, 'prefix' : 'n'},
          -8 : {'multiplier' : 10 ** 9, 'prefix' : 'n'},
          -7 : {'multiplier' : 10 ** 9, 'prefix' : 'n'},
          -6 : {'multiplier' : 10 ** 6, 'prefix' : 'u'},
          -5 : {'multiplier' : 10 ** 6, 'prefix' : 'u'},
          -4 : {'multiplier' : 10 ** 6, 'prefix' : 'u'},
          -3 : {'multiplier' : 10 ** 3, 'prefix' : 'm'},
          -2 : {'multiplier' : 10 ** 2, 'prefix' : 'c'},
          -1 : {'multiplier' : 10 ** 1, 'prefix' : 'd'},
           0 : {'multiplier' : 1, 'prefix' : ''},
           1 : {'multiplier' : 10 ** 1, 'prefix' : 'da'},
           2 : {'multiplier' : 10 ** 3, 'prefix' : 'k'},
           3 : {'multiplier' : 10 ** 3, 'prefix' : 'k'},
           4 : {'multiplier' : 10 ** 3, 'prefix' : 'k'},
           5 : {'multiplier' : 10 ** 3, 'prefix' : 'k'},
           6 : {'multiplier' : 10 ** 6, 'prefix' : 'M'},
           7 : {'multiplier' : 10 ** 6, 'prefix' : 'M'},
           8 : {'multiplier' : 10 ** 6, 'prefix' : 'M'},
           9 : {'multiplier' : 10 ** 9, 'prefix' : 'G'},
          10 : {'multiplier' : 10 ** 9, 'prefix' : 'G'},
          11 : {'multiplier' : 10 ** 9, 'prefix' : 'G'},
          12 : {'multiplier' : 10 ** 12, 'prefix' : 'T'},
          13 : {'multiplier' : 10 ** 12, 'prefix' : 'T'},
          14 : {'multiplier' : 10 ** 12, 'prefix' : 'T'},
          15 : {'multiplier' : 10 ** 15, 'prefix' : 'P'},
          16 : {'multiplier' : 10 ** 15, 'prefix' : 'P'},
          17 : {'multiplier' : 10 ** 15, 'prefix' : 'P'},
          18 : {'multiplier' : 10 ** 18, 'prefix' : 'E'},
          }

def convert(self, number):
    # Checking if its negative or positive
    if number < 0:
        negative = True;
    else:
        negative = False;

    # if its negative converting to positive (math.log()....)
    if negative:
        number = number - (number*2);

    # Taking the exponent
    exponent = int(math.log10(number));

    # Checking if it was negative converting it back to negative
    if negative:
        number = number - (number*2);

    # If the exponent is smaler than 0 dividing the exponent with -1
    if exponent < 0:
        exponent = exponent-1;
        return [number * si[exponent]['multiplier'], si[exponent]['prefix']]; 
    # If the exponent bigger than 0 just return it
    elif exponent > 0:
        return [number / si[exponent]['multiplier'], si[exponent]['prefix']]; 
    # If the exponent is 0 than return only the value
    elif exponent == 0:
        return [number, ''];

这是它的工作原理:
c1 = +1.189404E-010
fres = -4.07237500000000E+007;
ls = +1.943596E-005;

units = sci.Units();
rValue, rPrefix = units.convert(c1);
print rValue;
print rPrefix;

print units.convert(fres);
print units.convert(ls);

并且响应是:
118.9404
p
[-40.72375, 'M']
[19.435959999999998, 'u']

我不知道是否有人会发现这个有用,但我希望你会。我在这里发布是为了让需要帮助的人看到,并给他们一个想法,也许他们可以优化它 :)


2
改进建议: 您使用“d”作为十分位和十位的前缀,这既令人困惑又不正确。 十位应该有前缀“da”。 - alkanen

3

我知道这是一个旧的帖子,但我想提供一个参考,即我编写的一个Python库,可以处理各种前缀单位转换。

以下是主要功能列表:


1

由于该问题尚未标记为已回答,我将在此提供我的解决方案,这在很大程度上受到si-prefix的启发,但我希望限制我的软件包依赖数量。

您可以使用以下函数:

def format_prefix(value, precision=2, unit="m"):
    """
   Formats a numerical value with an appropriate SI prefix.

   Args:
   - value: A float representing the numerical value to be formatted.
   - precision: An integer representing the number of decimal places to include in the formatted value (default: 2).
   - unit: A string representing the unit of measurement to be appended to the formatted value (default: "m").

   Returns:
   - A string representing the formatted value with the appropriate SI prefix and unit.

   Raises:
   - ValueError: If the exponent is out of range of the available prefixes.
    """
    SI_PREFIX_UNITS = u"yzafpnµm kMGTPEZY"
    negative = False
    digits = precision + 2

    if value == 0.:
        expof10 = 0
    else:
        if value < 0.:
            value = -value
            negative = True
        expof10 = int(np.log10(value))
        if expof10 > 0:
            expof10 = (expof10 // 3) * 3
        else:
            expof10 = (-expof10 + 3) // 3 * (-3)

        value *= 10 ** (-expof10)

        if value >= 1000.:
            value /= 1000.0
            expof10 += 3
        elif value >= 100.0:
            digits -= 2
        elif value >= 10.0:
            digits -= 1

        if negative:
            value *= -1
    expof10 = int(expof10)
    prefix_levels = (len(SI_PREFIX_UNITS) - 1) // 2
    si_level = expof10 // 3

    if abs(si_level) > prefix_levels:
        raise ValueError("Exponent out range of available prefixes.")
    return f"{round(value*10**digits)/10**digits} "+SI_PREFIX_UNITS[si_level + prefix_levels].strip()+unit

该函数输出如下字符串:
>>> format_prefix(123687.2e-9, unit="m")
'123.69 µm'
>>> format_prefix(123687.2e-9, precision=12, unit="rad")
'123.6872 µrad'

0
您可以使用带前缀,该前缀包含有额外的浮点数格式选项。
通过包含前缀,您可以创建类似于浮点数的数字。
>>> from prefixed import Float

>>> Float('2k')
Float(2000.0)

prefixed.Floatfloat 的子类,所以您可以像使用float一样使用它,但在输出时,它支持额外的格式说明符。

num = Float('2k')
>>> f'{num}'
'2000.0'
>>> f'{num:.2h}'
'2.00k'

二进制前缀也被支持,并且还有一些额外的格式选项。请参阅文档获取更多信息。


0

@naitsirhc,感谢你的包。 我已经添加了一个小功能来使用你的包

import pandas as pd
import collections
Measure = collections.namedtuple('Measure', 'SLOT TEXT AVG HIGH LAST LOW SIGMA SWEEPS')
d=[Measure(SLOT='1', TEXT='CH1,AMPLITUDE', AVG='584.4782173493248E-3', HIGH='603.9744119119119E-3', LAST='594.125218968969E-3', LOW='561.1797735235235E-3', SIGMA='5.0385410346638E-3', SWEEPS='237996'), Measure(SLOT='2', TEXT='CH1,FREQUENCY', AVG='873.9706607717992E+6', HIGH='886.1564731675113E+6', LAST='873.9263571643770E+6', LOW='854.8833348698727E+6', SIGMA='4.382200567330E+6', SWEEPS='20705739'), Measure(SLOT='3', TEXT='CH4,PERIOD', AVG='1.1428492411436E-9', HIGH='1.1718844685593E-9', LAST='1.1432428766843E-9', LOW='1.1261916413092E-9', SIGMA='6.6735923746950E-12', SWEEPS='20680921'), Measure(SLOT='4', TEXT='CH4,FREQUENCY', AVG='875.0358282079155E+6', HIGH='887.9483414008331E+6', LAST='874.780693212961E+6', LOW='853.3264385945507E+6', SIGMA='5.0993358972092E+6', SWEEPS='20681008')]

from si_prefix import si_format

import si_prefix
si_prefix.SI_PREFIX_UNITS="yzafpnum kMGTPEZY"

def siSuffixNotation(element):
    try:
        ret=float(element)
        return str(si_format(ret)).replace(' ','')
    except ValueError:
        return element

df=pd.DataFrame(d)

df.T.applymap(siSuffixNotation) #<= nice pretty print output table
                    0              1           2              3
SLOT              1.0            2.0         3.0            4.0
TEXT    CH1,AMPLITUDE  CH1,FREQUENCY  CH4,PERIOD  CH4,FREQUENCY
AVG            584.5m         874.0M        1.1n         875.1M
HIGH           604.0m         885.6M        1.2n         887.9M
LAST           586.5m         874.2M        1.1n         874.9M
LOW            561.2m         854.9M        1.1n         854.1M
SIGMA            5.0m           4.4M        6.7p           5.1M
SWEEPS         191.5k          16.7M       16.6M          16.6M

谢谢你,现在我可以拥有一个漂亮打印的表格,就像我喜欢的那样。 (我不喜欢数字和后缀之间有空格,也不喜欢Unicode类型,我更喜欢使用u表示微小) ++

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