The value of floatToRawIntBits(0.0f/0.0f) is different on x86_64 and aarch64 platforms

#1

The value of floatToRawIntBits(0.0f/0.0f) is different on x86_64 and aarch64 platforms:

on x86_64

root@donotdel-openlab-allinone-l00242678:/home/ubuntu# uname -a
Linux donotdel-openlab-allinone-l00242678 4.4.0-154-generic #181-Ubuntu SMP Tue Jun 25 05:29:03 UTC
2019 x86_64 x86_64 x86_64 GNU/Linux
scala> import java.lang.Float.floatToRawIntBits
import java.lang.Float.floatToRawIntBits
scala> floatToRawIntBits(0.0f/0.0f)
res0: Int = -4194304
scala> floatToRawIntBits(Float.NaN)
res1: Int = 2143289344

on aarch64

[root@arm-huangtianhua spark]# uname -a
Linux arm-huangtianhua 4.14.0-49.el7a.aarch64 #1 SMP Tue Apr 10 17:22:26 UTC 2018 aarch64 aarch64 aarch64 GNU/Linux
scala> import java.lang.Float.floatToRawIntBits
import java.lang.Float.floatToRawIntBits
scala> floatToRawIntBits(0.0f/0.0f)
res1: Int = 2143289344
scala> floatToRawIntBits(Float.NaN)
res2: Int = 2143289344

I think the behaviour should be same on aarch64 and x86_64 for scala, right? If I was wrong, welcome to tell me, thank you.

Also I took tests for java on these two platforms too, and the results are same for java, the results of floatToRawIntBits(0.0f/0.0f) | floatToRawIntBits(-0.0f/0.0f) | floatToRawIntBits(Float.NaN) are all Int = 2143289344.

In fact I was run the scala tests of apache/spark on aarch64 platform, see https://github.com/apache/spark/blob/master/sql/core/src/test/scala/org/apache/spark/sql/DataFrameAggregateSuite.scala#L732 and the test failed, because the result is equals on aarch64 platform(Int = 2143289344).

I am not sure this is a bug of scala, if you are interesting on this, please help to go deep into this, thank you all.

#2

And according https://docs.oracle.com/javase/8/docs/api/java/lang/Float.html#floatToRawIntBits-float- seems they should be same, because 0.0f/0.0f is NaN, right? So why they are different in scala?

#3

I think it is a pretty low level stuff and since Scala compiles to Java bytecode, I would also check that function in pure Java code. There’s a high chance that Scala compiler has nothing to do with that buggy behaviour.

#4

@tarsa, thanks for your attention.
I took tests for java on these two platforms, the results of aarch64 and x86_64 are same, as below:
public class javaTest
{
public static void main(String[] args)
{
System.out.println(java.lang.Float.floatToRawIntBits(0.0f/0.0f));
System.out.println(java.lang.Float.floatToRawIntBits(-0.0f/0.0f));
System.out.println(java.lang.Float.floatToRawIntBits(Float.NaN));
}
}
[root@arm-huangtianhua javaTest]# java javaTest
2143289344
2143289344
2143289344

#6

The reason that Java produces different results than Scala is that the constant folding in the Java compiler is implemented differently. Both scalac and javac simply replace the expression 0.0f/0.0f with the constant NaN, but javac replaces it with Float.NaN and scalac replaces it with the actual result of calculating 0.0f/0.0f. You can see here that the result in Java is actually the same without constant folding:

$ cat C.java 
public class C {
  public float a() {
    return 0f;
  }

  public int foo() {
    return java.lang.Float.floatToRawIntBits(0f / 0f);
  }

  public int bar() {
    return java.lang.Float.floatToRawIntBits(a() / a());
  }
}

$ sbt console
Welcome to Scala 2.13.0 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_152).
Type in expressions for evaluation. Or try :help.

scala> new C().foo
res0: Int = 2143289344

scala> new C().bar
res1: Int = -4194304
#7

Thank you very much. So this explains why scala has different values on x86_64(compare with java). But the most thing confused me is that the behaviour of scala is different between x86_64 and aarch64 platforms:
[root@arm-huangtianhua spark]# uname -a
Linux arm-huangtianhua 4.14.0-49.el7a.aarch64 #1 SMP Tue Apr 10 17:22:26 UTC 2018 aarch64 aarch64 aarch64 GNU/Linux
scala> import java.lang.Float.floatToRawIntBits
import java.lang.Float.floatToRawIntBits
scala> floatToRawIntBits(0.0f/0.0f)
res1: Int = 2143289344
scala> floatToRawIntBits(Float.NaN)
res2: Int = 2143289344

#8

Does it make a difference when you add the @strictfp annotation to the method or class where you do the calculations?

#9

No. And I write the simple test code on aarch64 as below:
object HelloWorld{
def main(args : Array[String]){
import java.lang.Float.floatToRawIntBits
println(floatToRawIntBits(0.0f/0.0f))
println(floatToRawIntBits(-0.0f/0.0f))
println(floatToRawIntBits(Float.NaN))
}
}
then result is:
2143289344
2143289344
2143289344

#10

Sorry, I don’t understand this explanation. If “scalac replaces it with the actual result of calculating 0.0f/0.0f”, what is that actual result, and why is it not Float.NaN? Thanks!

#11

NaN has many possible representations at the bit level. Float.NaN is one such representation, more specifically it is equivalent to Float.intBitsToFloat(0x7fc00000). Apparently the CPU returns a different representation when it computes 0.0f/0.0f. It isNaN, but it is not made up of exactly the same bits as Float.NaN. I don’t know enough about how floating point calculations, especially the ones involving NaN or Infinity, happen to tell you exactly why. But that’s what I inferred from the code I ran and the bytecode it compiled to.

1 Like
#12

There is a surprisingly good Stack Overflow post for this.

Basically, the Java SE platform has a canonical NaN value, but the JVM itself doesn’t have a single NaN. The programmer can distinguish between different NaN representations using methods like Float.floatToRawIntBits.

It sounds to me like this is expected behavior for the JVM, and is not specific to Scala.

#13

Hi
Thanks very much, you guys.
Now, we know the different implements on x86(As we all test them for java and scala). Then the next question is why the scala runing the test on arm, and the result is the same, which doesn’t like we analysis in the previous comments(But java keep the same behavior on both ARCH).
Then if that is true a issue, how to fix it. Should we need to keep scalac with the same behavior like x86? Or we need to fix the gap between javac and scalac, make it like javac does?(The same value on different ARCH platfom)
So this problem really need your kind help. Wishing your reply on this. Thank you

#14

Actually scalac’s behavior looks more correct to me. In Java the value of the expression floatToRawIntBits(a() / a()) changes when you inline a(), in Scala it doesn’t.

1 Like
#15

Thank you all to discuss this.
Sorry I am not familiar with this area, let’s assume scalac’s behavior is correct, then my question is that why the ‘behavior’ of scala is different between x86_64 and aarch64 platforms?

#16

@Jasper-M, I took test with your example java code, on aarch64 platform it returns ‘Int = 2143289344’ when print bar(), instead on x86_64 platform it returns ‘Int = -4194304’ . So perhaps it depends on the hardware?