繁体   English   中英

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

[英]Convert float to string in positional format (without scientific notation and false precision)

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

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

众所周知,如果指数大于 15 或小于 -4,则floatrepr以科学计数法编写:

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

如果使用str ,则生成的字符串再次采用科学计数法:

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

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

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

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

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

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

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

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


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

也就是说,例如将浮点值0.00000005转换为字符串'0.00000005'的函数或操作; 0.1'0.1' ; 420000000000000000.0'420000000000000000.0'420000000000000000并将浮点值-4.5678e-5格式化为'-0.000045678'


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

因此,

由于我主要在 Python 3 上进行开发,因此我将接受我自己的答案,并将奖励 Karin 赏金。

不幸的是,似乎连带有float.__format__的新型格式都不支持这一点。 float s 的默认格式与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

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

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

从 NumPy 1.14.0 开始,您可以只使用numpy.format_float_positional 例如,针对您的问题的输入运行:

>>> numpy.format_float_positional(0.000000054321654321)
'0.000000054321654321'
>>> numpy.format_float_positional(0.00000005)
'0.00000005'
>>> numpy.format_float_positional(0.1)
'0.1'
>>> numpy.format_float_positional(4.5678e-20)
'0.000000000000000000045678'

numpy.format_float_positional使用 Dragon4 算法以位置格式生成最短的十进制表示,该表示可以往返返回到原始浮点输入。 还有numpy.format_float_scientific用于科学记数法,这两个函数都提供了可选参数来自定义诸如舍入和修剪零之类的东西。

如果您准备通过在浮点数上调用str()来任意丢失精度,那么这是要走的路:

import decimal

def float_to_string(number, precision=20):
    return '{0:.{prec}f}'.format(
        decimal.Context(prec=100).create_decimal(str(number)),
        prec=precision,
    ).rstrip('0').rstrip('.') or '0'

它不包括全局变量,并允许您自己选择精度。 选择十进制精度 100 作为str(float)长度的上限。 实际的上限要低得多。 or '0'部分用于小数字和零精度的情况。

请注意,它仍然有其后果:

>> float_to_string(0.10101010101010101010101010101)
'0.10101010101'

否则,如果精度很重要, format就可以了:

import decimal

def float_to_string(number, precision=20):
    return '{0:.{prec}f}'.format(
        number, prec=precision,
    ).rstrip('0').rstrip('.') or '0'

它不会错过调用str(f)时丢失的精度。 or

>> float_to_string(0.1, precision=10)
'0.1'
>> float_to_string(0.1)
'0.10000000000000000555'
>>float_to_string(0.1, precision=40)
'0.1000000000000000055511151231257827021182'

>>float_to_string(4.5678e-5)
'0.000045678'

>>float_to_string(4.5678e-5, precision=1)
'0'

无论如何,最大小数位是有限的,因为float类型本身有其限制,不能表达真正的长浮点数:

>> float_to_string(0.1, precision=10000)
'0.1000000000000000055511151231257827021181583404541015625'

此外,整数按原样格式化。

>> float_to_string(100)
'100'

我认为rstrip可以完成工作。

a=5.4321654321e-08
'{0:.40f}'.format(a).rstrip("0") # float number and delete the zeros on the right
# '0.0000000543216543210000004442039220863003' # there's roundoff error though

让我知道这是否适合你。

有趣的问题,为问题添加更多内容,这里有一个比较@Antti Haapala 和@Harold 解决方案输出的小测试:

import decimal
import math

ctx = decimal.Context()


def f1(number, prec=20):
    ctx.prec = prec
    return format(ctx.create_decimal(str(number)), 'f')


def f2(number, prec=20):
    return '{0:.{prec}f}'.format(
        number, prec=prec,
    ).rstrip('0').rstrip('.')

k = 2*8

for i in range(-2**8,2**8):
    if i<0:
        value = -k*math.sqrt(math.sqrt(-i))
    else:
        value = k*math.sqrt(math.sqrt(i))

    value_s = '{0:.{prec}E}'.format(value, prec=10)

    n = 10

    print ' | '.join([str(value), value_s])
    for f in [f1, f2]:
        test = [f(value, prec=p) for p in range(n)]
        print '\t{0}'.format(test)

对于所有情况,它们都没有给出“一致”的结果。

  • 使用 Anti's,您会看到像 '-000' 或 '000' 这样的字符串
  • 使用 Harolds,您会看到类似 '' 的字符串

即使我牺牲一点速度,我也更喜欢一致性。 取决于您想为您的用例假设哪些权衡。

使用格式(浮点数,'.f'):

old = 0.00000000000000000000123
if str(old).__contains__('e-'):
    float_length = str(old)[-2:]
    new=format(old,'.'+str(float_length)+'f')
    print(old)
    print(new)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM