Dave Holoway
Posted on June 21, 2020
I'm a fan of diving into the internals of programming languages, compilers and the low-level stuff that most developers (rightly!) ignore but sometimes strangeness lurks in those corners.
Case in point: Integer division by constant 0 in Java.
Small background: I'm currently working my way through a project to provide code-completion for Java in VSCode, specifically for Android development. There's long been support for Java in VSCode, but it's not that great and doesn't support some quirks that Android development uses.
Providing high-quality code-completion means understanding how the underlying programming language works. Not just the syntax, but the rules of engagement that come into play when a project and its source is compiled.
Back to the post. In almost all programming languages, an integer division by constant zero produces a warning (C, C++) or a compile-time error (C#, go). Unlike floating-point numbers, there's no standard way to represent infinity with integers so it makes sense to ward off developers performing such operations.
$ cat a.c
int main() {
return 1/0;
}
$ gcc a.c
a.c: In function ‘main’:
a.c:3:11: warning: division by zero [-Wdiv-by-zero]
3 | return 1/0;
| ^
Enter: Java.
Java, like all languages, likes to optimize at compile-time in order to improve the run-time speed of a program. A really common way to do this is to pre-calculate constant numeric values during compilation. If you were to code:
class A {
public static final int X = 5 + 3;
}
what gets stored in the compiled class file for field X
is 8. Makes sense, right?
So, what is happening when this code is compiled - without any warnings or errors from the javac
compiler:
class A {
public static final int ONE = 1 / 1;
public static final int INF = 1 / 0;
}
The first field can obviously be pre-calculated to 1. But the second field is not representable in JRE integer format - the divide-by-zero will always throw an ArithmeticException
when the class is loaded.
Now, this is not altogether strange or confusing - the Java language designers just decided to permit all numeric operations during compilation. What is weird, is that supporting this requires more work at compile-time to handle a situation which has no benefit to the developer or end-user. I will try to explain...
When Java source files are compiled, a class file is produced - this is a binary format which contains all the data needed to load and execute methods in the class, including any pre-calculated field values.
Using a decoder, it's possible to look inside the compiled version of the A class above.
{
mods: 32,
thisclass: 'A',
superclass: 'java/lang/Object',
interfaces: [],
fields: [
{
mods: 25,
name: 'ONE',
type: 'I',
attributes: [ { name: 'ConstantValue', info: [ 0, 8 ] } ]
},
{
mods: 25,
name: 'INF',
type: 'I',
attributes: []
}
],
}
(the above has been edited for brevity - there's a lot more in the class file!)
The bit to notice is that the 'ONE' field has ConstantValue
attribute, but the 'INF' field doesn't. In fact, the 'INF' field is "calculated" in code, as part of a generated class-initialisation routine executed when the class is loaded - during that calculation, the exception is thrown.
In order to do this, the Java compiler writers need to specifically look out for division-by-zero situations and then code up a completely separate chunk of code in a generated function to throw the inevitable error, stopping the class from ever being loaded at runtime.
Now Java's raison d'être is to run across any platform with a compatible runtime (remember the "write once, run anywhere" mantra...), so it's perfectly possible that an alternate platform allows infinity-integers (although I can't think of any - let me know if you've seen one)
I'd have preferred a trivial compile-time
Error: division by zero.
but as someone much wiser than me stated: "writing languages is hard".
Posted on June 21, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.