Python中截断浮点数

2024-04-25 23:54:33 发布

您现在位置:Python中文网/ 问答频道 /正文

我想从浮点数中删除数字,使其在点后具有固定数量的数字,例如:

1.923328437452 -> 1.923

我需要作为字符串输出到另一个函数,而不是打印。

另外,我想忽略丢失的数字,而不是四舍五入。


Tags: 函数字符串数量数字浮点数
3条回答

round的结果是一个浮点数,因此请注意(示例来自Python 2.6):

>>> round(1.923328437452, 3)
1.923
>>> round(1.23456, 3)
1.2350000000000001

当使用格式化字符串时,您会更好:

>>> "%.3f" % 1.923328437452
'1.923'
>>> "%.3f" % 1.23456
'1.235'
round(1.923328437452, 3)

Python's documentation on the standard types。你需要向下滚动一点才能进入圆形函数。实际上,第二个数字表示四舍五入到多少个小数位。

首先,函数,对于那些只想复制和粘贴代码的人:

def truncate(f, n):
    '''Truncates/pads a float f to n decimal places without rounding'''
    s = '{}'.format(f)
    if 'e' in s or 'E' in s:
        return '{0:.{1}f}'.format(f, n)
    i, p, d = s.partition('.')
    return '.'.join([i, (d+'0'*n)[:n]])

这在Python2.7和3.1+中有效。对于较旧的版本,不可能获得相同的“智能舍入”效果(至少,不是没有很多复杂的代码),但是在截断之前舍入到12个小数位将在大部分时间内起作用:

def truncate(f, n):
    '''Truncates/pads a float f to n decimal places without rounding'''
    s = '%.12f' % f
    i, p, d = s.partition('.')
    return '.'.join([i, (d+'0'*n)[:n]])

解释

底层方法的核心是以全精度将值转换为字符串,然后切掉超出所需字符数的所有内容。后面的步骤很简单;可以通过字符串操作来完成

i, p, d = s.partition('.')
'.'.join([i, (d+'0'*n)[:n]])

或者decimal模块

str(Decimal(s).quantize(Decimal((0, (1,), -n)), rounding=ROUND_DOWN))

第一步,转换成一个字符串,是相当困难的,因为有一些浮点文字对(即你在源代码中写的东西),它们都产生相同的二进制表示,但应该以不同的方式截断。例如,考虑0.3和0.299999999999998。如果您在Python程序中编写0.3,编译器会使用IEEE浮点格式将其编码为位序列(假设是64位浮点)

0011111111010011001100110011001100110011001100110011001100110011

这是最接近0.3的值,可以精确地表示为IEEE浮点。但是,如果您在Python程序中编写0.29999999999999998,编译器会将其转换为与完全相同的值。在一种情况下,您的意思是它被截断为0.3,而在另一种情况下,您的意思是它被截断为0.2,但是Python只能给出一个答案。这是Python的一个基本限制,或者说是任何没有延迟计算的编程语言。截断函数只能访问存储在计算机内存中的二进制值,而不能访问您在源代码中实际键入的字符串。1

如果再次使用IEEE 64位浮点格式将位序列解码为十进制数,则

0.2999999999999999888977697537484345957637...

所以一个天真的实现会产生0.2,尽管这可能不是你想要的。有关浮点表示错误的详细信息,see the Python tutorial

使用非常接近一个整数但故意不等于该整数的浮点值是非常罕见的。因此,在截断时,从所有可能与内存中的值相对应的十进制表示中选择“最好的”十进制表示可能是有意义的。Python2.7及更高版本(但不是3.0)包含一个sophisticated algorithm to do just that,我们可以通过默认的字符串格式化操作访问它。

'{}'.format(f)

唯一需要注意的是,这就像一个g格式规范,即如果数字足够大或足够小,它使用指数符号(1.23e+4)。所以这个方法必须抓住这个问题,并以不同的方式处理它。有一些情况下,使用f格式规范反而会导致问题,例如试图将3e-10截断为28位精度(它产生0.0000000002999999999999999980),我还不确定如何最好地处理这些问题。

如果实际上使用非常接近整数但故意不等于整数的floats(例如0.29999999999999998或99.95999999999994),这将产生一些误报,即它将舍入您不希望舍入的数字。在这种情况下,解决方案是指定固定精度。

'{0:.{1}f}'.format(f, sys.float_info.dig + n + 2)

这里使用的精度位数并不重要,它只需要足够大,以确保在字符串转换中执行的任何舍入不会将值“提升”为其漂亮的十进制表示。我认为sys.float_info.dig + n + 2在所有情况下都是足够的,但如果不是这样的话2可能就必须增加,这样做并不有害。

在早期版本的Python(高达2.6或3.0)中,浮点数格式要粗糙得多,通常会产生如下内容

>>> 1.1
1.1000000000000001

如果这是你的情况,如果你想使用“nice”十进制对于截断的表示,您所能做的(据我所知)就是选择一些位数,小于可以用float表示的全精度,并在截断之前将该位数舍入到该位数。一个典型的选择是12

'%.12f' % f

但是你可以调整这个以适应你正在使用的数字。


1好吧。。。我撒谎了。技术上,您可以指示Python重新解析它自己的源代码,并提取与传递给truncation函数的第一个参数对应的部分。如果该参数是浮点文字,则只需将其截断小数点后的某个位置,然后返回。但是,如果参数是变量,则此策略不起作用,这使得它相当无用。以下仅为娱乐价值:

def trunc_introspect(f, n):
    '''Truncates/pads the float f to n decimal places by looking at the caller's source code'''
    current_frame = None
    caller_frame = None
    s = inspect.stack()
    try:
        current_frame = s[0]
        caller_frame = s[1]
        gen = tokenize.tokenize(io.BytesIO(caller_frame[4][caller_frame[5]].encode('utf-8')).readline)
        for token_type, token_string, _, _, _ in gen:
            if token_type == tokenize.NAME and token_string == current_frame[3]:
                next(gen) # left parenthesis
                token_type, token_string, _, _, _ = next(gen) # float literal
                if token_type == tokenize.NUMBER:
                    try:
                        cut_point = token_string.index('.') + n + 1
                    except ValueError: # no decimal in string
                        return token_string + '.' + '0' * n
                    else:
                        if len(token_string) < cut_point:
                            token_string += '0' * (cut_point - len(token_string))
                        return token_string[:cut_point]
                else:
                    raise ValueError('Unable to find floating-point literal (this probably means you called {} with a variable)'.format(current_frame[3]))
                break
    finally:
        del s, current_frame, caller_frame

将其泛化以处理传入变量的情况似乎是一个失败的原因,因为在找到赋予变量值的浮点文字之前,必须向后跟踪程序的执行过程。如果真的有。大多数变量将从用户输入或数学表达式初始化,在这种情况下,二进制表示就是全部。

相关问题 更多 >