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

Ant*_*ala 58 python floating-point python-2.x number-formatting python-3.x

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

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

>>> n = 0.000000054321654321
>>> n
5.4321654321e-08  # scientific notation
Run Code Online (Sandbox Code Playgroud)

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

>>> str(n)
'5.4321654321e-08'
Run Code Online (Sandbox Code Playgroud)

有人建议我可以用strformat标志和足够的精度摆脱了科学计数法:

>>> format(0.00000005, '.20f')
'0.00000005000000000000'
Run Code Online (Sandbox Code Playgroud)

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

>>> format(0.1, '.20f')
'0.10000000000000000555'
Run Code Online (Sandbox Code Playgroud)

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

>>> format(4.5678e-20, '.20f')
'0.00000000000000000005'
Run Code Online (Sandbox Code Playgroud)

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


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

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


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

从而,

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

Ant*_*ala 44

不幸的是,似乎甚至没有float.__format__支持这种新式格式化.floats 的默认格式与with相同repr; 并且带有f标志默认有6个小数位数:

>>> format(0.0000000005, 'f')
'0.000000'
Run Code Online (Sandbox Code Playgroud)

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

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

因此我们可以做一个简单的效用函数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')
Run Code Online (Sandbox Code Playgroud)

必须注意不要使用全局小数上下文,因此为此函数构造了新的上下文.这是最快的方式; 另一种方法是使用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'
Run Code Online (Sandbox Code Playgroud)

最后一个结果在最后一位数字处舍入

正如@Karin所说,float_to_str(420000000000000000.0)并不严格符合预期的格式; 它返回420000000000000000没有尾随.0.

  • 你为什么不用[`decimal.localcontext`](https://docs.python.org/3.5/library/decimal.html#decimal.localcontext)?`with localcontext()as ctx:ctx.prec = 20; d1 =十进制(str(f))` (2认同)

Kar*_*rin 32

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

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')
Run Code Online (Sandbox Code Playgroud)

表现:

我担心这种方法可能太慢,所以我运行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))
Run Code Online (Sandbox Code Playgroud)

  • 啊,从现在的文档中可以看出这一点.很高兴知道!我已经更新了我的计时代码,现在看起来更干净了,谢谢你:) (2认同)
  • 我一直很惊讶天真的"只是字符串化"方法的工作方式,有时甚至比其他情况更好. (2认同)

use*_*ica 13

从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'
Run Code Online (Sandbox Code Playgroud)

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

  • 嘿,那很好。如果 NumPy 不需要,则不实用,但如果是,这绝对是应该使用的。 (2认同)

guk*_*off 5

如果您准备通过调用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'
Run Code Online (Sandbox Code Playgroud)

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

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

>> float_to_string(0.10101010101010101010101010101)
'0.10101010101'
Run Code Online (Sandbox Code Playgroud)

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

import decimal

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

它不会丢失调用时丢失的精度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'
Run Code Online (Sandbox Code Playgroud)

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

>> float_to_string(0.1, precision=10000)
'0.1000000000000000055511151231257827021181583404541015625'
Run Code Online (Sandbox Code Playgroud)

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

>> float_to_string(100)
'100'
Run Code Online (Sandbox Code Playgroud)