Make division by zero equal to zero
Question:
How can I ignore ZeroDivisionError
and make n / 0 == 0
?
Answers:
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
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
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 ZeroDivisionError
s 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.
def foo(x, y):
return 0 if y == 0 else x / y
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)
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.
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.
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
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)]'
Yet another way:
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
Zero denominator
removes the need to load zero with LOAD_CONST
.
How can I ignore ZeroDivisionError
and make n / 0 == 0
?
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
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
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 ZeroDivisionError
s 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.
def foo(x, y):
return 0 if y == 0 else x / y
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)
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.
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.
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 haveTrue and a / b or 0
.True and a / b
is equal toa / b
.a / b or 0
is equal toa / b
. - When
b == 0
we haveFalse and a / b or 0
.False and a / b
is equal toFalse
.False or 0
is equal to0
.
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
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)]'
Yet another way:
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
Zero denominator
removes the need to load zero with LOAD_CONST
.