python - 如何在 python 中以长的、漂亮的圆角和本地化格式打印任何 float ,例如 1.234e22?

标签 python localization number-formatting python-babel

有几个existing questions关于 float 格式,但我认为没有人回答以下问题。

我正在寻找一种方法,以长的、漂亮的圆形和本地化格式打印大型 float :

>>> print magic_format(1.234e22, locale="en_US")
12,340,000,000,000,000,000,000
>>> print magic_format(1.234e22, locale="fr_FR")
12 340 000 000 000 000 000 000

不幸的是,magic_format 不存在。 ;-) 我该如何实现它?

详细信息

这里有几种打印 float 的方法。它们都不会产生上述输出:

>>> x = 1.234e22
>>> print str(x)
1.234e+22
>>> print repr(x)
1.234e+22
>>> print "%f" % x
12339999999999998951424.000000
>>> print "%g" % x
1.234e+22

失败:要么得到简短版本,要么得到非分组非本地化非舍入输出。

顺便说一句,我知道 1.234e22 不能完全存储为 float ,存在必要的舍入误差(这解释了上面的奇数输出)。但由于 strrepr"%g"% x 能够正确地将其舍入为正确的值,我希望有相同的友好舍入数字,但采用长且本地化的形式。

现在让我们尝试本地化...

>>> import locale
>>> locale.setlocale(locale.LC_ALL, "en_US")
'en_US'
>>> locale.format("%g", x, grouping = True)
'1.234e+22'
>>> locale.format("%f", x, grouping = True)
'12,339,999,999,999,998,951,424.000000'
>>> locale.setlocale(locale.LC_ALL, "fr_FR")
'fr_FR'
>>> locale.format("%g", x, grouping = True)
'1,234e+22'
>>> locale.format("%f", x, grouping = True)
'12339999999999998951424,000000'

接近,但还不行。我仍然有恼人的舍入错误,而且法语本地化很糟糕,它根本不允许分组。

所以让我们使用优秀的Babel图书馆,也许它可以做我想做的一切:

>>> from babel.numbers import format_number
>>> format_number(x, locale = "en_US")
u'12,339,999,999,999,998,951,424'
>>> format_number(x, locale = "fr_FR")
u'12\xa0339\xa0999\xa0999\xa0999\xa0998\xa0951\xa0424'

哇,真的很接近。他们甚至使用不可破坏的空格来用法语进行分组,我喜欢它。真是太糟糕了,他们仍然存在舍入问题。

嘿!?如果我使用 python Decimals 会怎么样? ?

>>> from decimal import Decimal
>>> Decimal(x)
Decimal('12339999999999998951424')
>>> Decimal("%g" % x)
Decimal('1.234E+22')
>>> "%g" % Decimal("%g" % x)
'1.234e+22'
>>> "%f" % Decimal("%g" % x)
'12339999999999998951424.000000'

不。我可以使用 Decimal("%g"% x) 获得所需数字的精确表示,但每当我尝试显示它时,它要么很短,要么在打印之前转换为错误的 float 。

但是如果我混合 Babel 和 Decimals 会怎样?

>>> Decimal("%g" % 1.234e22)
Decimal('1.234E+22')
>>> dx = _
>>> format_number(dx, locale = "en_US")
Traceback (most recent call last):
...
TypeError: bad operand type for abs(): 'str'

哎呀。但是 Babel 有一个名为 format_decimal 的函数,让我们用它来代替:

>>> from babel.numbers import format_decimal
>>> format_decimal(dx, locale = "en_US")
Traceback (most recent call last):
...
TypeError: bad operand type for abs(): 'str'

