As with most posts in this series, a weird problem cropped up in one of my courses, so I wanted to talk about it. The issue today is all about what happens when you divide by zero in Java.
Table of Contents
Background
For a little context, I figured I’d share why I’m writing this article. Every semester, I teach a course on software components. As an educator, I see it as my job to make sure students have proper support and guidance. Much of that support comes in the form of anticipating issues that students might encounter.
As I’ve taught the course a few times, I’ve noticed patterns in the way students grapple with the material. For instance, very early in the course, we ask the students to compute a square root using Newton iteration. The process looks something like this:
- Take a guess,
g
, at the square root of a number,x
(e.g.,x
itself is a great starting point) - Square
g
and subtractx
from it. Then, divide the result byx
. That gives us some error,e
- If
e
is close enough to 0, then we know we have the rightg
. We’re done! - If
e
is not close enough to 0, then we need to take another guess. - To compute a new
g
, we can takeg
and add it to the ratio ofx
overg
. This sum can then be halved to give us our newg
. - Repeat steps 2-5 as needed.
To see how this works in practice, let’s try to predict the square root of 9. To start, we take a guess of 9. Our error comes out to 8 (i.e., (9 * 9 - 9) / 9
). This is not close enough to 0. Our updated guess is 5 (i.e., (9 + 9 / 9) / 2
). The error for 5 comes out to 1.78. Much better, but we can do better. Our updated guess is 3.4 which gives us an error of .28. Again, we’re getting closer. After that, our guess becomes 3.02, at which point we might stop (if we deem this close enough).
Now, the reason I show you this is because this process involves a potential division by 0 when x
is 0. As a result, we usually ask students to handle this. Unfortunately, what ends up happening is that students will notice that their code works even when this division by 0 occurs. How is this possible? That’s the topic of todays article!
The Division by Zero Error in Java
If you’ve ever messed around with algebra, you probably know that division by zero is a big no-no. I don’t have the math skill to explain why, but it makes somewhat intuitive sense, right? What does it mean to divide something into zero parts?
Because division by zero causes so many problems, programming languages have their own ways of dealing with it. For example, in Java, integer division by zero will cause an ArithmeticException. Here’s an example using JDoodle:
Exception in thread "main" java.lang.ArithmeticException: / by zero at MyClass.main(MyClass.java:6)
Personally, I’m a huge fan of errors like these because they give me some place to look when things go wrong. That said, I understand why developers sometimes avoid them due to the complexity they introduce.
Introducing NaN
Unfortunately, Java does not always provide this nice ArithmeticException in all cases—specifically when working with doubles. In the example I mentioned in the background, we compute the square root using doubles. As you saw, this more or less goes well, but there is one scenario where it does not: when x = 0.
To illustrate this, let’s try going through the same list of steps above. For example, we’ll start computing the square root of 0 by taking a guess, g
, of 0. To be clear, both x
and g
are doubles. As a result, when it comes to computing the error, we get the following expression: (0 * 0 - 0) / 0
. When simplified, we end up with the following expression: 0 / 0
. If these were integers, our program would crash as expected. Instead, our expression evaluates to NaN
.
NaN
is a bit of a weird value. It literally means “not a number,” but it can be stored in a double variable. As a result, it’s somewhat mischievous. To make matters worse, it won’t cause obvious problems when it is computed. For example, NaN
can be used in relational expressions just like any double, so don’t expect it to cause any errors as it propagates.
In our case, when NaN
is generated, it is then immediately checked if it’s close enough to x
by using some threshold (e.g., NaN >= .0001
). Because NaN
is not a number, this expression always returns false. Up until this point, false would mean our condition was met, so we could return our guess as the square root. Funnily enough, because we set our first guess to x
, we’ll return x
. And since x
happens to be its own square root, we might argue that the code works.
But the question is: does the code work? This is a bit of a philosophical question. After all, when I teach, I usually define correctness as a function whose set of outputs exist in the set of expected outputs. Using this black box definition of correctness, we might not care that our square root function accidentally came upon the right answer. And for our code golf friends, we might even prefer this “bug” to computing square roots. That said, there is something uneasy about the way things work out.
But It Works!
Every day, folks are going through code review processes while receiving comments like “this is somewhat of a hack” and “this has a bad smell,” and I’m starting to wonder if comments like this are valid. After all, the square root code works! As a result, I started to question some of the many assumptions we make about coding. For example, what makes code hacky? What makes code have a bad smell? Here are some discussions I managed to drum up:
- What constitutes a hack or hacky code?
- What is “hacky” code?
- The Problem of Code Smell and Secrets to Effective Refactoring
Perhaps in a future article I might go down this philosophical rabbit hole. For now though, I need to call it a day! As always, here are some other coding tangents you might enjoy:
- The “else if” Keyword Doesn’t Exist in Java
- The Behavior of i=i++ in Java
- The Difference Between Statements and Expressions
With that said, thanks for sticking around. See you next time!
Recent Posts
As of March of this year, I'm off Twitter for good! In fact, at the time of writing, my old account should be deleted. Let's talk about that!
Recently, I was thinking about how there are so many ways to approach software design. While some of these approaches have fancy names, I'm not sure if anyone has really thought about them...