将浮点数转换为位置格式的字符串(没有科学记数法和错误精度)

新手上路,请多包涵

我想打印一些浮点数,以便它们始终以十进制形式写入(例如 12345000000000000000000.00.000000000000012345 ,而不是 科学记数法,但我希望结果为有一个 IEEE 754 double 的 ~15.7 有效数字,仅此而已。

我想要的是 _理想情况下_,结果是位置十进制格式的 最短 字符串,在转换为 float 时仍会产生相同的值

众所周知,a reprfloat 如果指数大于15,或者小于-4,则用科学记数法写成:

 >>> n = 0.000000054321654321
>>> n
5.4321654321e-08  # scientific notation

如果使用 str ,结果字符串再次采用科学记数法:

 >>> str(n)
'5.4321654321e-08'


有人建议我可以使用 formatf 标志和足够的精度来摆脱科学记数法:

 >>> format(0.00000005, '.20f')
'0.00000005000000000000'

它适用于该数字,尽管它有一些额外的尾随零。但是 .1 的相同格式失败了,它给出了超出 float 实际机器精度的十进制数字:

 >>> format(0.1, '.20f')
'0.10000000000000000555'

如果我的号码是 4.5678e-20 ,使用 .20f 仍然会失去相对精度:

 >>> format(4.5678e-20, '.20f')
'0.00000000000000000005'

因此 这些方法不符合我的要求


这就引出了一个问题:以十进制格式打印任意浮点数的最简单且性能最好的方法是什么,其数字与 repr(n) (或 str(n) 在 Python 中一样) 3) ,但始终使用十进制格式,而不是科学记数法。

也就是说,例如将浮点值 0.00000005 转换为字符串 '0.00000005' 的函数或操作; 0.1'0.1' ; 420000000000000000.0 to '420000000000000000.0' or 420000000000000000 and formats the float value -4.5678e-5 as '-0.000045678' .


赏金期之后:似乎至少有 2 种可行的方法,正如 Karin 所证明的,与我在 Python 2 上的初始算法相比,使用字符串操作可以显着提高速度。

因此,

由于我主要是在 Python 3 上开发,我会接受我自己的答案,并将赏金奖励给 Karin。

原文由 Antti Haapala – Слава Україні 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 606
2 个回答

不幸的是,似乎即使是带有 float.__format__ 的新型格式也不支持这一点。 float repr ;并带有 f 标志,默认情况下有 6 个小数位:

 >>> format(0.0000000005, 'f')
'0.000000'


然而,有一种方法可以得到想要的结果——不是最快的,但相对简单:

  • 首先使用 str()repr() 将浮点数转换为字符串
  • 然后从该字符串创建一个新的 Decimal 实例。
  • Decimal.__format__ 支持 f 给出所需结果的标志,并且与 float 不同,它打印实际精度而不是精度默认值。

因此我们可以制作一个简单的效用函数 float_to_str

 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')

必须注意不要使用全局十进制上下文,因此为此函数构造了一个新的上下文。这是最快的方法;另一种方法是使用 decimal.local_context 但它会更慢,为每次转换创建一个新的线程本地上下文和上下文管理器。

此函数现在返回包含尾数中所有可能数字的字符串,四舍五入为 最短的等效表示形式

 >>> float_to_str(0.1)
'0.1'
>>> float_to_str(0.00000005)
'0.00000005'
>>> float_to_str(420000000000000000.0)
'420000000000000000'
>>> float_to_str(0.000000000123123123123123123123)
'0.00000000012312312312312313'

最后的结果四舍五入到最后一位

正如@Karin 指出的那样, float_to_str(420000000000000000.0) 与预期格式不严格匹配;它返回 420000000000000000 没有尾随 .0

原文由 Antti Haapala – Слава Україні 发布,翻译遵循 CC BY-SA 3.0 许可协议

如果您对科学记数法的精度感到满意,那么我们是否可以采用简单的字符串操作方法?也许它不是很聪明,但它似乎有效(通过了你提出的所有用例),而且我认为它是可以理解的:

 def float_to_str(f):
    float_string = repr(f)
    if 'e' in float_string:  # detect scientific notation
        digits, exp = float_string.split('e')
        digits = digits.replace('.', '').replace('-', '')
        exp = int(exp)
        zero_padding = '0' * (abs(int(exp)) - 1)  # minus 1 for decimal point in the sci notation
        sign = '-' if f < 0 else ''
        if exp > 0:
            float_string = '{}{}{}.0'.format(sign, digits, zero_padding)
        else:
            float_string = '{}0.{}{}'.format(sign, zero_padding, digits)
    return float_string

n = 0.000000054321654321
assert(float_to_str(n) == '0.000000054321654321')

n = 0.00000005
assert(float_to_str(n) == '0.00000005')

n = 420000000000000000.0
assert(float_to_str(n) == '420000000000000000.0')

n = 4.5678e-5
assert(float_to_str(n) == '0.000045678')

n = 1.1
assert(float_to_str(n) == '1.1')

n = -4.5678e-5
assert(float_to_str(n) == '-0.000045678')

性能

我担心这种方法可能太慢,所以我运行了 timeit 并与 OP 的十进制上下文解决方案进行了比较。看起来字符串操作实际上要快得多。 编辑:它似乎只在 Python 2 中快得多。在 Python 3 中,结果相似,但使用十进制方法稍微快一些。

结果

  • Python 2:使用 ctx.create_decimal()2.43655490875

  • Python 2:使用字符串操作: 0.305557966232

  • Python 3:使用 ctx.create_decimal()0.19519368198234588

  • Python 3:使用字符串操作: 0.2661344590014778

这是时间代码:

 from timeit import timeit

CODE_TO_TIME = '''
float_to_str(0.000000054321654321)
float_to_str(0.00000005)
float_to_str(420000000000000000.0)
float_to_str(4.5678e-5)
float_to_str(1.1)
float_to_str(-0.000045678)
'''
SETUP_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')
'''
SETUP_2 = '''
def float_to_str(f):
    float_string = repr(f)
    if 'e' in float_string:  # detect scientific notation
        digits, exp = float_string.split('e')
        digits = digits.replace('.', '').replace('-', '')
        exp = int(exp)
        zero_padding = '0' * (abs(int(exp)) - 1)  # minus 1 for decimal point in the sci notation
        sign = '-' if f < 0 else ''
        if exp > 0:
            float_string = '{}{}{}.0'.format(sign, digits, zero_padding)
        else:
            float_string = '{}0.{}{}'.format(sign, zero_padding, digits)
    return float_string
'''

print(timeit(CODE_TO_TIME, setup=SETUP_1, number=10000))
print(timeit(CODE_TO_TIME, setup=SETUP_2, number=10000))

原文由 Karin 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题