将长数字格式化为字符串

78

如何使用Python简单地将整数格式化成以K表示千位,以M表示百万位,并在逗号后只保留几个数字的字符串?

例如,我希望把7436313显示为7.44M,而把2345显示为2.34K。

是否有可用于此目的的%格式化操作符?或者这只能通过循环除以1000并逐步构建结果字符串来实现?

14个回答

112

这个版本不会出现之前答案中的错误,即 999,999 显示为 1000.0K。同时,它只允许三个有效数字,并消除了末尾的 0。

def human_format(num):
    num = float('{:.3g}'.format(num))
    magnitude = 0
    while abs(num) >= 1000:
        magnitude += 1
        num /= 1000.0
    return '{}{}'.format('{:f}'.format(num).rstrip('0').rstrip('.'), ['', 'K', 'M', 'B', 'T'][magnitude])

输出结果如下:
>>> human_format(999999)
'1M'
>>> human_format(999499)
'999K'
>>> human_format(9994)
'9.99K'
>>> human_format(9900)
'9.9K'
>>> human_format(6543165413)
'6.54B'

4
这是我唯一完美运行的版本(没有烦人的尾随0)。 - Harry Palmer
如果我想要的格式是'9_9K'而不是'9.9K',该怎么办? - AleB
@AleB 之后使用替换函数将 . 替换为 _。 - rtaft

97
我认为没有内置的函数可以完成这个任务。您需要自己编写代码,例如:
def human_format(num):
    magnitude = 0
    while abs(num) >= 1000:
        magnitude += 1
        num /= 1000.0
    # add more suffixes if you need them
    return '%.2f%s' % (num, ['', 'K', 'M', 'G', 'T', 'P'][magnitude])

print('the answer is %s' % human_format(7436313))  # prints 'the answer is 7.44M'

8
999999 应显示 1M 而不是 1000.00K。 - rtaft
2
你可以在编辑器中编写long_nr=1_000_000而不是1000000,Python 不会将 _ 视为计算符号。 - Timo

18

一种更"数学化"的解决方案是使用math.log

from math import log, floor


def human_format(number):
    units = ['', 'K', 'M', 'G', 'T', 'P']
    k = 1000.0
    magnitude = int(floor(log(number, k)))
    return '%.2f%s' % (number / k**magnitude, units[magnitude])

测试:

>>> human_format(123456)
'123.46K'
>>> human_format(123456789)
'123.46M'
>>> human_format(1234567890)
'1.23G'

3
999999 应该显示 1M,但它显示为 1000.00K。 - rtaft
在我看来,最好的方法是因为它不需要循环。 - Addison Klinke
如果您想支持负值,可以将绝对值函数 abs 添加到幅度计算中: magnitude = int(floor(log(abs(number), k))) - benlev
除了支持负数之外,还需要处理0到1之间的数字: magnitude = int(math.floor(math.log(max(abs(number), 1), k))) - benlev
@AddisonKlinke 它真的会产生性能差异吗?对数函数是如何计算的?如果有的话,我可以使用它来改善下面的答案。 - m-schwob

10

变量精度和没有999999错误:

def human_format(num, round_to=2):
    magnitude = 0
    while abs(num) >= 1000:
        magnitude += 1
        num = round(num / 1000.0, round_to)
    return '{:.{}f}{}'.format(num, round_to, ['', 'K', 'M', 'G', 'T', 'P'][magnitude])

2
在最后一行的格式字符串中,我认为你可以用 num 替换 round(num, round_to),因为此时它已经被四舍五入了。 - tdy

6

我今天需要这个函数,稍微更新了已接受的答案,以适应Python >= 3.6的人:

def human_format(num, precision=2, suffixes=['', 'K', 'M', 'G', 'T', 'P']):
    m = sum([abs(num/1000.0**x) >= 1 for x in range(1, len(suffixes))])
    return f'{num/1000.0**m:.{precision}f}{suffixes[m]}'

print('the answer is %s' % human_format(7454538))  # prints 'the answer is 7.45M'

编辑:考虑到评论,您可能想要更改为round(num/1000.0)


2
999999 显示为 1000.00K,但应该显示为 1M。 - rtaft
1
更加优雅的翻译:m = int(math.log10(num) // 3) - Nimrod Morag

5

Numerize 库很好。

from numerize import numerize
a = numerize.numerize(1000)
print(a)
1k

感谢 @tdy 指出这个问题,
a = numerize.numerize(999999) 
print(a)  # 1000K 
1000K

1
与大多数其他答案/库一样,它也存在相同的999999 -> 1000K bug。 - tdy
是的。感谢 @tdy 的提醒。 - John Prawyn

2
我有点困惑其他人展示的一些东西,所以我写了下面的代码。它可以四舍五入到第二个小数点,例如“23.56十亿”,但是您可以通过将最后一行中的两个“100.0”替换为更大或更小的数字来更改四舍五入的小数位数,例如“10.0”四舍五入到一位小数,“1000.0”四舍五入到三位小数。此外,使用此代码,它总是向下舍入。如果您愿意,可以通过将“floor”替换为“ceil”或“round”来更改此设置。
#make the dictionary to store what to put after the result (ex. 'Billion'). You can go further with this then I did, or to wherever you wish. 
#import the desired rounding mechanism. You will not need to do this for round. 
from math import floor
magnitudeDict={0:'', 1:'Thousand', 2:'Million', 3:'Billion', 4:'Trillion', 5:'Quadrillion', 6:'Quintillion', 7:'Sextillion', 8:'Septillion', 9:'Octillion', 10:'Nonillion', 11:'Decillion'}
def simplify(num):
    num=floor(num)
    magnitude=0
    while num>=1000.0:
        magnitude+=1
        num=num/1000.0
    return(f'{floor(num*100.0)/100.0} {magnitudeDict[magnitude]}')

在最后一行字符串前的 'f' 是为了让python知道你正在格式化它。运行 print(simplify(34867123012.13)) 的结果是:

34.86 Billion

如果您有任何问题,请告诉我! 谢谢, 安格斯


现有的代码在显示999999时会输出“999.99 Thousand”,但实际上应该是“1 Million”。此外,这段代码只能在Python 3.6及以上版本中使用。 - rtaft
1
我知道。它是这样制作的。对于我使用它的那种游戏,我希望它是这样的,这样你总是实际拥有它所说的东西,而不是少一个,以避免当你想购买与你的资金相同的东西但无法购买因为实际数字比你的资金低一个时产生的混淆。您可以通过将floor()替换为round()来解决此问题。 - Angus The Car

1
基于这里的评论,我为此编写了一份更好的代码。它可能有点长,但可以解决更多情况,包括小数字(m、u、n、p)。
希望对某些人有所帮助。
# print number in a readable format.
# default is up to 3 decimal digits and can be changed
# works on numbers in the range of 1e-15 to 1e 1e15 include negatives numbers
# can force the number to a specific magnitude unit
def human_format(num:float, force=None, ndigits=3):
    perfixes = ('p', 'n', 'u', 'm', '', 'K', 'M', 'G', 'T')
    one_index = perfixes.index('')
    if force:
        if force in perfixes:
            index = perfixes.index(force)
            magnitude = 3*(index - one_index)
            num = num/(10**magnitude)
        else:
            raise ValueError('force value not supported.')
    else:
        div_sum = 0
        if(abs(num) >= 1000):
            while abs(num) >= 1000:
                div_sum += 1
                num /= 1000
        else:
            while abs(num) <= 1:
                div_sum -= 1
                num *= 1000
        temp = round(num, ndigits) if ndigits else num
        if temp < 1000:
            num = temp 
        else:
            num = 1
            div_sum += 1
        index = one_index + div_sum
    return str(num).rstrip('0').rstrip('.') + perfixes[index]

从这里开始的测试和更多测试

# some tests
print(human_format(999)              ,' = '         , '999') 
print(human_format(999.999)          ,' = '         , '999.999') 
print(human_format(999.9999)         ,' = '         , '1K')  
print(human_format(999999)           ,' = '         , '999.999K')   
print(human_format(999499)           ,' = '         , '999.499K')   
print(human_format(9994)             ,' = '         , '9.994K')   
print(human_format(9900)             ,' = '         , '9.9K')   
print(human_format(6543165413)       ,' = '         , '6.543G')  
print(human_format(46780.9)          ,' = '         , '46.781K')  
print(human_format(0.001)            ,' = '         , '1m')   
print(human_format(0.000000999999)   ,' = '         , '999.999n')  
print(human_format(1.00394200)       ,' = '         , '1.004')   
print(human_format(0.0999)           ,' = '         , '99.9m')  
print(human_format(0.00000000999999) ,' = '         , '10n') 
print(human_format(0.0000000099995)  ,' = '         , '9.999n')  
print(human_format(0.000000009999)   ,' = '         , '9.999n') 
print(human_format(999999            ,ndigits=2)    ,' = '           , '1M')   
print(human_format(9994              ,force='')     ,' = '           , '9994K')   
print(human_format(6543165413        ,ndigits=5)    ,' = '           , '6.54317G')  
print(human_format(6543165413        ,ndigits=None) ,' = '           , '6.543165413G')  
print(human_format(7436313           ,ndigits=2)    ,' = '           , '7.44M')   
print(human_format(2344              ,ndigits=2)    ,' = '           , '2.34K')
print(human_format(34867123012.13    ,ndigits=2)    ,' = '           , '34.87G')   

9994 == 9994K 是什么意思?你认为 999.999K 比 999999 更清晰吗?另外,ndigits 应该被称为 ndecimals,因为这是你使用它的方式。你还有一个 if force:,当使用 '' 时会被评估为 false。 - rtaft

1

millify 还将 999999 显示为 1000k。 - rtaft

0
def human_format(value):
   num = value
   magnitude = 0
   while abs(num) >= 1000:
      magnitude += 1
      num /= 1000.0
   result = round(value / (1000**magnitude),3)
   return '{}{}'.format(result, ['', 'K', 'M', 'B', 'T'][magnitude])

1
你应该添加一些关于你发布的代码与其他答案不同(和/或更好)的解释。对我来说,它看起来非常相似。 - Adrian Mole

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