呵呵 这是很久之前 看 jls 的时候就存在的疑惑, 当时写了 case 来看, 果然结论 和 jls 的规范是一致的
但是 从来没有思考过 为什么会这样, 然后 具体又是 怎么实现的 ?
回溯一下用例, 可以回溯到 2019 年, 但是 实际的应该还有 这个问题的更早的用例
呵呵 今天就来看一下
- /**
- * Test12FloatEquals
- *
- * @author Jerry.X.He <970655147@qq.com>
- * @version 1.0
- * @date 2019-08-23 15:21
- */
- public class Test12FloatEquals {
-
- // Test12FloatEquals
- public static void main(String[] args) {
-
- System.out.println(Float.NaN == Float.NaN);
- System.out.println(new Float(Float.NaN).equals(new Float(Float.NaN)));
-
- System.out.println(+0.0f == -0.0f);
- System.out.println(new Float(+0.0f).equals(new Float(-0.0f)));
-
- }
-
- }
以下代码的截图, 调试基于 jdk8
- /**
- * Test12FloatEquals
- *
- * @author Jerry.X.He <970655147@qq.com>
- * @version 1.0
- * @date 2021-10-31 10:03
- */
- public class Test12FloatEquals {
-
- // Test12FloatEquals
- public static void main(String[] args) throws Exception {
-
- float NaN = 0.0f / 0.0f;
- float Max = 1.0f / 0.0f;
- float Min = -1.0f / 0.0f;
-
- boolean equals01 = Float.NaN == Float.NaN;
- boolean equals02 = NaN == NaN;
- boolean equals03 = NaN != NaN;
- boolean equals04 = Max == Max;
- boolean equals05 = Min == Min;
-
- System.in.read();
-
- }
-
- }
然后结果如下, 我们这里主要关注的是 NaN 相关的计算
反编译得到字节码相关信息, 这里直接使用 Float.NaN 的计算是被常量折叠了, 然后 "自己计算得" NaN 是没有被折叠
- master:test12 jerry$ javap -c Test12FloatEquals.class
- Compiled from "Test12FloatEquals.java"
- public class com.hx.test12.Test12FloatEquals {
- public com.hx.test12.Test12FloatEquals();
- Code:
- 0: aload_0
- 1: invokespecial #1 // Method java/lang/Object."
" :()V - 4: return
-
- public static void main(java.lang.String[]) throws java.lang.Exception;
- Code:
- 0: ldc #2 // float NaNf
- 2: fstore_1
- 3: ldc #3 // float Infinityf
- 5: fstore_2
- 6: ldc #4 // float -Infinityf
- 8: fstore_3
- 9: iconst_0
- 10: istore 4
- 12: fload_1
- 13: fload_1
- 14: fcmpl
- 15: ifne 22
- 18: iconst_1
- 19: goto 23
- 22: iconst_0
- 23: istore 5
- 25: fload_1
- 26: fload_1
- 27: fcmpl
- 28: ifeq 35
- 31: iconst_1
- 32: goto 36
- 35: iconst_0
- 36: istore 6
- 38: fload_2
- 39: fload_2
- 40: fcmpl
- 41: ifne 48
- 44: iconst_1
- 45: goto 49
- 48: iconst_0
- 49: istore 7
- 51: fload_3
- 52: fload_3
- 53: fcmpl
- 54: ifne 61
- 57: iconst_1
- 58: goto 62
- 61: iconst_0
- 62: istore 8
- 64: getstatic #6 // Field java/lang/System.in:Ljava/io/InputStream;
- 67: invokevirtual #7 // Method java/io/InputStream.read:()I
- 70: pop
- 71: return
- }
4.2.3. Floating-Point Types, Formats, and Values
可以看到的是 我们上面的 NaN 的 "==", "!=" 的结果是符合规范的描述的
另外在 jvms 里面 float 提供了两个操作的字节码
差异仅仅在于 NaN 的情况
看一下 javac 里面对于上面 Float.NaN == Float.NaN 的处理
Both value1 and value2 must be of type
float
. The values are popped from the operand stack and undergo value set conversion (§2.8.3), resulting in value1' and value2'. A floating-point comparison is performed:
If value1' is greater than value2', the
int
value 1 is pushed onto the operand stack.Otherwise, if value1' is equal to value2', the
int
value 0 is pushed onto the operand stack.Otherwise, if value1' is less than value2', the
int
value -1 is pushed onto the operand stack.Otherwise, at least one of value1' or value2' is NaN. The fcmpg instruction pushes the
int
value 1 onto the operand stack and the fcmpl instruction pushes theint
value -1 onto the operand stack.
可以看到 这里比较还是根据 float 进行的比较, 那这不就成了套娃了? 那么更下层的计算是怎么处理的呢?, 这才是我们需要关注的"根本原因"
上面拿到的 t1 值为 -1, 经过 fold1 处理之后, 转换为 ifeq 期望的 bool, 值为 false
此篇幅截图基于 jdk9
为了更友好一些, 我们直接看 byteCodeInterceptor 中的实现逻辑
VMfloatCompare 的实现如下, 是通过 jfloat 的 大于, 等于, 小于 等操作符来控制的
我们这里还需要明确一下 jfloat 是什么, java 中的 float 映射到 c 层面是什么呢?
呵呵 就是 c 中普通的 float 类型
那么 我们来验证一下 c 中的 NaN 呢?
测试用例如下
- //
- // Created by Jerry.X.He on 2021/10/31.
- //
-
- int main(int argc, char** argv) {
-
- float Nan = 0.0 / 0.0f;
- float Max = 1.0 / 0.0f;
- float Min = -1.0 / 0.0f;
-
- bool equals01 = Nan == Nan;
- bool equals02 = Nan != Nan;
- bool equals03 = Max == Max;
- bool equals04 = Min == Min;
-
- return 0;
- }
执行结果如下
看到了吧, 到这里 就先到此为止了
再往下, 可能需要参考 浮点数 的相关规范了
完