45 Float.NaN == Float.NaN 为 false 是怎么实现的?

Posted 蓝风9

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了45 Float.NaN == Float.NaN 为 false 是怎么实现的?相关的知识,希望对你有一定的参考价值。

前言

呵呵 这是很久之前 看 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."<init>":()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

jls 关于 NaN 计算的说明  

4.2.3. Floating-Point Types, Formats, and Values

可以看到的是 我们上面的 NaN 的 "==", "!=" 的结果是符合规范的描述的  

另外在 jvms 里面 float 提供了两个操作的字节码 

差异仅仅在于 NaN 的情况

Float.NaN == 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 the int value -1 onto the operand stack.

可以看到 这里比较还是根据 float 进行的比较, 那这不就成了套娃了? 那么更下层的计算是怎么处理的呢?, 这才是我们需要关注的"根本原因"

上面拿到的 t1 值为 -1, 经过 fold1 处理之后, 转换为 ifeq 期望的 bool, 值为 false 

HotSpotVM 中的 float 比较

此篇幅截图基于 jdk9 

为了更友好一些, 我们直接看 byteCodeInterceptor 中的实现逻辑 

VMfloatCompare 的实现如下, 是通过 jfloat 的 大于, 等于, 小于 等操作符来控制的 

我们这里还需要明确一下 jfloat 是什么, java 中的 float 映射到 c 层面是什么呢? 

呵呵 就是 c 中普通的 float 类型 

那么 我们来验证一下 c 中的 NaN 呢? 

c/c++ 中的 float 比较

测试用例如下 

//
// 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;

执行结果如下 

看到了吧, 到这里 就先到此为止了 

再往下, 可能需要参考 浮点数 的相关规范了 

完 

以上是关于45 Float.NaN == Float.NaN 为 false 是怎么实现的?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 float.NaN != double.NaN 在 C# 中?

浮点数.NaN == 浮点数.NaN

如何从python中的混合数据类型列表中删除nan(float)项目[重复]

如何检查单元测试中的值是不是为nan?

Python:无法将float NaN转换为整数

Python3 Infinity/NaN:十进制与浮点数