繁体   English   中英

在python中将浮点数转换为int的有效方法是什么?

[英]Which is the efficient way to convert a float into an int in python?

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

最近,我遇到了另一种做同样事情的方法:

n = n // 1

哪种方式最有效,为什么?

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 floor Division 做的工作更少):

$ 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

同样,您需要运行 1000 万次循环才能始终如一地查看差异。

也就是说, int(n)更加明确,除非您在时间关键循环中执行此操作,否则int(n)在可读性方面胜过n // 1 时间差异太小,以至于不得不计算// 1这里做什么的认知成本是值得的。

尽管 Martijn Pieters 回答了您关于什么更快以及如何测试它的问题,但我觉得速度对于如此小的操作来说并不那么重要。 正如 Inbar Rose 所说,我会使用 int() 来提高可读性。 通常在处理某些事情时,这种小的可读性要重要得多; 但是,一个常见的方程式可能是一个例外。

实际上, 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

请注意,您没有使用地板除法运算符从 float 转换为 int 这个操作的结果仍然是一个浮点数。 在 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) 性能。

只是一个统计测试以获得一点乐趣 - 将 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.'

太长了; 没读:

使用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%的收益! 这里发生了什么?

事实证明,所有builtints.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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM