1.起因

让我关注到这一点的起因是一道题:牛客网上的max-points-on-a-line

题目是这么描述的:

Given n points on a 2D plane, find the maximum number of points that lie on the same straight line.

大意就是给我一些点的X,Y坐标,找到过这些点最多的直线,输出这条线上的点数量

于是我就敲出了以下的代码:

import java.util.HashMap;
import java.util.Map;//class Point {
//    int x;
//    int y;
//    Point(int a, int b) { x = a; y = b; }
//}public class Solution {public int maxPoints(Point[] points) {if (points.length <= 2) {return points.length;}int max = 2;for (int i = 0; i < points.length - 1; i++) {Map<Float, Integer> map = new HashMap<>(16);// 记录垂直点数; 当前和Points[i]在一条线上的最大点数; 和Points[i]垂直的点数int ver = 0, cur, dup = 0;for (int j = i + 1; j < points.length; j++) {if (points[j].x == points[i].x) {if (points[j].y != points[i].y) {ver++;} else {dup++;}} else {float d = (float)((points[j].y - points[i].y) / (double) (points[j].x - points[i].x));map.put(d, map.get(d) == null ? 1 : map.get(d) + 1);}}cur = ver;for (int v : map.values()) {cur = Math.max(v, cur);}max = Math.max(max, cur + dup + 1);}return max;}
}

这段代码在天真的我看来是没啥问题的,可就是没办法过,经过长久的排查错误,我写了以下代码加在上面的代码里运行

public static void main(String[] args) {int[][] vals = {{2,3},{3,3},{-5,3}};Point[] points = new Point[3];for (int i=0; i<vals.length; i++){points[i] = new Point(vals[i][0], vals[i][1]);}Solution solution = new Solution();System.out.println(solution.maxPoints(points));
}

它输出的,竟然是2

也就是说,它认为(3-3) / (3-2) 和 (3-3) / (-5-2) 不同? 什么鬼…

经过debug,发现上述结果分别是0.0和-0.0

0.0 难道不等于 -0.0 ?

这时我心里已经一阵卧槽了,不过我还是写了验证代码:

System.out.println(0.0 == -0.0);

结果是True,没问题啊,我凌乱了……

一定是java底层代码错了! 我没错……

又是一阵debug,我找到了这条语句:

map.put(d, map.get(d) == null ? 1 : map.get(d) + 1);

我觉得map.get()很有问题, 它的源代码是这样的:

public V get(Object key) {Node<K,V> e;return (e = getNode(hash(key), key)) == null ? null : e.value;
}

唔,先获得hash()是吧,那我找到了它的hash函数:

static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

再来,这里是要比较h 和key的hashCode是吧,那我们去看hashCode()函数

public native int hashCode();

这是一个本地方法,看不到源码了,唔,,那我就使用它看看吧,测试一下不就好了吗,我写了以下的测试代码:

public static void main(String[] args) {System.out.println(0.0 == -0.0);System.out.println(new Float(0.0).hashCode() == new Float(-0.0).hashCode());}

结果竟然是True和False !!!

这个源头终于找到了, 0.0 和 -0.0 的hashCode值是不同的 !

经过一番修改,我通过了这道题(其实精度也会有问题,应该使用BigDecimal的,不过牛客网要求没那么高。后来我想了想只有把直线方程写成Ax+By+C=0的形式才能完全避免精度问题)

接下来,探讨下实数的hashCode()函数是个啥情况:

2.实数的hashCode()

  • 在程序执行期间,只要equals方法的比较操作用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法必须始终如一地返回同一个整数。

  • 如果两个对象根据equals方法比较是相等的,那么调用两个对象的hashCode方法必须返回相同的整数结果。

  • 如果两个对象根据equals方法比较是不等的,则hashCode方法不一定得返回不同的整数。——《effective java》

那么我们来看看,0.0和-0.0调用equals方法是否相等:

System.out.println(new Float(0.0).equals(0.0f));
System.out.println(new Float(0.0).equals((float) -0.0));

输出是True 和 False

好吧,二者调用equals() 方法不相等,看来是满足了书里说的逻辑的

那我们看看Float底层equals函数咋写的:

