others - python 将浮点数转换为不带科学符号和错误精度的字符串

我想打印一些浮点数,以便它们总是以十进制形式书写(例如,12345000000000000000000.0或0.000000000000012345,不是科学记数法,但是,我想保留15.7十进制数字的精度而不是更多位。

众所周知,如果指数大于15或小于-4,浮点数的repr用科学记数法写成,

 

>>> 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仍然会丢失相对精度:

 

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

所以

时间:

不幸的是,似乎没有float.__format__的新样式格式支持这个,float的默认格式与repr相同; 在f标志中,默认情况下有6个小数位:

 

>>> format(0.0000000005, 'f')


'0.000000'



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

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

因此,我们可以创建一个简单的实用工具函数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)并不严格匹配所预期的格式; 返回不带尾随.0420000000000000000

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

 

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,并且与十进制上下文运算的解决方案进行比较,看起来字符串操作实际上是相当快的,编辑: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))



如果可以通过调用浮点号上的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)长度的上限,实际的最大supremum ,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'



有趣的问题,为问题添加一些内容,这里有一些比较@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)



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

  • 对于反斜杠,将看到类似'-000'或000'的字符串',

即使我牺牲一点速度,我更愿意保持一致性,视你的使用情况而定。

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



如果这对您有用,请告诉我。

...