在Python中,将浮点数转换为整数的高效方法是什么?

15

我一直使用n = int(n)float转换为int

最近,我发现了另一种做法:

n = n // 1

哪种方法效率更高?为什么?


1
@Srichakradhar:抱歉,我不明白...在第二种方式中(使用//),您并没有将浮点数转换为整数(至少在Python 2.7中),此操作的结果仍然是浮点数。因此,在我的理解中,您正在比较两个不同事物的性能... - Hugo Corrá
@HugoCorrá 在Python中,//始终表示整数除法,而/则根据上下文(在Python 2中)或实际除法(在Python 3或在2中使用from __future__ import division)进行计算。 - Kos
3
@Kos: 实际上,// 仍然返回一个浮点数,只不过是向下取整。 - Wooble
@Kos // 总是执行地板除法,但其返回类型取决于输入类型。例如,3.0 // 2 返回 1.0 - user395760
@Kos 即使使用了 from future import division,语句 type(float() // 1) 对我来说仍然返回一个浮点数。所以,我真的不明白我们在比较什么。 - Hugo Corrá
哦,你说得对。谢谢你纠正我。 - Kos
6个回答

19

使用timeit测试一下:

$ bin/python -mtimeit -n10000000 -s 'n = 1.345' 'int(n)'
10000000 loops, best of 3: 0.234 usec per loop
$ bin/python -mtimeit -n10000000 -s 'n = 1.345' 'n // 1'
10000000 loops, best of 3: 0.218 usec per loop

因此,整数除法只比普通除法快一点点。请注意,这些值非常接近,而且我不得不增加循环重复次数来消除计算机上的随机影响。即使进行了如此高的重复次数,您仍需要多次重复实验以查看数字变化了多少,并且在大多数情况下哪种方式更快。

这是合理的,因为int()需要全局查找和函数调用(因此状态被推送和弹出):

>>> import dis
>>> def use_int(n):
...     return int(n)
... 
>>> def use_floordiv(n):
...     return n // 1
... 
>>> dis.dis(use_int)
  2           0 LOAD_GLOBAL              0 (int)
              3 LOAD_FAST                0 (n)
              6 CALL_FUNCTION            1
              9 RETURN_VALUE        
>>> dis.dis(use_floordiv)
  2           0 LOAD_FAST                0 (n)
              3 LOAD_CONST               1 (1)
              6 BINARY_FLOOR_DIVIDE 
              7 RETURN_VALUE        

LOAD_GLOBALCALL_FUNCTION指令比LOAD_CONSTBINARY_FLOOR_DIVIDE指令慢; LOAD_CONST是简单的数组查找,LOAD_GLOBAL需要进行字典查找。

int()绑定到本地名称可能会产生微小的差异,再次给予它优势(因为它需要比// 1整数除法做更少的工作):

$ bin/python -mtimeit -n10000000 -s 'n = 1.345' 'int(n)'
10000000 loops, best of 3: 0.233 usec per loop
$ bin/python -mtimeit -n10000000 -s 'n = 1.345; int_=int' 'int_(n)'
10000000 loops, best of 3: 0.195 usec per loop
$ bin/python -mtimeit -n10000000 -s 'n = 1.345' 'n // 1'
10000000 loops, best of 3: 0.225 usec per loop

需要再次运行10百万次以始终看到差异。

尽管如此,int(n)更加明确,除非您在时间关键循环中执行此操作,否则在可读性方面,int(n)胜过n // 1。 时间差异太小,无法使人们花费认知成本去解释// 1在这里的作用。


1
你应该在 n = some_float 而不是 n = some_int 的情况下计时,对吗? - Inbar Rose
@MartijnPieters 我进行了检查并得到了一个不错的结果。并不是很惊讶,我预计 a//1 会比函数调用多做一点工作。 - Kos
2
对于这样微小的计算和时间,结果可能会因后台进程而产生偏差。但似乎两者的效率几乎相同,我会选择使用int(n)以提高可读性。 - Inbar Rose
@phresnel:我已经更新了答案;我也看到一些不一致的结果,这就是为什么我增加了重复计数的原因。这里的边际很小,所以磁盘刷新或操作系统的后台任务重新安排会非常快地扭曲结果。 - Martijn Pieters
@phresnel:无论如何,我很感激您的反馈;我确实看到时间非常接近,结果并不总是清晰明了。同时,对Python解释器的了解也让我有信心知道我的答案在这里是正确的。 - Martijn Pieters
显示剩余14条评论

3
虽然Martijn Pieters回答了你的问题,即什么更快以及如何测试它,但我觉得对于这样一个小操作来说,速度并不那么重要。像Inbar Rose所说的那样,我会使用int()来提高可读性。通常在处理这样小的东西时,可读性要远比速度重要;虽然,常见的等式可能是个例外。

2

实际上,int 似乎比除法更快。慢的部分是在全局范围内查找函数。

如果我们避免这种情况,以下是我的数字:

$ python -mtimeit -s 'i=int; a=123.456' 'i(a)'
10000000 loops, best of 3: 0.122 usec per loop
$ python -mtimeit -s 'i=int; a=123.456' 'a//1'
10000000 loops, best of 3: 0.145 usec per loop

为什么第二个timeit中使用了i=int - Inbar Rose
1
@InbarRose:重复使用命令行?在这里没有任何区别。 - Martijn Pieters
@MartijnPieters 这个结果来自于在Virtualbox上运行的32位Ubuntu系统中的Python 2.7.3版本。 :) - Kos
1
对于这样微小的计算和时间,结果可能会因后台进程而产生偏差。但似乎两者的效率几乎相同,我会选择使用int(n)以提高可读性。 - Inbar Rose
@Kos: 2.7.5; Python 3.3.2 没有做得更好。 - Martijn Pieters
显示剩余2条评论

2

太长了,没看:

使用float.__trunc__()builtins.int()快30%

我喜欢详细的解释:

@MartijnPieters的绑定builtins.int技巧确实很有趣,它让我想起了一个优化轶事。然而,调用builtins.int并不是最高效的方法。

让我们来看一下这个例子:

python -m timeit -n10000000 -s "n = 1.345" "int(n)"
10000000 loops, best of 5: 48.5 nsec per loop

python -m timeit -n10000000 -s "n = 1.345" "n.__trunc__()"
10000000 loops, best of 5: 33.1 nsec per loop

这是一个30%的增益!发生了什么?

原来所有builtins.int所做的就是调用以下方法链

  • 如果定义了1.345.__int__,则返回1.345.__int__(),否则:
  • 如果定义了1.345.__index__,则返回1.345.__index__(),否则:
  • 如果定义了1.345.__trunc__,则返回1.345.__trunc__()

1.345.__int__未定义1 - 1.345.__index__也未定义。因此,直接调用1.345.__trunc__()可以跳过所有不必要的方法调用 - 这相对昂贵。


关于绑定技巧呢?嗯,float.__trunc__ 本质上只是一个实例方法,我们可以将 1.345 作为 self 参数传递。
python -m timeit -n10000000 -s "n = 1.345; f=int" "f(n)"
10000000 loops, best of 5: 43 nsec per loop

python -m timeit -n10000000 -s "n = 1.345; f=float.__trunc__" "f(n)"
10000000 loops, best of 5: 27.4 nsec per loop

两种方法的表现都如预期所料2,并且它们保持大致相同的比例!


1 我对此并不完全确定 - 如果有人知道请纠正我。

2 这让我感到惊讶,因为我原以为 float.__trunc__ 在实例创建时绑定到了 1.345。如果有人能够友善地解释一下这个问题那就太好了。


还有这个方法builtins.float.__floor__,文档中没有提到 - 它比builtins.int快但比buitlins.float.__trunc__慢。

python -m timeit -n10000000 -s "n = 1.345; f=float.__floor__" "f(n)"
10000000 loops, best of 5: 32.4 nsec per loop

似乎它在负浮点数和正浮点数上产生相同的结果。如果有人能解释一下这如何与其他方法相配合,那就太棒了。

这里不包括 //,因为它返回的是 float 而不是 int - Leonardus Chen

1
请注意,您不是使用地板除法运算符将浮点数转换为整数。此操作的结果仍然是浮点数。在Python 2.7.5(CPython)中,n=n//1与以下内容完全相同:
n.__floordiv__(1)

这基本上是同样的事情:
n.__divmod__(1)[0]

这两个函数返回浮点数而不是整数。在CPython的__divmod__函数内,分母和分子必须从PyObject转换为double。因此,在这种情况下,使用floor函数比使用//运算符更快,因为只需要进行一次转换。

from cmath import floor
n=floor(n)

如果您真的想将浮点数转换为整数,我认为没有比int(n)更快的方法。

0

只是一个统计测试,让我们开心一下 - 将timeit测试更改为您喜欢的任何内容:

import timeit
from scipy import mean, std, stats, sqrt

# Parameters:
reps = 100000
dups = 50
signif = 0.01
timeit_setup1 = 'i=int; a=123.456'
timeit_test1 = 'i(a)'
timeit_setup2 = 'i=int; a=123.456'
timeit_test2 = 'a//1'

#Some vars
t1_data = []
t2_data = []
frmt = '{:.3f}'
testformat = '{:<'+ str(max([len(timeit_test1), len(timeit_test2)]))+ '}'

def reportdata(mylist):
    string = 'mean = ' + frmt.format(mean(mylist)) + ' seconds, st.dev. = ' + \
             frmt.format(std(mylist))
    return string

for i in range(dups):
    t1_data.append(timeit.timeit(timeit_test1, setup = timeit_setup1,
                                    number = reps))
    t2_data.append(timeit.timeit(timeit_test2, setup = timeit_setup2,
                                    number = reps))

print testformat.format(timeit_test1) + ':', reportdata(t1_data)
print testformat.format(timeit_test2) + ':', reportdata(t2_data)
ttest = stats.ttest_ind(t1_data, t2_data)
print 't-test: the t value is ' + frmt.format(float(ttest[0])) + \
      ' and the p-value is ' + frmt.format(float(ttest[1]))
isit = ''
if float(ttest[1]) > signif:
    isit = "not "
print 'The difference of ' + \
      '{:.2%}'.format(abs((mean(t1_data)-mean(t2_data))/mean(t1_data))) + \
      ' +/- ' + \
      '{:.2%}'.format(3*sqrt((std(t1_data)**2 + std(t2_data)**2)/dups)) + \
      ' is ' + isit + 'significative.'

1
如果你还没有使用过,可以试试 pandasipython notebook :-) - Kos
到目前为止,我只看了一下pandas是什么,因为这里每个人都会问关于pandas的问题!它在待办列表上 :) 谢谢! - Roberto
哦,IPython笔记本非常棒:> - Roberto

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接