关于浮点格式,有一些existing questions,但我认为没有一个可以回答以下问题。
我正在寻找一种以长的,很好的四舍五入和本地化的格式打印大型浮子的方法:
>>> 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
不存在。 ;-)我该如何实施?细节
以下是几种打印浮点数的方法。它们都不产生上面的输出:
>>> 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精确地存储为浮点数,有必要的舍入误差(这解释了上面的奇数输出)。但是,由于
str
,repr
和"%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)
精确表示所需的数字,但是每当尝试显示它时,它要么很短,要么在打印之前转换为错误的浮点数。但是,如果我将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-22
或0.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中以长整形且本地化的格式打印任何浮点数(如1.234e22)?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/17113996/