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


    前言

    呵呵 这是很久之前 看 jls 的时候就存在的疑惑, 当时写了 case 来看, 果然结论 和 jls 的规范是一致的  

    但是 从来没有思考过 为什么会这样, 然后 具体又是 怎么实现的 ? 

    回溯一下用例, 可以回溯到 2019 年, 但是 实际的应该还有 这个问题的更早的用例 

    呵呵 今天就来看一下  

    1. /**
    2. * Test12FloatEquals
    3. *
    4. * @author Jerry.X.He <970655147@qq.com>
    5. * @version 1.0
    6. * @date 2019-08-23 15:21
    7. */
    8. public class Test12FloatEquals {
    9. // Test12FloatEquals
    10. public static void main(String[] args) {
    11. System.out.println(Float.NaN == Float.NaN);
    12. System.out.println(new Float(Float.NaN).equals(new Float(Float.NaN)));
    13. System.out.println(+0.0f == -0.0f);
    14. System.out.println(new Float(+0.0f).equals(new Float(-0.0f)));
    15. }
    16. }

    以下代码的截图, 调试基于 jdk8 

    测试用例 

    1. /**
    2. * Test12FloatEquals
    3. *
    4. * @author Jerry.X.He <970655147@qq.com>
    5. * @version 1.0
    6. * @date 2021-10-31 10:03
    7. */
    8. public class Test12FloatEquals {
    9. // Test12FloatEquals
    10. public static void main(String[] args) throws Exception {
    11. float NaN = 0.0f / 0.0f;
    12. float Max = 1.0f / 0.0f;
    13. float Min = -1.0f / 0.0f;
    14. boolean equals01 = Float.NaN == Float.NaN;
    15. boolean equals02 = NaN == NaN;
    16. boolean equals03 = NaN != NaN;
    17. boolean equals04 = Max == Max;
    18. boolean equals05 = Min == Min;
    19. System.in.read();
    20. }
    21. }

    然后结果如下, 我们这里主要关注的是 NaN 相关的计算   

    反编译得到字节码相关信息, 这里直接使用 Float.NaN 的计算是被常量折叠了, 然后 "自己计算得" NaN 是没有被折叠 

    1. master:test12 jerry$ javap -c Test12FloatEquals.class
    2. Compiled from "Test12FloatEquals.java"
    3. public class com.hx.test12.Test12FloatEquals {
    4. public com.hx.test12.Test12FloatEquals();
    5. Code:
    6. 0: aload_0
    7. 1: invokespecial #1 // Method java/lang/Object."":()V
    8. 4: return
    9. public static void main(java.lang.String[]) throws java.lang.Exception;
    10. Code:
    11. 0: ldc #2 // float NaNf
    12. 2: fstore_1
    13. 3: ldc #3 // float Infinityf
    14. 5: fstore_2
    15. 6: ldc #4 // float -Infinityf
    16. 8: fstore_3
    17. 9: iconst_0
    18. 10: istore 4
    19. 12: fload_1
    20. 13: fload_1
    21. 14: fcmpl
    22. 15: ifne 22
    23. 18: iconst_1
    24. 19: goto 23
    25. 22: iconst_0
    26. 23: istore 5
    27. 25: fload_1
    28. 26: fload_1
    29. 27: fcmpl
    30. 28: ifeq 35
    31. 31: iconst_1
    32. 32: goto 36
    33. 35: iconst_0
    34. 36: istore 6
    35. 38: fload_2
    36. 39: fload_2
    37. 40: fcmpl
    38. 41: ifne 48
    39. 44: iconst_1
    40. 45: goto 49
    41. 48: iconst_0
    42. 49: istore 7
    43. 51: fload_3
    44. 52: fload_3
    45. 53: fcmpl
    46. 54: ifne 61
    47. 57: iconst_1
    48. 58: goto 62
    49. 61: iconst_0
    50. 62: istore 8
    51. 64: getstatic #6 // Field java/lang/System.in:Ljava/io/InputStream;
    52. 67: invokevirtual #7 // Method java/io/InputStream.read:()I
    53. 70: pop
    54. 71: return
    55. }

    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 比较

    测试用例如下 

    1. //
    2. // Created by Jerry.X.He on 2021/10/31.
    3. //
    4. int main(int argc, char** argv) {
    5. float Nan = 0.0 / 0.0f;
    6. float Max = 1.0 / 0.0f;
    7. float Min = -1.0 / 0.0f;
    8. bool equals01 = Nan == Nan;
    9. bool equals02 = Nan != Nan;
    10. bool equals03 = Max == Max;
    11. bool equals04 = Min == Min;
    12. return 0;
    13. }

    执行结果如下 

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

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

    完 

  • 相关阅读:
    Android开发PopupWindow的使用
    CMMI认证多少钱?
    Redis源码漂流记(二)-搭建Redis调试环境
    DCMM的定义和基础条件
    【C/C++】智能指针
    Java项目:中药药方管理系统(java+SSM+JSP+bootstrap+Mysql)
    开始在 Windows 上将 Python 用于脚本和自动化
    路由器基础(八):策略路由配置
    【LeetCode-中等题】230. 二叉搜索树中第K小的元素
    网页课程设计大作业——华山旅游网
  • 原文地址:https://blog.csdn.net/u011039332/article/details/121061738