在Python中截断浮点数

Joa*_*nge 100 python floating-point

我想从浮点数中删除数字,使点后面有一个固定的位数,如:

1.923328437452 -> 1.923
Run Code Online (Sandbox Code Playgroud)

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

此外,我想忽略丢失的数字,而不是围绕它们.

Tei*_*ion 143

round(1.923328437452, 3)
Run Code Online (Sandbox Code Playgroud)

请参阅Python有关标准类型的文档.你需要向下滚动一下才能进入圆形函数.基本上第二个数字表示将它四舍五入到的小数位数.

  • 我的意思是四舍五入不是我需要的.我需要截断,这是不同的. (45认同)
  • 这是一个错误的解决方案的很多赞成!其中一个奇怪的Stackoverflow稀有.我想知道它是否有徽章...... (17认同)
  • 很多人会来这个页面寻找四舍五入;) (5认同)
  • 对于这个问题,有多少错误答案(以及对错误答案的支持)是令人震惊的. (4认同)
  • 啊哈,够公平的。我的错误抱歉。 (2认同)

Dav*_*d Z 106

首先,该功能适用​​于那些只想要一些复制粘贴代码的人:

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

这在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]])
Run Code Online (Sandbox Code Playgroud)

说明

底层方法的核心是以完全精度将值转换为字符串,然后只删除超出所需字符数的所有内容.后一步很容易; 它可以通过字符串操作来完成

i, p, d = s.partition('.')
'.'.join([i, (d+'0'*n)[:n]])
Run Code Online (Sandbox Code Playgroud)

decimal模块

str(Decimal(s).quantize(Decimal((0, (1,), -n)), rounding=ROUND_DOWN))
Run Code Online (Sandbox Code Playgroud)

第一步,转换为字符串,是非常困难的,因为有一些浮点文字(即你在源代码中编写的内容),它们都产生相同的二进制表示,但应该被截断不同.例如,考虑0.3和0.29999999999999998.如果您0.3在Python程序中编写,则编译器使用IEEE浮点格式将其编码为位序列(假设为64位浮点数)

0011111111010011001100110011001100110011001100110011001100110011
Run Code Online (Sandbox Code Playgroud)

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

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

0.2999999999999999888977697537484345957637...
Run Code Online (Sandbox Code Playgroud)

所以0.2即使这可能不是你想要的,也会提出一个天真的实现.有关浮点表示错误的更多信息,请参阅Python教程.

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

'{}'.format(f)
Run Code Online (Sandbox Code Playgroud)

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

如果你确实正在与工作floats表示非常接近圆形数字,但故意不等于他们(像0.29999999999999998或99.959999999999994),这会产生一些假阳性,即它会圆你不想四舍五入数值.在这种情况下,解决方案是指定固定的精度.

'{0:.{1}f}'.format(f, sys.float_info.dig + n + 2)
Run Code Online (Sandbox Code Playgroud)

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

在Python的早期版本(高达2.6或3.0)中,浮点数格式化更粗糙,并且会定期生成像

>>> 1.1
1.1000000000000001
Run Code Online (Sandbox Code Playgroud)

如果这是你的情况,如果你希望使用"好"十进制表示为截断,所有你能做的(据我所知)是挑选一些号码的数字,小于由全精度表示的float,与轮在截断之前编号为那么多位数.典型的选择是12,

'%.12f' % f
Run Code Online (Sandbox Code Playgroud)

但你可以调整它以适应你正在使用的数字.


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

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


Fer*_*yer 31

结果round是一个浮点数,所以要注意(例如来自Python 2.6):

>>> round(1.923328437452, 3)
1.923
>>> round(1.23456, 3)
1.2350000000000001
Run Code Online (Sandbox Code Playgroud)

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

>>> "%.3f" % 1.923328437452
'1.923'
>>> "%.3f" % 1.23456
'1.235'
Run Code Online (Sandbox Code Playgroud)

  • 在我的Python上,那轮:'%.3f'%1.23456 =='1.235' (7认同)
  • 这是不正确的,因为问题是关于截断而不是四舍五入。 (5认同)
  • @Ahmad 不一定。这里的例子来自 Python 2.6(注意答案的日期)。Python 2.7/3.1 中改进了字符串格式,这可能是您得到不同结果的原因。尽管如此,浮点数通常会有意想不到的字符串表示形式,请参阅:https://docs.python.org/3.6/tutorial/floatingpoint.html (2认同)

小智 19

n = 1.923328437452
str(n)[:4]
Run Code Online (Sandbox Code Playgroud)

  • 因此,如果用户输入例如`2`,则在字符串的末尾会有一个小数点`.` - 我认为这不是一个很好的解决方案. (4认同)
  • 简单和pythonic.4是整数的大小,而不仅是点后的数字. (3认同)

小智 12

在我的Python 2.7提示符下:

>>> int(1.923328437452 * 1000)/1000.0 1.923


mar*_*xor 10

简单的python脚本 -

n = 1.923328437452
n = float(int(n * 1000))
n /=1000
Run Code Online (Sandbox Code Playgroud)

  • 干净的答案.你只是错过了一步,在除以1000之前转换回浮动.否则,你将获得1. (2认同)

Mat*_*att 9

def trunc(num, digits):
   sp = str(num).split('.')
   return '.'.join([sp[0], sp[1][:digits]])
Run Code Online (Sandbox Code Playgroud)

这应该工作.它应该给你你正在寻找的截断.


Ant*_*ins 8

真正的pythonic方式是

from decimal import *

with localcontext() as ctx:
    ctx.rounding = ROUND_DOWN
    print Decimal('1.923328437452').quantize(Decimal('0.001'))
Run Code Online (Sandbox Code Playgroud)


nul*_*atz 8

为这个问题提出的许多答案都是完全错误的.它们要么将浮动(而不是截断)弄圆,要么不适用于所有情况.

当我搜索"Python truncate float"时,这是谷歌的最高结果,这个概念非常简单,值得更好的答案.我同意Hatchkins的说法,使用该decimal模块是这样做的pythonic 方式,所以我在这里给出一个函数,我认为这个函数正确地回答了问题,并且在所有情况下都能正常工作.

作为旁注,通常,小数值不能用二进制浮点变量精确表示(参见此处讨论),这就是我的函数返回一个字符串的原因.

from decimal import Decimal, localcontext, ROUND_DOWN

def truncate(number, places):
    if not isinstance(places, int):
        raise ValueError("Decimal places must be an integer.")
    if places < 1:
        raise ValueError("Decimal places must be at least 1.")
    # If you want to truncate to 0 decimal places, just do int(number).

    with localcontext() as context:
        context.rounding = ROUND_DOWN
        exponent = Decimal(str(10 ** - places))
        return Decimal(str(number)).quantize(exponent).to_eng_string()
Run Code Online (Sandbox Code Playgroud)


小智 6

>>> from math import floor
>>> floor((1.23658945) * 10**4) / 10**4
1.2365
Run Code Online (Sandbox Code Playgroud)

#除以 10**所需位数


cs9*_*s95 5

如果你喜欢一些数学魔法,这适用于 +ve 数字:

>>> v = 1.923328437452
>>> v - v % 1e-3
1.923
Run Code Online (Sandbox Code Playgroud)