61

I was reading about floating-point NaN values in the Java Language Specification (I'm boring). A 32-bit float has this bit format:

seee eeee emmm mmmm mmmm mmmm mmmm mmmm

s is the sign bit, e are the exponent bits, and m are the mantissa bits. A NaN value is encoded as an exponent of all 1s, and the mantissa bits are not all 0 (which would be +/- infinity). This means that there are lots of different possible NaN values (having different s and m bit values).

On this, JLS §4.2.3 says:

IEEE 754 allows multiple distinct NaN values for each of its single and double floating-point formats. While each hardware architecture returns a particular bit pattern for NaN when a new NaN is generated, a programmer can also create NaNs with different bit patterns to encode, for example, retrospective diagnostic information.

The text in the JLS seems to imply that the result of, for example, 0.0/0.0, has a hardware-dependent bit pattern, and depending on whether that expression was computed as a compile time constant, the hardware it is dependent on might be the hardware the Java program was compiled on or the hardware the program was run on. This all seems very flaky if true.

I ran the following test:

System.out.println(Integer.toHexString(Float.floatToRawIntBits(0.0f/0.0f)));
System.out.println(Integer.toHexString(Float.floatToRawIntBits(Float.NaN)));
System.out.println(Long.toHexString(Double.doubleToRawLongBits(0.0d/0.0d)));
System.out.println(Long.toHexString(Double.doubleToRawLongBits(Double.NaN)));

The output on my machine is:

7fc00000
7fc00000
7ff8000000000000
7ff8000000000000

The output shows nothing out of the expected. The exponent bits are all 1. The upper bit of the mantissa is also 1, which for NaNs apparently indicates a "quiet NaN" as opposed to a "signalling NaN" (https://en.wikipedia.org/wiki/NaN#Floating_point). The sign bit and the rest of the mantissa bits are 0. The output also shows that there was no difference between the NaNs generated on my machine and the constant NaNs from the Float and Double classes.

My question is, is that output guaranteed in Java, regardless of the CPU of the compiler or VM, or is it all genuinely unpredictable? The JLS is mysterious about this.

If that output is guaranteed for 0.0/0.0, are there any arithmetic ways of producing NaNs that do have other (possibly hardware-dependent?) bit patterns? (I know intBitsToFloat/longBitsToDouble can encode other NaNs, but I'd like to know if other values can occur from normal arithmetic.)


A followup point: I've noticed that Float.NaN and Double.NaN specify their exact bit pattern, but in the source (Float, Double) they are generated by 0.0/0.0. If the result of that division is really dependent on the hardware of the compiler, it seems like there is a flaw there in either the spec or the implementation.

Boann
  • 48,794
  • 16
  • 117
  • 146

4 Answers4

39

This is what §2.3.2 of the JVM 7 spec has to say about it:

The elements of the double value set are exactly the values that can be represented using the double floating-point format defined in the IEEE 754 standard, except that there is only one NaN value (IEEE 754 specifies 253-2 distinct NaN values).

and §2.8.1:

The Java Virtual Machine has no signaling NaN value.

So technically there is only one NaN. But §4.2.3 of the JLS also says (right after your quote):

For the most part, the Java SE platform treats NaN values of a given type as though collapsed into a single canonical value, and hence this specification normally refers to an arbitrary NaN as though to a canonical value.

However, version 1.3 of the Java SE platform introduced methods enabling the programmer to distinguish between NaN values: the Float.floatToRawIntBits and Double.doubleToRawLongBits methods. The interested reader is referred to the specifications for the Float and Double classes for more information.

Which I take to mean exactly what you and CandiedOrange propose: It is dependent on the underlying processor, but Java treats them all the same.

But it gets better: Apparently, it is entirely possible that your NaN values are silently converted to different NaNs, as described in Double.longBitsToDouble():

Note that this method may not be able to return a double NaN with exactly same bit pattern as the long argument. IEEE 754 distinguishes between two kinds of NaNs, quiet NaNs and signaling NaNs. The differences between the two kinds of NaN are generally not visible in Java. Arithmetic operations on signaling NaNs turn them into quiet NaNs with a different, but often similar, bit pattern. However, on some processors merely copying a signaling NaN also performs that conversion. In particular, copying a signaling NaN to return it to the calling method may perform this conversion. So longBitsToDouble may not be able to return a double with a signaling NaN bit pattern. Consequently, for some long values, doubleToRawLongBits(longBitsToDouble(start)) may not equal start. Moreover, which particular bit patterns represent signaling NaNs is platform dependent; although all NaN bit patterns, quiet or signaling, must be in the NaN range identified above.

For reference, there is a table of the hardware-dependant NaNs here. In summary:

- x86:     
   quiet:      Sign=0  Exp=0x7ff  Frac=0x80000
   signalling: Sign=0  Exp=0x7ff  Frac=0x40000
- PA-RISC:               
   quiet:      Sign=0  Exp=0x7ff  Frac=0x40000
   signalling: Sign=0  Exp=0x7ff  Frac=0x80000
- Power:
   quiet:      Sign=0  Exp=0x7ff  Frac=0x80000
   signalling: Sign=0  Exp=0x7ff  Frac=0x5555555500055555
- Alpha:
   quiet:      Sign=0  Exp=0      Frac=0xfff8000000000000
   signalling: Sign=1  Exp=0x2aa  Frac=0x7ff5555500055555

So, to verify this you would really need one of these processors and go try it out. Also any insights on how to interpret the longer values for the Power and Alpha architectures are welcome.

Community
  • 1
  • 1
jmiserez
  • 2,991
  • 1
  • 23
  • 34
  • 2
    +1 for mentioning of the concept "canonical NaN". There are millions of valid NaN bit patterns but only one canonical NaN. The OP could have better asked if the JVM is required by its specification to always produce a canonical NaN when it produces a NaN. It seems this is not required, so there is room for hardware dependency. – Mishax Jul 31 '14 at 09:29
  • 2
    "Apparently, it is entirely possible that your NaN values are silently converted to different NaNs, as described in Double.longBitsToDouble()" I have observed that in practice on my x86 AMD CPU. If I generate floats with bit patterns in the range 7f800001 through 7f8036a9, they are silently converted to 7fc00001 through 7fc036a9 (i.e., it sets the quiet NaN bit) before I even get a chance to look at them. – Boann Jul 31 '14 at 12:32
  • I don't think those NaN values for `Alpha` can be right: `0x2aa8000000000000` is a representation of a finite number (`3.348595872897289986960303364....E-103` to be precise) in IEEE 754, and as far as I'm aware the VAX G-floating point format supported by some Alpha machines doesn't have NaNs. – Mark Dickinson Jul 31 '14 at 14:07
  • It's [true enough](http://h21007.www2.hp.com/portal/download/files/unprot/parisc20/PA_8_FP_coprocessor.pdf) about the PA-RISC though, unfortunately. That goes against IEEE 754, which *recommends* but doesn't *require* that a signalling NaN "should" have the first bit of the trailing significand field being 0, while a quiet NaN "should" have the first bit of the trailing significand field being 1. (Section 6.2.1 of the standard, if you have a copy.) – Mark Dickinson Jul 31 '14 at 14:57
  • @MarkDickinson 1. I think I've misinterpreted the values in the linked table for the NaN bit pattern. If you know how to properly read that table, feel free to add the values back in. 2. VAX Floating Point has a [completely different layout of Sign bit, Exponent, and Fraction](http://nssdc.gsfc.nasa.gov/nssdc/formats/VAXFloatingPoint.htm) inside the bit pattern, which could explain some of the strange values. 3. Apparently, the [bias is also different](http://pubs.usgs.gov/of/2005/1424/of2005-1424_v1.2.pdf) – jmiserez Jul 31 '14 at 20:27
  • 1
    @jmiserez +1, nicely researched answer. It provides the perfect evidence for why java refuses to label NaN's as signalling or quiet. The x86 and PA-RISC both use the same values but give them opposite meanings. No wonder java throws up it's hands and just says: It's a NaN. – candied_orange Aug 01 '14 at 04:26
  • 1
    @jmiserez: Yes, I'm not sure how to read that table; it doesn't make much sense to me. And yes, VAX Floating point is different; but it's different to the extent that it doesn't even *have* NaNs (or infinities), so if the table is referring to VAX Floating point then it doesn't make sense to have a NaN bit pattern at all. Frankly, I think the author of the table is confused. :-) – Mark Dickinson Aug 01 '14 at 06:43
  • Is the purpose of `doubleToRawLongBits` really to allow reading out bits, or is the purpose to allow applications to more quickly read out bits in the case that they don't care about the exact value reported for a NaN? On many processors, the time taken by the extra logic required to handle `doubleToLongBits` is non-trivial. – supercat Feb 21 '15 at 23:47
16

The way I read the JLS here the exact bit value of a NaN is dependent on who/what made it and since the JVM didn't make it, don't ask them. You might as well ask them what an "Error code 4" string means.

The hardware produces different bit patterns meant to represent different kinds of NaN's. Unfortunately the different kinds hardware produce different bit patterns for the same kinds of NaN's. Fortunately there is a standard pattern that Java can use to at least tell that it is some kind of NaN.

It's like Java looked at the "Error code 4" string and said, "We don't know what 'code 4' means on your hardware, but there was the word 'error' in that string, so we think it's an error."

The JLS tries to give you a chance to figure it out on your own though:

"However, version 1.3 of the Java SE platform introduced methods enabling the programmer to distinguish between NaN values: the Float.floatToRawIntBits and Double.doubleToRawLongBits methods. The interested reader is referred to the specifications for the Float and Double classes for more information."

Which looks to me like a C++ reinterpret_cast. It's Java giving you a chance to analyze the NaN yourself in case you happen to know how its signal was encoded. If you want to track down the hardware specs so you can predict what different events should produce which NaN bit patterns you are free to do so but you are outside the uniformity the JVM was meant to give us. So expect it might change from hardware to hardware.

When testing if a number is NaN we check if it's equal to itself since it's the only number that isn't. This isn't to say that the bits are different. Before comparing the bits the JVM tests for the many bit patterns that say it's a NaN. If it's any of those patterns then it reports that it's not equal, even if the bits of the two operands really are the same (and even if they're different).

Back in 1964, when pressed to give an exact definition for pornography, U.S. Supreme Court Justice Stewart famously said, “I Know It When I See It”. I think of Java as doing the same thing with NaN's. Java can't tell you anything that a "signaling" NaN might be signaling cause it doesn't know how that signal was encoded. But it can look at the bits and tell it's a NaN of some kind since that pattern follows one standard.

If you happen to be on hardware that encodes all NaN's with uniform bits you'll never prove that Java is doing anything to make NaN's have uniform bits. Again, the way I read the JLS, they are outright saying you are on your own here.

I can see why this feels flaky. It is flaky. It's just not Java's fault. I'll lay odds that some where out there some enterprising hardware manufactures came up with wonderfully expressive signaling NaN bit patterns but they failed to get it adopted as a standard widely enough that Java can count on it. That's what's flaky. We have all these bits reserved for signalling what kind of NaN we have and can't use them because we don't agree what they mean. Having Java beat NaN's into a uniform value after the hardware makes them would only destroy that information, harm performance, and the only payoff is to not seem flaky. Given the situation, I'm glad they realized they could cheat their way out of the problem and define NaN as not being equal to anything.

Nayuki
  • 17,911
  • 6
  • 53
  • 80
candied_orange
  • 7,036
  • 2
  • 28
  • 62
  • 3
    Re last sentence: It's not Java that defined NaN as not being equal to anything, and that's not what the question is about anyway... – user253751 Jul 31 '14 at 08:33
  • 4
    +1 This answer makes a lot of sense to me. Java doesn't guarantee that NaN will have a particular bit pattern because it's designed to make nearly any program cross-platform, and every platform will have its own hardware representation of NaN. – Kevin Jul 31 '14 at 16:38
  • 1
    @immibis IEEE defined that a comparison involving a NaN would always return false. Java defined equal as giving you that comparison. They didn't have to. They could have defined two signalling NaN's as being equal. If they had a reliable way to identify types of NaN's that might have even worked. But they didn't find a way. Other languages tried to find a way to do it: http://www.johndcook.com/blog/2009/07/21/ieee-arithmetic-python/ – candied_orange Aug 05 '14 at 03:57
15

Here is a program demonstrating different NaN bit patterns:

public class Test {
  public static void main(String[] arg) {
    double myNaN = Double.longBitsToDouble(0x7ff1234512345678L);
    System.out.println(Double.isNaN(myNaN));
    System.out.println(Long.toHexString(Double.doubleToRawLongBits(myNaN)));
    final double zeroDivNaN = 0.0 / 0.0;
    System.out.println(Double.isNaN(zeroDivNaN));
    System.out
        .println(Long.toHexString(Double.doubleToRawLongBits(zeroDivNaN)));
  }
}

output:

true
7ff1234512345678
true
7ff8000000000000

Regardless of what the hardware does, the program can itself create NaNs that may not be the same as e.g. 0.0/0.0, and may have some meaning in the program.

Patricia Shanahan
  • 25,849
  • 4
  • 38
  • 75
5

The only other NaN value that I could generate with normal arithmetic operations so far is the same but with the sign changed:

public static void main(String []args) {
    Double tentative1 = 0d/0d;
    Double tentative2 = Math.sqrt(-1d);
    
    System.out.println(tentative1);
    System.out.println(tentative2);
    
    System.out.println(Long.toHexString(Double.doubleToRawLongBits(tentative1)));
    System.out.println(Long.toHexString(Double.doubleToRawLongBits(tentative2)));
    
    System.out.println(tentative1 == tentative2);
    System.out.println(tentative1.equals(tentative2));
}

Output:

NaN

NaN

7ff8000000000000

fff8000000000000

false

true

Community
  • 1
  • 1
falsarella
  • 12,217
  • 9
  • 69
  • 115