Python中将浮点数格式化为固定宽度的方法

3
我不关心小数点的位置,希望在需要显示最高精度时以科学计数法方式打印。然而,我希望适当四舍五入以保持固定宽度,不论左对齐还是右对齐都可以。
例如:
>>> my_format("{:10f}", 0.0000000456)
"  4.56e-08"
>>> my_format("{:10f}", 12.345678987654321)
" 12.345679"
#or "12.34567890" because significant digits
#but not "1.2346e+01", since it is less accurate than the above representations
>>> my_format("{:10f}", 12345678987654321)
"1.2345e+16"

编辑为了澄清例子,width格式说明符并没有提供固定宽度。它提供了一个最小宽度。如何获取一个固定宽度的表示形式?

5个回答

3

g格式说明符通常用于科学计数法,例如:

my_format = "{:.10g}".format

应该做正确的事情:

>>> list(my_format(v) for v in (0.0000000456, 12.345678987654321, 12345678987654321))
['4.56e-08', '12.34567899', '1.234567899e+16']

我后来意识到上述函数并不符合OP的要求。
根据@a_guest的有益评论,我得出了以下结论:
def my_format(v, length=10):
    n = length
    while n > 0:
        i = len('%#.*g' % (n, v))
        s = '%.*g' % (n + n - i, v)
        if len(s) <= length:
            return s
        n -= 1
    return s

我现在得到了['4.56e-08', '12.345679', '1.2346e+16'],这更接近于所需的内容。

我通过使用以下方式生成大量随机数进行了测试:

from random import uniform
def rnd_float():
    return uniform(-10, 10) * 10 ** (uniform(-1.6, 1.6) ** 11)

这个生成的数字看起来相当随意,但可以合理地接近我关心的分布。也就是说,大部分在1左右,但有些很小或很大的数字也有相当高的概率。

我已经将这些数字传递给my_format 100k次,并且得到了格式正确的数字。


哎呀,我刚意识到这个程序并没有做正确的事情。你想要将输出限制在10个字符内,而不是显示10位精度。我会进行更新。 - Sam Mason
@Scott 这是Python 3.6语法。如果您使用的是<= 3.5,则可以将其替换为'{{v:.{length}g}}'.format(length=length).format(v=v)(下一个也是类似)。 - a_guest
如果您想要兼容性,我建议使用格式;类似这样的:'%.*g' % (length, v)应该可以工作。 - Sam Mason
@SamMason 0.123456789怎么处理?它返回的结果没有变化,总长度12(>10)。 - a_guest
@SamMason 的问题在于第一个格式化字符串 {:.10g} 去掉了尾随零,因此多余的长度计算不准确。我刚刚发现你可以通过使用 {:#.10g} 保留尾随零 ("“#”选项会导致使用“备用形式”进行转换。[...] 此外,在 'g' 和 'G' 转换中,结果中不会删除尾随零。")。因此,您可以使用先前的解决方案,在第一个格式化字符串中添加 # 并保持第二个不变。 - a_guest
显示剩余2条评论

1
您需要的是一种维护显示字符数的方法。因此,请创建一个函数来执行此操作。
import decimal

# create a new context for this task
ctx = decimal.Context()

# 20 digits should be enough for everyone :D
ctx.prec = 20

def float_to_str(f):
    """
    Convert the given float to a string,
    without resorting to scientific notation
    """
    d1 = ctx.create_decimal(repr(f))
    return format(d1, 'f')

print float_to_str(0.0000000456)
print float_to_str(12.345678987654321)

这是一个聪明的方法,虽然它忽略了科学计数法。对于任何感兴趣但不想复制代码的人来说,结果如下: `# 0.0000000456

12.34567898765`

- Dan

1
你可以测试使用{:f}{:e}格式化数字,然后解析结果字符串以查看哪个更合适:
import re


def format(spec, number):
    def _decimal(s):
        return re.search(r'^\s*-?([0-9]+(\.[0-9]+)?)', s).group(1)

    def _significant_digits(s):
        return _decimal(s).rstrip('0')

    def _fit_to_width(s):
        decimal, significant = _decimal(s), _significant_digits(s)
        stripped = s.replace(decimal, significant)
        excess = len(stripped) - spec
        if excess > 0:
            # Replace excess digits from the right.
            significant = significant[::-1].replace(
                re.match(
                    r'[0-9]{{,{}}}'.format(excess),
                    significant[::-1]
                ).group(0), ''
            )[::-1]
        return s.replace(decimal, significant)

    formats = [
        _fit_to_width('{{:{}f}}'.format(spec).format(number)),
        _fit_to_width('{{:{}e}}'.format(spec).format(number)),
    ]
    return max(
        filter(
            lambda x: len(x[0]) <= spec,
            [(f, len(_significant_digits(f))) for f in formats]
        ),
        key=lambda x: x[-1]
    )[0].rjust(spec)


print(format(10, 0.0000000456))
print(format(10, 12.345678987654321))
print(format(10, 12345678987654321))

#   4.56e-08
#  12.345679
# 1.2345e+16

0

这似乎有效。可以不使用numpy,但四舍五入需要更多的工作。

import numpy as np

SCIENTIFIC_NOTATION_WIDTH = 4

def my_format(number, n):
    places = np.log10(np.abs(number))
    if abs(places) == np.inf:
        places = 0
    highest_place = -int(places)
    if 1 <= highest_place < 3:
        rounded = np.round(number, n - highest_place - 1)
    elif highest_place >= 3:
        rounded = np.round(number, highest_place + n - 5)
    elif -n < highest_place < 1:
        rounded = np.round(number, n + highest_place - 2)
    else:
        rounded = np.round(number, highest_place + n - 6)

    return "{{:{}.{}g}}".format(n,n).format(rounded)
print(my_format(12345678987654321, 10))
print(my_format(12.345678987654321,10))
print(my_format(0.0000000456,10))

#1.2346e+16
# 12.345679
#  4.56e-08

0
以下代码可以完成任务,如果提供的长度对于给定的数字太小,则会产生更长的输出,例如仅使用四个字符无法表示4e100
import math

def float_fmt(x, length=10):
  reduce_fp_by = 0

  if abs(x) > 0.0:
    abs_power = abs(math.floor(math.log10(abs(x))))
    if abs_power > 0:
      power_len = max(1, math.floor(math.log10(abs_power)))
      if abs_power > 4:
        reduce_fp_by = power_len + 3

  fp_n = max(0, length - reduce_fp_by - 1)
  fmt = ('%#.'+str(fp_n)+'g')
  return fmt % x

for l in [6, 8, 10, 13]:
  for bf in [0, 4.2, 4.222, 4.22222, 4.2222222]:
    for p in [-500, -100, -50, -5, 50, 100]:
      f = bf * (10.0 ** p)
      print(float_fmt(f, l), len(float_fmt(f, l)))

生成

0.0000 6
0.0000 6
0.0000 6
0.0000 6
0.0000 6
0.0000 6
0.0000 6
4.e-100 7
4.e-50 6
4.e-05 6
4.e+50 6
4.e+100 7
0.0000 6
4.e-100 7
4.e-50 6
4.e-05 6
4.e+50 6
4.e+100 7
0.0000 6
4.e-100 7
4.e-50 6
4.e-05 6
4.e+50 6
4.e+100 7
0.0000 6
4.e-100 7
4.e-50 6
4.e-05 6
4.e+50 6
4.e+100 7
0.000000 8
0.000000 8
0.000000 8
0.000000 8
0.000000 8
0.000000 8
0.000000 8
4.2e-100 8
4.20e-50 8
4.20e-05 8
4.20e+50 8
4.2e+100 8
0.000000 8
4.2e-100 8
4.22e-50 8
4.22e-05 8
4.22e+50 8
4.2e+100 8
0.000000 8
4.2e-100 8
4.22e-50 8
4.22e-05 8
4.22e+50 8
4.2e+100 8
0.000000 8
4.2e-100 8
4.22e-50 8
4.22e-05 8
4.22e+50 8
4.2e+100 8
0.00000000 10
0.00000000 10
0.00000000 10
0.00000000 10
0.00000000 10
0.00000000 10
0.00000000 10
4.200e-100 10
4.2000e-50 10
4.2000e-05 10
4.2000e+50 10
4.200e+100 10
0.00000000 10
4.222e-100 10
4.2220e-50 10
4.2220e-05 10
4.2220e+50 10
4.222e+100 10
0.00000000 10
4.222e-100 10
4.2222e-50 10
4.2222e-05 10
4.2222e+50 10
4.222e+100 10
0.00000000 10
4.222e-100 10
4.2222e-50 10
4.2222e-05 10
4.2222e+50 10
4.222e+100 10
0.00000000000 13
0.00000000000 13
0.00000000000 13
0.00000000000 13
0.00000000000 13
0.00000000000 13
0.00000000000 13
4.200000e-100 13
4.2000000e-50 13
4.2000000e-05 13
4.2000000e+50 13
4.200000e+100 13
0.00000000000 13
4.222000e-100 13
4.2220000e-50 13
4.2220000e-05 13
4.2220000e+50 13
4.222000e+100 13
0.00000000000 13
4.222220e-100 13
4.2222200e-50 13
4.2222200e-05 13
4.2222200e+50 13
4.222220e+100 13
0.00000000000 13
4.222222e-100 13
4.2222222e-50 13
4.2222222e-05 13
4.2222222e+50 13
4.222222e+100 13

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