In the last two posts (“RTFM” and “RTFS”) I looked at the importance of reading the manual and the screen.
In this post, I want to emphasize the importance of knowledge and observation.
Knowledge — knowing the rules of the Java language.
Observation — really seeing what you’re looking at while you’re coding and debugging.
Example 1
Let’s look at a small example:
public class Example {
public static void main (String args[]) {
System.out.println("7/4 = " + 7/4); // line 1.
System.out.println("7./4 = " + 7./4); // line 2.
}
} // end of class
What happens when you run the program? What gets printed?
The output will be the following:
7/4 = 1
7./4 = 1.75
Why two different values? Did you expect that? We all know that in real life when we divide seven by four we get 1.75. But here we get two different results. Why?
We know that Java is a strongly typed language. Every variable, literal and expression has a type. When mixing compatible types in an expression, we must be aware of the rules of type promotion and the type of the resulting value.
Java language rules say that a literal number containing only digits and no decimal point has the default type of int
. If a literal number contains digits and a decimal point it defaults to type double
.
So the expression 7/4
in line 1 contains an int
divided by another int
. This can only result in another int
, i.e. the value of 1
.
However, the expression 7./4
in line 2 has a decimal point after the 7. The expression then contains a double
divided by an int
. The int
literal is then promoted to a double
to balance the operation. The expression becomes a double
divided by an double
resulting in a double
with the value of 1.75
.
The single decimal point is easy to miss when debugging your code, so an eagle eye is needed. Two sets of eyes are better than one, so enlist the help of a colleague if you’re battling when debugging.
Example 2
Let’s add three more lines to the previous code:
public class Example {
public static void main (String args[]) {
System.out.println("7/4 = " + 7/4); // line 1.
System.out.println("7./4 = " + 7./4); // line 2.
int value = 42;
System.out.println("value/0.0 = " + value/0.0); // line 3.
System.out.println("value/0 = " + value/0); // line 4.
}
} // end of class
What happens when you run the program?
The output is now:
7/4 = 1
7./4 = 1.75
value/0.0 = Infinity
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Example.main(Example.java:10)
Another “What?” Was that expected? Again we get two different results.
This was caused by a decimal point in the number in line 3, but no decimal point in line 4. This is the same cause as the different values in example 1, but here there is a very different end result with a more complicated explanation.
We know that in mathematics if we divide anything by zero, we get infinity as a result. So the output from line 3 should have been expected. But line 4 throws an ArithmeticException: / by zero
? What happened there?
The answer is that floating point types and integral types are represented internally in computer memory in different ways.
Integral data types are stored in positional notation where each bit represents a power of two. The leftmost bit of the number is used as the sign bit (0 for positive numbers; 1 for negative numbers). Negative numbers are represented using two’s complement arithmetic. Integral data types have no representation of infinity.
Floating point data types are stored in mantissa and exponent form where the number and the exponent are stored separately in different bytes of the double
and float
. Floating point numbers follow the IEEE–754 Standard for Floating-Point Arithmetic. This was established in 1985 by the Institute of Electrical and Electronics Engineers (IEEE) to solve problems and promote portability across the many different floating-point implementations at the time.
Not only are floating point and integral data types represented differently in memory, but they are processed by different parts of the computer’s CPU (central processing unit). An ALU (arithmetic logic unit) performs arithmetic and bitwise operations on integral binary numbers. An FPU (floating-point unit) operates on floating point numbers. Most hardware FPUs use the IEEE 754 standard.
Compiler Warnings
We can get some help from the Java compiler to help us catch at least one of the problems we came across in this post.
The compiler has a number of options (some standard; some non-standard) that we can use when compiling code. A very useful option is the -Xlint
non-standard option. This can warn us of a variety of potential problems.
We can use -Xlint:divzero
to warn us of dividing by a integral literal zero.
Running javac -Xlint:divzero Example.java
would give us the following output:
Example.java:10: warning: [divzero] division by zero
System.out.println("value/0 = " + value/0);
Both -Xlint
and -Xlint:all
can be used to enable all the available compiler warnings. Enabling warnings is highly recommended.
Ending Off
These two simple examples stress the importance of having sharp eyes, reading the screen carefully and knowing the Java language rules well.
I hope you’ll share your comments and Java experiences. And if you found this post useful, please share it with others.
1 thought on “Observation, Knowledge and the Decimal Point”
Pingback: NaN - Not a Number • 2022 • Incus Data Programming Courses