在Python中优雅地表示浮点数
我想把一个浮点数表示成一个字符串,四舍五入到一定的有效数字,并且不使用科学计数法。简单来说,我想显示任何浮点数,并确保它“看起来不错”。
这个问题有几个方面:
- 我需要能够指定有效数字的数量。
- 有效数字的数量需要是可变的,这个用字符串格式化操作符是做不到的。[编辑] 我被纠正了;字符串格式化操作符可以做到这一点。
- 我需要它按照人们的期望来四舍五入,而不是像1.999999999999这样的结果。
我找到了一种方法来实现这个,虽然看起来有点绕,而且不是完全完美的。(最大精度是15个有效数字。)
>>> def f(number, sigfig):
return ("%.15f" % (round(number, int(-1 * floor(log10(number)) + (sigfig - 1))))).rstrip("0").rstrip(".")
>>> print f(0.1, 1)
0.1
>>> print f(0.0000000000368568, 2)
0.000000000037
>>> print f(756867, 3)
757000
有没有更好的方法来做到这一点?为什么Python没有内置的函数来处理这个?
4 个回答
0
这里有一段代码,用来根据给定的误差范围来格式化一个数值。
from math import floor, log10, round
def sigfig3(v, errplus, errmin):
i = int(floor(-log10(max(errplus,errmin)) + 2))
if i > 0:
fmt = "%%.%df" % (i)
return "{%s}^{%s}_{%s}" % (fmt % v,fmt % errplus, fmt % errmin)
else:
return "{%d}^{%d}_{%d}" % (round(v, i),round(errplus, i), numpy.round(i))
示例:
5268685 (+1463262,-2401422) becomes 5300000 (+1500000,-2400000)
0.84312 +- 0.173124 becomes 0.84 +- 0.17
6
如果你想要更精确的小数计算,就需要使用 decimal
模块,这个模块是 Python 标准库的一部分,可以在这里找到详细信息:Python 标准库。
>>> import decimal
>>> d = decimal.Decimal('0.0000000000368568')
>>> print '%.15f' % d
0.000000000036857
8
看起来没有现成的方法可以让你做到这两点:第一,打印出小数点后第15位之后的浮点数;第二,不使用科学计数法。所以我们只能手动处理字符串了。
下面我使用了 decimal
模块来提取浮点数的小数部分。float_to_decimal
这个函数是用来把浮点数转换成 Decimal
对象的。直接用 decimal.Decimal(str(f))
这种方式是不对的,因为 str(f)
可能会丢失重要的数字。
float_to_decimal
是从 decimal 模块的文档 中提取的。
一旦我们得到了小数部分的整数元组,下面的代码就会做一些简单的事情:去掉多余的有效数字,如果需要的话进行四舍五入,把这些数字拼接成一个字符串,加上符号,适当地在左边或右边放上小数点和零。
在最后,你会看到我用来测试 f
函数的一些例子。
import decimal
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):
# http://stackoverflow.com/questions/2663612/nicely-representing-a-floating-point-number-in-python/2663623#2663623
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)
if __name__=='__main__':
tests=[
(0.1, 1, '0.1'),
(0.0000000000368568, 2,'0.000000000037'),
(0.00000000000000000000368568, 2,'0.0000000000000000000037'),
(756867, 3, '757000'),
(-756867, 3, '-757000'),
(-756867, 1, '-800000'),
(0.0999999999999,1,'0.1'),
(0.00999999999999,1,'0.01'),
(0.00999999999999,2,'0.010'),
(0.0099,2,'0.0099'),
(1.999999999999,1,'2'),
(1.999999999999,2,'2.0'),
(34500000000000000000000, 17, '34500000000000000000000'),
('34500000000000000000000', 17, '34500000000000000000000'),
(756867, 7, '756867.0'),
]
for number,sigfig,answer in tests:
try:
result=f(number,sigfig)
assert(result==answer)
print(result)
except AssertionError:
print('Error',number,sigfig,result,answer)