public boolean equals(Object obj) {return (obj instanceof Float)&& (floatToIntBits(((Float)obj).value) == floatToIntBits(value));
}

哦,原来是把Float转换成Bits的时候发生了点奇妙的事,于是我找到了一切的源头:

/*** Returns a representation of the specified floating-point value* according to the IEEE 754 floating-point "single format" bit* layout.** <p>Bit 31 (the bit that is selected by the mask* {@code 0x80000000}) represents the sign of the floating-point* number.* Bits 30-23 (the bits that are selected by the mask* {@code 0x7f800000}) represent the exponent.* Bits 22-0 (the bits that are selected by the mask* {@code 0x007fffff}) represent the significand (sometimes called* the mantissa) of the floating-point number.** <p>If the argument is positive infinity, the result is* {@code 0x7f800000}.** <p>If the argument is negative infinity, the result is* {@code 0xff800000}.** <p>If the argument is NaN, the result is {@code 0x7fc00000}.** <p>In all cases, the result is an integer that, when given to the* {@link #intBitsToFloat(int)} method, will produce a floating-point* value the same as the argument to {@code floatToIntBits}* (except all NaN values are collapsed to a single* "canonical" NaN value).** @param   value   a floating-point number.* @return the bits that represent the floating-point number.*/public static int floatToIntBits(float value) {int result = floatToRawIntBits(value);// Check for NaN based on values of bit fields, maximum// exponent and nonzero significand.if (((result & FloatConsts.EXP_BIT_MASK) ==FloatConsts.EXP_BIT_MASK) &&(result & FloatConsts.SIGNIF_BIT_MASK) != 0)result = 0x7fc00000;return result;}

这文档挺长的,也查了其它资料,看了半天终于搞懂了

就是说Java浮点数的语义一般遵循IEEE 754二进制浮点算术标准。IEEE 754标准提供了浮点无穷,负无穷,负零和NaN(非数字)的定义。在使用Java过程中,一些特殊的浮点数通常会让大家很迷惑

当浮点运算产生一个非常接近0的负浮点数时,会产生“-0.0”,而这个浮点数不能正常表示

我们可以输出一波0.0和-0.0的数据:

System.out.println(Float.floatToIntBits((float) 0.0));
System.out.println(Float.floatToIntBits((float) -0.0));
System.out.println(Float.floatToRawIntBits(0.0f));
System.out.println(Float.floatToRawIntBits((float)-0.0));

结果:

0
-2147483648
0
-2147483648

就是说,存储-0.0, 竟然用的是0x80000000

这也是我们熟悉的Integer.MIN_VALUE

3.总结

java中浮点数的表示比较复杂,特别是牵涉到-0.0, NaN, 正负无穷这种,所以不适宜用来作为Map的key, 因为可能跟我们预想的不一致。

往期推荐

开源分布式定时任务框架技术选型

Java 阻塞队列实现原理分析

基于Elasticsearch 实现站内全文搜索

2.3W字,这可能是把Nginx讲得最全面的一篇文章了,建议收藏备用

架构设计分享:项目用得到的多级缓存架构设计方案

案例分享,git项目持续集成实践

SpringBoot + Redis搭建支撑10w 人的秒杀抢单系统!

100多个高频MySQL面试问题及答案

Java8 Stream:20+实际例子,玩转集合的筛选、归约、分组、聚合

扩展Redis的JSON处理模块,非常强调性能的RedisJson!速学

回复干货】获取精选干货视频教程

回复加群】加入疑难问题攻坚交流群

回复mat】获取内存溢出问题分析详细文档教程

回复赚钱】获取用java写一个能赚钱的微信机器人

回复副业】获取程序员副业攻略一份

好文请点赞+分享

同事把实数作为 HashMap 的key,领导心态崩了相关推荐

  1. 同事把实数作为 HashMap 的key,领导发飙了...

    [文章来源]https://sourl.cn/bqPSc4 起 因 让我关注到这一点的起因是一道题:牛客网上的max-points-on-a-line. 题目是这么描述的: Given n point ...

  2. 牛客网:为什么不能将实数作为 HashMap 的 key?

    欢迎关注方志朋的博客,回复"666"获面试宝典 1.起因 让我关注到这一点的起因是一道题:牛客网上的max-points-on-a-line 题目是这么描述的: Given n p ...

  3. java8中谨慎使用实数作为HashMap的key

    欢迎关注方志朋的博客,回复"666"获面试宝典 来源:https://blog.csdn.net/qq_30219017/article/details/79689492 1.起因 ...

  4. 为什么不建议你使用实数作为 HashMap 的key?

    点击关注公众号,实用技术文章及时了解 来源:blog.csdn.net/qq_30219017/article/details/79689492 1.起因 让我关注到这一点的起因是一道题:牛客网上的m ...

  5. 为什么很多人不建议使用实数作为 HashMap 的 Key?

    点击上方"Java精选",选择"设为星标" 别问别人为什么,多问自己凭什么! 下方有惊喜留言必回,有问必答! 每天 08:35 更新文章,每天进步一点点... ...

  6. 蔚来一面:用Object做hashMap的Key时需要做什么?

    作者 | petterp 来源 | https://blog.csdn.net/petterp/article/details/89043847 先来说一下hashcode()和equals方法吧. ...

  7. java hashmap替换key,HashMap 用可变对象作为 key 踩坑

    点击上方☝ Java编程技术乐园,轻松关注! 及时获取有趣有料的技术文章 做一个积极的人编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 作者:Icharle https://icharl ...

  8. hashmap 允许key重复吗_HashTable和HashMap的区别详解

    一.HashMap简介 HashMap是基于哈希表实现的,每一个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长. HashMap是非线程安全的, ...

  9. 5.字符串:aa:zhangsan@163.com!bb:lisi@sina.com!cc:wangwu@126.com 将存入hashMap中 key:aa,bb,cc value:zhang

    5.字符串: "aa:zhangsan@163.com!bb:lisi@sina.com!cc:wangwu@126.com" 将存入hashMap中 key:aa,bb,cc v ...

  10. hashmap 允许key重复吗_Java HashMap key 可以重复吗?

    Java HashMap key 可以重复吗? Java的HashMap中key是不可以重复的,如果重复添加的话,HashMap会自动覆盖key一样的数据,保证一个key对应一个value,使用时只要 ...

最新文章

  1. centos下设置node.js开机启动(并且启动自己的项目js)
  2. iebook 发布到网站 独家秘诀
  3. dlib android
  4. Python 的字符串转int
  5. Codeforces 893 D Credit Card 贪心 思维
  6. 01 Nginx的高并发处理
  7. Android 如何全局获取Context
  8. 计算机网络技术练习,计算机网络技术基础各章节综合练习题及答案
  9. 铁矿石再次冲高回落,豆粕认购大涨,纯碱09-01季节性反套?2022.4.21
  10. 【VB+数控原理与系统】数控原理与系统课程设计刀具半径补偿直线-直线VB模拟软件实现
  11. 基于easyx低配版flappybird
  12. 优秀的程序员应该具备哪些素质
  13. 二.LVGL学习——(lv_obj基础对象)
  14. 从罗永浩进军AR聊开
  15. 5:Echarts数据可视化-多条曲线、多个子图、TreeMap类似盒图、树形图、热力图、词云...
  16. vue中v-for图片src路径错误
  17. 【20220108】【雷达】毫米波雷达(二)—— 毫米波雷达和激光雷达的区别及优缺点比较
  18. 数据分析师只适合男生吗,女生可不可以胜任?
  19. 各种进制转换通用代码
  20. 音乐播放(Toggle PlayerPrefs)

热门文章

  1. 2022-2028年中国SIP行业竞争现状及投资策略研究报告
  2. 《Python语言程序设计基础》嵩天著-第3章程序部分练习题答案
  3. 网宿cdn api 刷新缓存函数
  4. Ubuntu 16.04下DNW的安装及使用
  5. 基于SSM的餐厅点餐系统设计与实现(Java+MySQL)
  6. DisparityCost Volume in Stereo
  7. Contradiction Detection with Contradiction-Specific Word Embedding
  8. pip install报错 There was a problem confirming the ssl certificate…
  9. 从中国的山水画谈谈游戏场景设计该有的状态
  10. 【codeforces 417D】Cunning Gena