如何截断浮点值?

新手上路,请多包涵

我想从浮点数中删除数字以在点后具有固定位数,例如:

 1.923328437452 → 1.923

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

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

原文由 Joan Venge 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 944
2 个回答

首先,功能,对于那些只想要一些复制和粘贴代码的人:

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

这在 Python 2.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.29999999999999998。如果您在 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 即使这可能不是你想要的。有关浮点表示错误的更多信息, 请参阅 Python 教程

使用非常接近整数但 故意 不等于该整数的浮点值是非常罕见的。因此,在截断时,从所有可能对应于内存值的十进制表示中选择“最好的”十进制表示可能是有意义的。 Python 2.7 及更高版本(但不是 3.0)包含一个 复杂的算法来做到这一点,我们可以通过默认的字符串格式化操作来访问它。

 '{}'.format(f)

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

如果您实际上 正在 使用非常接近整数但故意不等于它们的 float s(如 0.299999999999999998 或 99.959999999999994),这将产生一些误报,即它会四舍五入您没有不想四舍五入。在这种情况下,解决方案是指定一个固定的精度。

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

如果这是您的情况,如果您 确实 想使用“不错的”十进制表示进行截断,那么您所能做的(据我所知)就是选择一些位数,小于 float 表示的完整精度 --- ,并在截断之前将数字四舍五入到那么多位数。一个典型的选择是 12,

 '%.12f' % f

但您可以调整它以适合您使用的数字。


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

 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

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

原文由 David Z 发布,翻译遵循 CC BY-SA 3.0 许可协议

round(1.923328437452, 3)

请参阅 Python 关于标准类型的文档。您需要向下滚动一点才能找到圆形功能。本质上,第二个数字表示要四舍五入到多少位小数。

原文由 Teifion 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题