将float转换为字符串,没有科学记数法和错误精度

时间:2016-08-09 10:01:59

标签: python python-3.x floating-point number-formatting python-2.x

我想要打印一些浮点数,以便它们始终以十进制形式(例如12345000000000000000000.00.000000000000012345,而不是scientific notation,但是我是&#39}。 d想要保持精度的15.7十进制数字而不再存在。

众所周知,如果指数大于15或小于-4,则repr的{​​{1}}用科学记数法写成:

float

如果使用>>> n = 0.000000054321654321 >>> n 5.4321654321e-08 # scientific notation ,则再次生成的字符串为科学记数法:

str

有人建议我可以使用带有>>> str(n) '5.4321654321e-08' 标记的format和足够的精度来摆脱科学记数法:

f

它适用于该数字,但它有一些额外的尾随零。但是>>> format(0.00000005, '.20f') '0.00000005000000000000' 的相同格式失败,它给出了超出浮点数的实际机器精度的十进制数字:

.1

如果我的号码为>>> format(0.1, '.20f') '0.10000000000000000555' ,使用4.5678e-20仍会失去相对精确度:

.20f

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

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

即,例如将浮点值>>> format(4.5678e-20, '.20f') '0.00000000000000000005' 转换为字符串0.00000005的函数或操作; '0.00000005'0.1; '0.1'420000000000000000.0'420000000000000000.0'并将浮点值420000000000000000格式化为-4.5678e-5

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

因此,

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

6 个答案:

答案 0 :(得分:35)

不幸的是,即使是decimal的新式格式也不支持这一点。 float.__format__的默认格式与float的格式相同;并且repr标志默认有6个小数位数:

f

然而,有一个黑客可以获得理想的结果 - 不是最快的结果,而是相对简单:

  • 首先使用>>> format(0.0000000005, 'f') '0.000000' str()
  • 将浮动转换为字符串
  • 然后从该字符串创建一个新的Decimal实例。
  • repr()支持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') ,但速度较慢,为每次转换创建一个新的线程局部上下文和上下文管理器。

此函数现在返回包含尾数中所有可能数字的字符串,四舍五入到shortest equivalent representation

decimal.local_context

最后一个结果是在最后一位数字

正如@Karin所说,>>> 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' 与预期的格式并不严格匹配;它会返回float_to_str(420000000000000000.0)而不会尾随420000000000000000

答案 1 :(得分:24)

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

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

答案 2 :(得分:4)

从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用于科学表示法,并且两个函数都提供了可选参数来自定义诸如舍入和修整零之类的东西。

答案 3 :(得分:2)

如果您准备通过在浮点数上调用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'

答案 4 :(得分:0)

有趣的问题是,要在问题中添加更多内容,这是一个比较@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,你会看到像'-000'或'000'
  • 这样的字符串
  • 有了Harolds,你会看到像''
  • 这样的字符串

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

答案 5 :(得分:0)

我认为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

请告诉我这是否适合您。