哎呀,format_decimal 无法格式化 python 小数。 :-(

好吧,最后一个想法:我可以尝试转换为 long

>>> x = 1.234e22
>>> long(x)
12339999999999998951424L
>>> long(Decimal(x))
12339999999999998951424L
>>> long(Decimal("%g" % x))
12340000000000000000000L

是的!我已经得到了想要格式化的确切数字。让我们把它交给 Babel:

>>> format_number(long(Decimal("%g" % x)), locale = "en_US")
u'12,339,999,999,999,998,951,424'

哦,不...显然 Babel 在尝试格式化它之前将 long 转换为 float 。我运气不好,也没有想法。 :-(

如果您认为这很难,请尝试针对 x = 1.234e-22 回答相同的问题。到目前为止,我只能打印缩写形式 1.234e-220.0!

我更喜欢这个:

>>> print magic_format(1.234e-22, locale="en_US")
0.0000000000000000000001234
>>> print magic_format(1.234e-22, locale="fr_FR")
0,0000000000000000000001234
>>> print magic_format(1.234e-22, locale="en_US", group_frac=True)
0.000,000,000,000,000,000,000,123,400
>>> print magic_format(1.234e-22, locale="fr_FR", group_frac=True)
0,000 000 000 000 000 000 000 123 400

我可以想象编写一个小函数来解析 "1.234e-22" 并很好地格式化它,但我必须了解数字本地化的所有规则,而且我宁愿不要重新发明轮子,Babel 应该这样做。我该怎么办?

感谢您的帮助。 :-)

最佳答案

这需要从 Nicely representing a floating-point number in python 中选择的答案中获取大量代码,但合并了 Babel 来处理 L10N。

注意: Babel 在许多语言环境中使用了奇怪的 unicode 版本的空格字符。因此,if 循环直接提到“fr_FR”,将其转换为普通空格字符。

import locale
from babel.numbers import get_decimal_symbol,get_group_symbol
import decimal

# https://stackoverflow.com/questions/2663612/nicely-representing-a-floating-point-number-in-python/2663623#2663623
def float_to_decimal(f):
    # http://docs.python.org/library/decimal.html#decimal-faq
    "Convert a floating point number to a Decimal with no loss of information"
    n, d = f.as_integer_ratio()
    numerator, denominator = decimal.Decimal(n), decimal.Decimal(d)
    ctx = decimal.Context(prec=60)
    result = ctx.divide(numerator, denominator)
    while ctx.flags[decimal.Inexact]:
        ctx.flags[decimal.Inexact] = False
        ctx.prec *= 2
        result = ctx.divide(numerator, denominator)
    return result 

def f(number, sigfig):
    assert(sigfig>0)
    try:
        d=decimal.Decimal(number)
    except TypeError:
        d=float_to_decimal(float(number))
    sign,digits,exponent=d.as_tuple()
    if len(digits) < sigfig:
        digits = list(digits)
        digits.extend([0] * (sigfig - len(digits)))    
    shift=d.adjusted()
    result=int(''.join(map(str,digits[:sigfig])))
    # Round the result
    if len(digits)>sigfig and digits[sigfig]>=5: result+=1
    result=list(str(result))
    # Rounding can change the length of result
    # If so, adjust shift
    shift+=len(result)-sigfig
    # reset len of result to sigfig
    result=result[:sigfig]
    if shift >= sigfig-1:
        # Tack more zeros on the end
        result+=['0']*(shift-sigfig+1)
    elif 0<=shift:
        # Place the decimal point in between digits
        result.insert(shift+1,'.')
    else:
        # Tack zeros on the front
        assert(shift<0)
        result=['0.']+['0']*(-shift-1)+result
    if sign:
        result.insert(0,'-')
    return ''.join(result)

def magic_format(num, locale="en_US", group_frac=True):
    sep = get_group_symbol(locale)
    if sep == get_group_symbol('fr_FR'): 
        sep = ' '
    else:
        sep = str(sep)
    dec = str(get_decimal_symbol(locale))

    n = float(('%E' % num)[:-4:])
    sigfig = len(str(n)) - (1 if '.' in str(n) else 0) 

    s = f(num,sigfig)

    if group_frac:
        ans = ""
        if '.' not in s:
            point = None
            new_d = ""
            new_s = s[::-1]
        else:
            point = s.index('.')
            new_d = s[point+1::]
            new_s = s[:point:][::-1]
        for idx,char in enumerate(new_d):
            ans += char
            if (idx+1)%3 == 0 and (idx+1) != len(new_d): 
                ans += sep
        else: ans = ans[::-1] + (dec if point != None else '')
        for idx,char in enumerate(new_s):
            ans += char
            if (idx+1)%3 == 0 and (idx+1) != len(new_s): 
                ans += sep 
        else:
            ans = ans[::-1]
    else:
        ans = s
    return ans

该代码块可以按如下方式使用:

>>> magic_format(num2, locale = 'fr_FR')
'0,000 000 000 000 000 000 000 123 456 0'
>>> magic_format(num2, locale = 'de_DE')
'0,000.000.000.000.000.000.000.123.456.0'
>>> magic_format(num2)
'0.000,000,000,000,000,000,000,123,456'
>>> f(num,6)
'12345600000000000000000'
>>> f(num2,6)
'0.000000000000000000000123456'

使用来自链接的 f 函数。

关于python - 如何在 python 中以长的、漂亮的圆角和本地化格式打印任何 float ,例如 1.234e22?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17113996/

相关文章:

python Pandas : Inserting new rows for date gaps in data

Python Pandas 错误标记数据

python - Plist一直加载不结束,满足条件也不退出

java - JDialog 窗口控制组件方向(从右到左)

android - DecimalFormat 不适用于 8233.4892578125 Double 值?

python - os.system ('TASKKILL/F/IM EXCEL.exe' ) 在 python 中

ruby-on-rails - 设置本地化文件中文本的样式 - 粗体、斜体等

ios - 添加可本地化字符串文件时出错

javascript - Number.prototype.toLocaleString() 和 Intl.NumberFormat.prototype.format 之间有什么关系?

python - 你可以格式化 pandas 整数以进行显示,例如 `pd.options.display.float_format` 用于 float 吗?