【问题标题】:Make division by zero equal to zero使除以零等于零
【发布时间】:2014-12-05 13:57:50
【问题描述】:

我怎样才能忽略ZeroDivisionError 而变成n / 0 == 0

【问题讨论】:

  • 你为什么要这样做?
  • 我除以一个可能为 0 的变量。
  • 请记住,将变量除以 0 的正确结果是不是 0。
  • @wds 有时,“正确”只是“我想要的”的同义词;)
  • 在 ARM64 的指令级 x/0 结果为 0,因此简单的 x/y 可以工作。但是不知道Python是怎么编译成机器码的

标签: python division zero


【解决方案1】:

除法前检查分母是否为零。这避免了捕获异常的开销,如果您希望经常被零除,这可能会更有效。

def weird_division(n, d):
    return n / d if d else 0

【讨论】:

    【解决方案2】:

    您可以为此使用try/except 块。

    def foo(x,y):
        try:
            return x/y
        except ZeroDivisionError:
            return 0
    
    >>> foo(5,0)
    0
    
    >>> foo(6,2)
    3.0
    

    【讨论】:

      【解决方案3】:

      我认为tryexcept(如 Cyber​​ 的回答)通常是最好的方式(而且更 Pythonic:请求宽恕比请求许可更好!),但这是另一个:

      def safe_div(x,y):
          if y == 0:
              return 0
          return x / y
      

      不过,支持这样做的一个论点是,如果您希望ZeroDivisionErrors 经常发生,提前检查 0 分母会快很多(这是 python 3):

      import time
      
      def timing(func):
          def wrap(f):
              time1 = time.time()
              ret = func(f)
              time2 = time.time()
              print('%s function took %0.3f ms' % (f.__name__, int((time2-time1)*1000.0)))
              return ret
          return wrap
      
      def safe_div(x,y):
          if y==0: return 0
          return x/y
      
      def try_div(x,y):
          try: return x/y
          except ZeroDivisionError: return 0
      
      @timing
      def test_many_errors(f):
          print("Results for lots of caught errors:")
          for i in range(1000000):
              f(i,0)
      
      @timing
      def test_few_errors(f):
          print("Results for no caught errors:")
          for i in range(1000000):
              f(i,1)
      
      test_many_errors(safe_div)
      test_many_errors(try_div)
      test_few_errors(safe_div)
      test_few_errors(try_div)
      

      输出:

      Results for lots of caught errors:
      safe_div function took 185.000 ms
      Results for lots of caught errors:
      try_div function took 727.000 ms
      Results for no caught errors:
      safe_div function took 223.000 ms
      Results for no caught errors:
      try_div function took 205.000 ms
      

      所以使用try except 会导致很多(或实际上,所有)错误慢 3 到 4 倍;也就是说:捕获错误的迭代速度要慢 3 到 4 倍。当错误很少(或实际上没有)时,使用if 语句的版本会稍微慢一些(10% 左右)。

      【讨论】:

      • try except 会在您更频繁地出现在 except 子句中时变慢。在这种情况下,总是。如果您知道只有 1000 次中的 1 次除以 0,try except 会更快。因此,您的测试仅与最小的案例子集相关。
      • 已编辑以说明您的观点。不过,我很惊讶检查 0 的速度只有 10% 左右。错误处理比我想象的要慢。
      • @RickTeachey 检查的开销相对较小,因为成本约为 90%+ 函数调用开销。
      • 有道理。捕捉错误本质上是另一个函数调用。
      【解决方案4】:

      解决方案

      如果您想高效处理ZeroDivisionError(除以零),则不应使用异常或条件。

      result = b and a / b or 0  # a / b
      

      它是如何工作的?

      • b != 0 我们有True and a / b or 0True and a / b 等于 a / ba / b or 0 等于 a / b
      • b == 0 我们有False and a / b or 0False and a / b 等于 FalseFalse or 0 等于 0

      基准测试

      Timer unit: 1e-06 s
      
      Total time: 118.362 s
      File: benchmark.py
      Function: exception_div at line 3
      
      Line #      Hits         Time  Per Hit   % Time  Line Contents
      ==============================================================
           3                                           @profile
           4                                           def exception_div(a, b):
           5 100000000   23419098.5      0.2     19.8      try:
           6 100000000   40715642.9      0.4     34.4          return a / b
           7 100000000   28910860.8      0.3     24.4      except ZeroDivisionError:
           8 100000000   25316209.7      0.3     21.4          return 0
      
      Total time: 23.638 s
      File: benchmark.py
      Function: conditional_div at line 10
      
      Line #      Hits         Time  Per Hit   % Time  Line Contents
      ==============================================================
          10                                           @profile
          11                                           def conditional_div(a, b):
          12 100000000   23638033.3      0.2    100.0      return a / b if b else 0
      
      Total time: 23.2162 s
      File: benchmark.py
      Function: logic_div at line 14
      
      Line #      Hits         Time  Per Hit   % Time  Line Contents
      ==============================================================
          14                                           @profile
          15                                           def logic_div(a, b):
          16 100000000   23216226.0      0.2    100.0      return b and a / b or 0
      

      【讨论】:

        【解决方案5】:
        def foo(x, y):
            return 0 if y == 0 else x / y
        

        【讨论】:

        • 我只是好奇。为什么其他答案的票数比这个多?我了解 Cyber​​ 越来越多,但其他 2 个等价于这个。
        • 嗯,通常我会在if 语句中使用该方法。但在这种情况下,我宁愿明确表示不希望 y 等于 0。我更喜欢它作为句子读出时的读取方式。
        【解决方案6】:

        我认为如果你不想面对 Zer0DivErrr,你不必等待或通过使用 try-except 表达式来经历它。更快的方法是通过使您的代码在分母变为零时根本不进行除法来跳过它:

        (if Y Z=X/Y else Z=0)
        

        【讨论】:

          【解决方案7】:

          您可以使用以下内容:

          x=0,y=0
          print (y/(x or not x))
          

          输出:

          >>>x=0
          >>>y=0
          >>>print(y/(x or not x))
          0.0
          >>>x =1000
          >>>print(y/(x or not x))
          0.000999000999000999
          

          如果 x 不等于 0,not x 会为假,所以此时它会除以实际的 x。

          【讨论】:

          • OP 希望结果是 0 而不是 1.0,所以你的第一个示例已经不符合 OP 的需求。
          • 我将 y 保持为 1,只需将其设为 0,您应该得到 0.0。但是谢谢你,我刚刚编辑了我的答案。
          【解决方案8】:

          如果您尝试将两个整数相除,您可以使用:

          if y !=0 :
             z = x/y
          else:
              z = 0
          

          或者你可以使用:

          z = ( x / y ) if y != 0 else 0
          

          如果您尝试划分两个整数列表,您可以使用:

          z = [j/k if k else 0 for j, k in zip(x, y)]
          

          这里,x 和 y 是两个整数列表。

          【讨论】:

            【解决方案9】:

            我很好奇为什么ToTomire 的解决方案会更快。如果感觉 conditional_div 通常应该因其自然语言可读性而受到首选,但如果我能确切地理解为什么 logic_div 更快,那可能对我未来有所帮助。为此,我查看了 python 的dis

            >>> conditional_div = lambda n,d: n/d if d else 0
            >>> logic_div = lambda n,d: d and n/d or 0
            >>> dis.dis(conditional_div)
              1           0 LOAD_FAST                1 (d)
                          2 POP_JUMP_IF_FALSE       12
                          4 LOAD_FAST                0 (n)
                          6 LOAD_FAST                1 (d)
                          8 BINARY_TRUE_DIVIDE
                         10 RETURN_VALUE
                    >>   12 LOAD_CONST               1 (0)
                         14 RETURN_VALUE
            >>> dis.dis(logic_div)
              1           0 LOAD_FAST                1 (d)
                          2 POP_JUMP_IF_FALSE       12
                          4 LOAD_FAST                0 (n)
                          6 LOAD_FAST                1 (d)
                          8 BINARY_TRUE_DIVIDE
                         10 JUMP_IF_TRUE_OR_POP     14
                    >>   12 LOAD_CONST               1 (0)
                    >>   14 RETURN_VALUE
            

            看来logic_div 实际上应该有一个额外的步骤。最多 '8' 这两个字节码是相同的。在 '10' conditional_div 只会返回一个值,而 logic_div 如果它为真则必须执行跳转然后返回。也许替代..._OR_POP 比返回更快,所以在某些情况下它的最后一步更短?但激活..._OR_POP 的唯一方法是分子为零且分母非零。当分母为零时,两个字节码采用相同的路线。这感觉不是一个令人满意的结论。如果我误解了什么,也许有人可以解释一下。

            供参考

            >>> sys.version
            '3.9.6 (tags/v3.9.6:db3ff76, Jun 28 2021, 15:26:21) [MSC v.1929 64 bit (AMD64)]'
            

            【讨论】:

            • 您是否也复制了@ToTomire 的基准测试以检查它在您的系统规格下实际上是否更快?速度差异很小,我认为需要反复试验才能确定差异的显着性水平。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-04-06
            • 2017-07-20
            • 1970-01-01
            • 1970-01-01
            • 2011-03-09
            相关资源
            最近更新 更多