在Python中截断浮动


110

我想从浮点数中删除数字,以使小数点后的位数固定不变,例如:

1.923328437452 -> 1.923

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

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


4
应该将-1.233截断为-1.23或-1.24吗?
安东尼·哈奇金斯2014年

Answers:


116

首先,该功能针对那些只需要复制和粘贴代码的人:

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。如果您0.3使用Python程序编写,则编译器将使用IEEE浮点格式将其编码为位序列(假定为64位浮点数)

0011111111010011001100110011001100110011001100110011001100110011

这是最接近0.3的值,可以准确地表示为IEEE浮点数。但是,如果您0.29999999999999998使用Python程序编写代码,则编译器会将其转换为完全相同的value。在一种情况下,您希望将其截断为(一位)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),但我不确定如何最好地处理这些问题。

如果你确实正在与工作floats表示非常接近圆形数字,但故意不等于他们(像0.29999999999999998或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

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


我们如何将此功能应用于数据框?
codelord

@RohithRNair顶住我,就像应用其他对单个元素(即applymap())起作用的函数一样。也许有一种方法可以使整个操作更有效率,但这将是一个单独的问题。
David Z

applymap()花费很多时间,因为我的数据帧非常大。我正在尝试比较两个数据帧的差异,但是浮点精度使我的输出偏离了期望值。就像您说的,我将对此提出一个单独的问题。谢谢。
codelord

@RohithRNair嗯,如果您要比较两个数据帧之间的差异,那么可以询问一下。截断值(这就是这个问题的意思)并不是实现它的最佳方法。
David Z

只需注意一下,您的代码似乎
会将负数切

152
round(1.923328437452, 3)

请参阅有关标准类型的Python文档。您需要向下滚动才能进入取整功能。本质上,第二个数字表示要舍入到小数点后位数。


49
我的意思是舍入不是我所需要的。我需要截断,这是不同的。
Joan Venge

1
嗯,很公平。我的错对不起。
Teifion

22
对于不正确的解决方案,有很多反对意见!那些奇怪的Stackoverflow稀有之一。我想知道是否有徽章吗?
tumultous_rooster 2014年

5
令人震惊的是,这个问题有多少错误答案(并对错误答案进行投票)。
nullstellensatz

6
很多人都会在此页面上寻找四舍五入;)
janjackson '18

33

的结果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'

8
在我的Python,这轮: '%.3f' %1.23456 == '1.235'
大卫ž

这比手工字符串格式的废话更优雅,好帖子!
rsethc '18

round(1.23456, 3)1.235不是1.2350000000000001
艾哈迈德

1
@艾哈迈德不一定。这里的示例来自Python 2.6(注意答案的日期)。字符串格式在Python 2.7 / 3.1中得到了改进,这可能就是为什么您获得不同结果的原因。不过,浮点数通常会具有意外的字符串表示形式,请参见:docs.python.org/3.6/tutorial/floatingpoint.html
Ferdinand Beyer

21
n = 1.923328437452
str(n)[:4]

3
简单而pythonic。但是,整数的大小是4,不仅是点后的数字。
GaTTaCa 2013年

4
因此,例如2,如果用户输入.,则在字符串末尾会有一个小数点-我认为这不是一个很好的解决方案。
Zelphir Kaltstahl'3

这是特定于此号码的情况。如何将其推广到11.923328437452?
极化

最佳答案!您还可以添加float()以返回数字:float(str(n)[:4])
justSaid

14

在我的Python 2.7提示符下:

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


11

简单的python脚本-

n = 1.923328437452
n = float(int(n * 1000))
n /=1000

3
干净的答案。你只是错过了一个步,回来1000,否则将之前转换为浮动,您将获得1
勒芒奥巴迪亚

9
def trunc(num, digits):
   sp = str(num).split('.')
   return '.'.join([sp[0], sp[1][:digits]])

这应该工作。它应该为您提供所需的截断。


9

真正的pythonic方式是

from decimal import *

with localcontext() as ctx:
    ctx.rounding = ROUND_DOWN
    print Decimal('1.923328437452').quantize(Decimal('0.001'))

或更短:

from decimal import Decimal as D, ROUND_DOWN

D('1.923328437452').quantize(D('0.001'), rounding=ROUND_DOWN)

更新资料

通常问题不在于截断浮点数本身,而在于舍入不正确使用浮点数。

例如:int(0.7*3*100)/100 == 2.09

如果您被迫使用浮点数(例如,使用来加速代码numba),则最好使用美分作为价格的“内部表示”:(70*3 == 210)并乘/除输入/输出。


我请帕森问这个问题,但是...为什么呢?
markroxor

@markroxor,不确定您要问的是什么。附带说明,通常问题不在于舍入本身,而在于舍入不正确使用浮点数。例如int(0.7*3*100)/100 == 2.09。我的一分钱去哪儿了?
安东尼·哈奇金斯

很有道理,您可以使用此说明来编辑答案吗?谢谢。
markroxor

获取ImportError: cannot import name 'D',我相信你想使一个名为进口没有?
Overdrivr

8

因此,针对这个问题给出的许多答案完全是错误的。它们要么将浮点取整(而不是截断),要么不能在所有情况下都起作用。

当我搜索“ Python truncate float”时,这是Google的最高结果,这个概念非常简单,应该得到更好的答案。我同意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()

4

我做了这样的事情:

from math import trunc


def truncate(number, decimals=0):
    if decimals < 0:
        raise ValueError('truncate received an invalid value of decimals ({})'.format(decimals))
    elif decimals == 0:
        return trunc(number)
    else:
        factor = float(10**decimals)
        return trunc(number*factor)/factor

4

你可以做:

def truncate(f, n):
    return math.floor(f * 10 ** n) / 10 ** n

测试:

>>> f=1.923328437452
>>> [truncate(f, n) for n in range(5)]
[1.0, 1.9, 1.92, 1.923, 1.9233]

这只会截断正数,负数会四舍五入(远离零)。
亚伦D

3

如果您喜欢一些数学运算,则适用于+ ve数:

>>> v = 1.923328437452
>>> v - v % 1e-3
1.923

据我了解,1e-3将在点后截断为3位数字。我喜欢这个答案,但它似乎不适用于4和
5。– egvo

2

使用pandas df时,这对我有用

import math
def truncate(number, digits) -> float:
    stepper = 10.0 ** digits
    return math.trunc(stepper * number) / stepper

df['trunc'] = df['float_val'].apply(lambda x: truncate(x,1))
df['trunc']=df['trunc'].map('{:.1f}'.format)

1

只是想提一下旧的“用floor()制作round()”技巧

round(f) = floor(f+0.5)

可以转过来从round()制作floor()

floor(f) = round(f-0.5)

尽管这两个规则都绕负数,但是使用它并不理想:

def trunc(f, n):
    if f > 0:
        return "%.*f" % (n, (f - 0.5*10**-n))
    elif f == 0:
        return "%.*f" % (n, f)
    elif f < 0:
        return "%.*f" % (n, (f + 0.5*10**-n))

1

int(16.5); 这将给出16的整数值,即trunc,将无法指定小数,但是您可以通过以下方式做到这一点

import math;

def trunc(invalue, digits):
    return int(invalue*math.pow(10,digits))/math.pow(10,digits);

1

这是一个简单的方法:

def truncate(num, res=3):
    return (floor(num*pow(10, res)+0.5))/pow(10, res)

对于num = 1.923328437452,则输出1.923



1

通用简单的功能:

def truncate_float(number, length):
    """Truncate float numbers, up to the number specified
    in length that must be an integer"""

    number = number * pow(10, length)
    number = int(number)
    number = float(number)
    number /= pow(10, length)
    return number

大!强制转换为int会截断正数和负数。
亚伦D

1

在python 3中有一个简单的解决方法。在哪里剪切我定义了一个帮助变量decPlace以使其易于适应。

f = 1.12345
decPlace= 4
f_cut = int(f * 10**decPlace) /10**decPlace

输出:

f = 1.1234

希望能帮助到你。


1
def precision(value, precision):
    """
    param: value: takes a float
    param: precision: int, number of decimal places
    returns a float
    """
    x = 10.0**precision
    num = int(value * x)/ x
    return num
precision(1.923328437452, 3)

1.923


很好,但您没有四舍五入。
亚历克斯

1

简短易用的变体

def truncate_float(value, digits_after_point=2):
    pow_10 = 10 ** digits_after_point
    return (float(int(value * pow_10))) / pow_10

>>> truncate_float(1.14333, 2)
>>> 1.14

>>> truncate_float(1.14777, 2)
>>> 1.14


>>> truncate_float(1.14777, 4)
>>> 1.1477

1

我认为大多数答案都太复杂了,这又如何呢?

digits = 2  # Specify how many digits you want

fnum = '122.485221'
truncated_float = float(fnum[:fnum.find('.') + digits + 1])

>>> 122.48

只需扫描索引“。”。并根据需要截断(不舍入)。将字符串转换为浮点数作为最后一步。

或者在您的情况下,如果您将float用作输入并希望将字符串作为输出:

fnum = str(122.485221)  # convert float to string first
truncated_float = fnum[:fnum.find('.') + digits + 1]  # string output

如果要截断的数字很小,则建议是有问题的,因为您会浪费很多精度,因为小数点右边的前导0。但是,该问题是上述问题所特有的。我要说的是,重要的数字才是真正的答案。
overcoil

1
>>> floor((1.23658945) * 10**4) / 10**4
1.2365

除以10 **所需位数


0

使用numpy.round

import numpy as np
precision = 3
floats = [1.123123123, 2.321321321321]
new_float = np.round(floats, precision)

0

足够简单以适合列表理解的东西,没有库或其他外部依赖项。对于Python> = 3.6,使用f字符串编写非常简单。

这样做的想法是让字符串转换将舍入到比您需要的更多的位置,然后截取最后一位。

>>> nout = 3  # desired number of digits in output
>>> [f'{x:.{nout+1}f}'[:-1] for x in [2/3, 4/5, 8/9, 9/8, 5/4, 3/2]]
['0.666', '0.800', '0.888', '1.125', '1.250', '1.500']

当然,这里会进行四舍五入(即四位数),但是在某些时候四舍五入是不可避免的。如果截断和舍入之间的过渡是相关的,那么下面是一个更好的示例:

>>> nacc = 6  # desired accuracy (maximum 15!)
>>> nout = 3  # desired number of digits in output
>>> [f'{x:.{nacc}f}'[:-(nacc-nout)] for x in [2.9999, 2.99999, 2.999999, 2.9999999]]
>>> ['2.999', '2.999', '2.999', '3.000']

奖励:删除右边的零

>>> nout = 3  # desired number of digits in output
>>> [f'{x:.{nout+1}f}'[:-1].rstrip('0') for x in [2/3, 4/5, 8/9, 9/8, 5/4, 3/2]]
['0.666', '0.8', '0.888', '1.125', '1.25', '1.5']

0

我看来,此处给出的核心思想是解决此问题的最佳方法。不幸的是,它获得的票数较少,而后来获得更多票数的答案尚未完成(如评论中所观察)。希望以下实现为截断提供了一个简短完整的解决方案。

def trunc(num, digits):
    l = str(float(num)).split('.')
    digits = min(len(l[1]), digits)
    return (l[0]+'.'+l[1][:digits])

应该处理在这里这里找到的所有极端情况。


-1

我也是python新手,在使用了一些零碎的内容后,我提供了2美分

print str(int(time.time()))+str(datetime.now().microsecond)[:3]

str(int(time.time()))将时间段作为整数,并将其转换为字符串,并与... str(datetime.now()。microsecond)[:3]结合使用,它仅返回微秒,转换字符串并截断为前3个字符


-1
# value  value to be truncated
# n  number of values after decimal

value = 0.999782
n = 3
float(int(value*1en))*1e-n

-3

如果您是说打印时,那么以下方法应该起作用:

print '%.3f' % number

2
这将数字取整,不会截断。
David Z
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.