【问题标题】:Which is the efficient way to convert a float into an int in python?在python中将浮点数转换为int的有效方法是什么?
【发布时间】:2013-12-09 12:17:30
【问题描述】:

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

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

n = n // 1

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

【问题讨论】:

  • @Srichakradhar:对不起,我不明白......在第二种方式(使用//)中,您没有将浮点数转换为整数(至少在python 2.7中),结果这个操作仍然是一个浮点数。因此,据我了解,您是在比较两种不同事物的性能......
  • @HugoCorrá // 始终是整数除法,而 / 是上下文除法(在 Python 2 中)或实数除法(在 Python 3 或 2 中使用 from __future__ import division)。
  • @Kos:实际上,// 仍然返回一个浮点数,它只是被固定了。
  • @Kos // 始终是楼层除法,但其返回类型取决于输入类型。例如3.0 // 2 返回1.0
  • @Kos 即使使用 from future 导入除法,语句 type(float() // 1) 也会为我返回一个浮点数。所以,我真的不明白我们在这里比较的是什么。

标签: python performance int


【解决方案1】:

太长了;没读:

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

我喜欢长篇大论:

@MartijnPieters 绑定builtins.int 的技巧确实很有趣,它让我想起了An Optimization Anecdote。但是,调用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 所做的只是调用以下method-chains

  • 如果定义了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
【解决方案2】:

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

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

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

【讨论】:

  • 你不应该在 n = some_float 而不是 n = some_int 时计时吗?
  • @MartijnPieters 如果你摆脱了全局查找,int 还会慢吗?
  • @MartijnPieters 感谢您的更正,我删除了我的答案。我保证下次我会知道的更好:)
  • @InbarRose:恶心!这实际上是一个重大错误,因为这会使n // 1 的速度变慢...
  • 对于这样的分钟计算和时间,结果可能会因后台进程而出现偏差。但似乎两者几乎同样有效,为了便于阅读,我会选择int(n)
【解决方案3】:

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

【讨论】:

    【解决方案4】:

    其实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
    

    【讨论】:

    • 在 Python 2.7 中,n // 1 仍然获胜
    • 第二次为什么要i=int
    • @InbarRose:重用命令行?在这里没什么区别。
    • @MartijnPieters 结果来自 Virtualbox 上 32 位 Ubuntu 上的 Python 2.7.3。 :)
    • 对于这样的分钟计算和时间,结果可能会因后台进程而出现偏差。但似乎两者几乎同样有效,为了便于阅读,我会选择int(n)
    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

      只是一个有趣的统计测试 - 将 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.'
      

      【讨论】:

      • 如果您还没有,请查看 pandasipython notebook :-)
      • 到目前为止我只看了pandas是什么,因为每个人都在这里问关于熊猫的问题!它在待办事项清单上:) 谢谢!
      • oooh ipython notebook 很漂亮:>
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-03-08
      • 1970-01-01
      • 1970-01-01
      • 2010-09-12
      • 2021-09-10
      • 1970-01-01
      相关资源
      最近更新 更多