Minimal Executable Example
The first thing you should know is that the NaN concept is implemented directly on the CPU equipment.
All major modern processors seem to follow IEEE 754, which defines floating point formats, and NaN, which are just special floating point values, are part of this standard.
Therefore, the concept will be very similar to any language, including Java, which simply passes floating point code directly to the CPU.
Before continuing, you can first read the following answers that I wrote:
- IEEE 754 quick update format: what is a subnormal floating point number?
- Some basics of lower-level NaN described in C / C ++: what is the difference between quiet NaN and signal NaN?
Now for some Java actions. Most functions of interest that are not in the main language live inside java.lang.Float .
Nan.java
import java.lang.Float; import java.lang.Math; public class Nan { public static void main(String[] args) { // Generate some NaNs. float nan = Float.NaN; float zero_div_zero = 0.0f / 0.0f; float sqrt_negative = (float)Math.sqrt(-1.0); float log_negative = (float)Math.log(-1.0); float inf_minus_inf = Float.POSITIVE_INFINITY - Float.POSITIVE_INFINITY; float inf_times_zero = Float.POSITIVE_INFINITY * 0.0f; float quiet_nan1 = Float.intBitsToFloat(0x7fc00001); float quiet_nan2 = Float.intBitsToFloat(0x7fc00002); float signaling_nan1 = Float.intBitsToFloat(0x7fa00001); float signaling_nan2 = Float.intBitsToFloat(0x7fa00002); float nan_minus = -nan; // Generate some infinities. float positive_inf = Float.POSITIVE_INFINITY; float negative_inf = Float.NEGATIVE_INFINITY; float one_div_zero = 1.0f / 0.0f; float log_zero = (float)Math.log(0.0); // Double check that they are actually NaNs. assert Float.isNaN(nan); assert Float.isNaN(zero_div_zero); assert Float.isNaN(sqrt_negative); assert Float.isNaN(inf_minus_inf); assert Float.isNaN(inf_times_zero); assert Float.isNaN(quiet_nan1); assert Float.isNaN(quiet_nan2); assert Float.isNaN(signaling_nan1); assert Float.isNaN(signaling_nan2); assert Float.isNaN(nan_minus); assert Float.isNaN(log_negative); // Double check that they are infinities. assert Float.isInfinite(positive_inf); assert Float.isInfinite(negative_inf); assert !Float.isNaN(positive_inf); assert !Float.isNaN(negative_inf); assert one_div_zero == positive_inf; assert log_zero == negative_inf; // Double check infinities. // See what they look like. System.out.printf("nan 0x%08x %f\n", Float.floatToRawIntBits(nan ), nan ); System.out.printf("zero_div_zero 0x%08x %f\n", Float.floatToRawIntBits(zero_div_zero ), zero_div_zero ); System.out.printf("sqrt_negative 0x%08x %f\n", Float.floatToRawIntBits(sqrt_negative ), sqrt_negative ); System.out.printf("log_negative 0x%08x %f\n", Float.floatToRawIntBits(log_negative ), log_negative ); System.out.printf("inf_minus_inf 0x%08x %f\n", Float.floatToRawIntBits(inf_minus_inf ), inf_minus_inf ); System.out.printf("inf_times_zero 0x%08x %f\n", Float.floatToRawIntBits(inf_times_zero), inf_times_zero); System.out.printf("quiet_nan1 0x%08x %f\n", Float.floatToRawIntBits(quiet_nan1 ), quiet_nan1 ); System.out.printf("quiet_nan2 0x%08x %f\n", Float.floatToRawIntBits(quiet_nan2 ), quiet_nan2 ); System.out.printf("signaling_nan1 0x%08x %f\n", Float.floatToRawIntBits(signaling_nan1), signaling_nan1); System.out.printf("signaling_nan2 0x%08x %f\n", Float.floatToRawIntBits(signaling_nan2), signaling_nan2); System.out.printf("nan_minus 0x%08x %f\n", Float.floatToRawIntBits(nan_minus ), nan_minus ); System.out.printf("positive_inf 0x%08x %f\n", Float.floatToRawIntBits(positive_inf ), positive_inf ); System.out.printf("negative_inf 0x%08x %f\n", Float.floatToRawIntBits(negative_inf ), negative_inf ); System.out.printf("one_div_zero 0x%08x %f\n", Float.floatToRawIntBits(one_div_zero ), one_div_zero ); System.out.printf("log_zero 0x%08x %f\n", Float.floatToRawIntBits(log_zero ), log_zero ); // NaN comparisons always fail. // Therefore, all tests that we will do afterwards will be just isNaN. assert !(1.0f < nan); assert !(1.0f == nan); assert !(1.0f > nan); assert !(nan == nan); // NaN propagate through most operations. assert Float.isNaN(nan + 1.0f); assert Float.isNaN(1.0f + nan); assert Float.isNaN(nan + nan); assert Float.isNaN(nan / 1.0f); assert Float.isNaN(1.0f / nan); assert Float.isNaN((float)Math.sqrt((double)nan)); } }
GitHub upstream .
Run with:
javac Nan.java && java -ea Nan
Exit:
nan 0x7fc00000 NaN zero_div_zero 0x7fc00000 NaN sqrt_negative 0xffc00000 NaN log_negative 0xffc00000 NaN inf_minus_inf 0x7fc00000 NaN inf_times_zero 0x7fc00000 NaN quiet_nan1 0x7fc00001 NaN quiet_nan2 0x7fc00002 NaN signaling_nan1 0x7fa00001 NaN signaling_nan2 0x7fa00002 NaN nan_minus 0xffc00000 NaN positive_inf 0x7f800000 Infinity negative_inf 0xff800000 -Infinity one_div_zero 0x7f800000 Infinity log_zero 0xff800000 -Infinity
So, from this we learn a few things:
weird floating operations that have no reasonable result give NaN:
0.0f/0.0fsqrt(-1.0f)log(-1.0f)
generate NaN .
In C, you can actually request signals that should be feenableexcept in such operations using feenableexcept for feenableexcept to detect them, but I don’t think it manifests itself in Java: why integer division by zero 1/0 gives an error, but with a floating point 1 / 0.0 returns "inf"?
strange operations that are at the limit of plus or minus infinity, but give + - infinity instead of NaN
0.0 almost falls into this category, but most likely the problem is that it can go into plus or minus infinity, so it was left as NaN.
if NaN is the input of a floating operation, the output also tends to be NaN
There are several possible values for NaN 0x7fc00000 , 0x7fc00001 , 0x7fc00002 , although x86_64 seems to only generate 0x7fc00000 .
NaN and infinity have the same binary representation.
Let's look at a few of them:
nan = 0x7fc00000 = 0 11111111 10000000000000000000000 positive_inf = 0x7f800000 = 0 11111111 00000000000000000000000 negative_inf = 0xff800000 = 1 11111111 00000000000000000000000 | | | | | mantissa | exponent | sign
Based on this, we confirm that IEEE754 indicates:
- both NaN and infinity have an exponent == 255 (all units)
- infinities have a mantissa == 0. Therefore, only two infinities are possible: + and 0, differentiated by the sign bit
- NaN has a mantissa! = 0. Therefore, there are several possibilities, except for the mantissa == 0, which is equal to infinity
NaN can be both positive and negative (upper bit), although this does not affect normal operations
Tested on Ubuntu 18.10 amd64, OpenJDK 1.8.0_191.