65

How can I ignore ZeroDivisionError and make n / 0 == 0?

davidism
  • 121,510
  • 29
  • 395
  • 339
octosquidopus
  • 3,517
  • 8
  • 35
  • 53

10 Answers10

138

Check if the denominator is zero before dividing. This avoids the overhead of catching the exception, which may be more efficient if you expect to be dividing by zero a lot.

def weird_division(n, d):
    return n / d if d else 0
davidism
  • 121,510
  • 29
  • 395
  • 339
45

Solution

When you want to efficient handle ZeroDivisionError (division by zero) then you should not use exceptions or conditionals.

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

How it's works?

  • When b != 0 we have True and a / b or 0. True and a / b is equal to a / b. a / b or 0 is equal to a / b.
  • When b == 0 we have False and a / b or 0. False and a / b is equal to False. False or 0 is equal to 0.

Benchmark

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
ToTamire
  • 1,425
  • 1
  • 13
  • 23
44

You can use a try/except block for this.

def foo(x,y):
    try:
        return x/y
    except ZeroDivisionError:
        return 0

>>> foo(5,0)
0

>>> foo(6,2)
3.0
Cory Kramer
  • 114,268
  • 16
  • 167
  • 218
20

I think try except (as in Cyber's answer) is usually the best way (and more pythonic: better to ask forgiveness than to ask permission!), but here's another:

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

One argument in favor of doing it this way, though, is if you expect ZeroDivisionErrors to happen often, checking for 0 denominator ahead of time will be a lot faster (this is 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)

Output:

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

So using try except turns out to be 3 to 4 times slower for lots of (or really, all) errors; that is: it is 3 to 4 times slower for iterations that an error is caught. The version using the if statement turns out to be slightly slower (10% or so) when there are few (or really, no) errors.

Rick
  • 43,029
  • 15
  • 76
  • 119
  • 4
    `try except` will be slower when you end up in the `except` clause more often. In this case, always. If you know that only about 1 in 1000 times, you get a division by 0, `try except` will be faster. So your test is only relevant for a minimal subset of cases. –  Dec 05 '14 at 15:18
  • Edited to illustrate your point. I was surprised that it's only a 10% or so slow down to check for 0, though. Error handling is slower than I realized. – Rick Dec 05 '14 at 15:57
  • 1
    @RickTeachey The overhead of checking is so small relatively because the cost is ~90%+ function call overhead. – Veedrac Dec 06 '14 at 15:42
  • Makes sense. Catching an error is in essence another function call. – Rick Dec 07 '14 at 23:16
10
def foo(x, y):
    return 0 if y == 0 else x / y
twasbrillig
  • 17,084
  • 9
  • 43
  • 67
  • 1
    I'm just curious. Why did the other answers get more votes than this one? I understand Cyber's getting more, but the other 2 are equivalent to this. – twasbrillig Dec 05 '14 at 14:14
  • Hmm, normally I would use that method in `if` statements. But in this case I'd rather be explicit about not wanting y to be equal to 0. I prefer the way it reads when read out as a sentence. – twasbrillig Dec 08 '14 at 19:01
3

I think if you don't want to face Zer0DivErrr, you haven't got to wait for it or go through it by using try-except expression. The quicker way is to jump over it by making your code simply not to do division when denominator becomes zero:

(if Y Z=X/Y else Z=0)
Vynylyn
  • 172
  • 11
2

If you are trying to divide two integers you may use :

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

or you can use :

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

If you are trying to divide two lists of integers you may use :

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

where here, x and y are two lists of integers.

my shark
  • 23
  • 5
1

You can use the following :

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

Output:

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

not x will be false if x is not equal to 0, so at that time it divides with actual x.

Saurav Panda
  • 558
  • 5
  • 12
0

I was intrigued why ToTomire's solution would be faster. If feels like conditional_div should often be preferred for its natural language readability, but if I can understand exactly why logic_div is faster that might help me in the future. I looked to python's dis for this.

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

And it appears that logic_div should actually have an extra step. Up to '8' the two bytecodes are identical. At '10' conditional_div would just return a value whereas logic_div has to do a jump if its true and then return. Perhaps the alternative ..._OR_POP is faster than returning so some percent of the time it has a shorter last step? But the only way that ..._OR_POP would be activated is if the numerator were zero and the denominator non-zero. Both bytecodes take the same route when the denominator is zero. This doesn't feel like a satisfying conclusion. Maybe someone can explain if I'm misunderstanding something.

For reference

>>> sys.version
'3.9.6 (tags/v3.9.6:db3ff76, Jun 28 2021, 15:26:21) [MSC v.1929 64 bit (AMD64)]'
jbf81tb
  • 785
  • 6
  • 8
  • Did you also replicate @ToTomire's benchmarking to check that it is actually faster under your system specs? The speed difference is tiny and I think repeated trials are needed to determine the significance level of the difference. – Jareth Holt Feb 03 '22 at 10:11
0

As simple as it gets:

def safe_division(numerator, denominator):
    """Return 0 if denominator is 0."""
    return denominator and numerator / denominator

and returns either zero denominator or the result of the division.

Bytecode:

In [51]: dis(safe_division)
 25           0 LOAD_FAST                1 (denominator)
              2 JUMP_IF_FALSE_OR_POP     5 (to 10)
              4 LOAD_FAST                0 (numerator)
              6 LOAD_FAST                1 (denominator)
              8 BINARY_TRUE_DIVIDE
        >>   10 RETURN_VALUE

Returning zero denominator removes the need to load zero with LOAD_CONST.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271