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

news/2024/7/8 1:44:03

欢迎关注方志朋的博客,回复”666“获面试宝典


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, 因为可能跟我们预想的不一致

来源:blog.csdn.net/qq_30219017/article/details/79689492

热门内容:
  • Docker 大势已去,Podman 即将崛起

  • 我同事说我写代码像写诗

  • Maven官宣:干掉Maven和Gradle!推出更强更快更牛逼的新一代构建工具,炸裂!

  • 面试官问:生成订单30分钟未支付,则自动取消,该怎么实现?

  • 使用雪花id或uuid作为Mysql主键,被老板怼了一顿!

537cb7c8350e795ac0ae054eac020d5e.png

最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。
获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

明天见(。・ω・。)ノ♡


http://lihuaxi.xjx100.cn/news/257218.html

相关文章

「摸鱼」神器来了,Python 实现人脸监测制作神器

作者 | 李秋键 出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09; 最近都在讨论工作摸鱼&#xff0c;网易云音乐也出了合理摸鱼时间表&#xff0c;今天给大家推荐如何用python实现摸鱼~码住呦&#xff01; 引言&#xff1a;脸部表情是人类情绪的最直接外部表现之一和进…

冲上热搜!清华95后博士,搞科研演绎浪漫爱情故事获赞千万

点击上方“视学算法”&#xff0c;选择加"星标"或“置顶”重磅干货&#xff0c;第一时间送达本文系募格课堂整合&#xff0c;参考来源&#xff1a;清华大学、中国新闻周刊、辽沈晚报、微博从清华本科毕业到博士后&#xff0c;他可以是拿过奥赛金牌、做起实验训练有素…

【C#串口编程计划】通信协议解析 -- byte[]与常用类型的转换

刚刚完成一个串口通讯的系统。目前在把串口通信的代码整合到团队的类库中&#xff08;把串口通信与网口Soket通讯整合起来&#xff0c;后面只需要配置参数&#xff0c;就可实现网络与串口通讯的转换&#xff09;&#xff0c;故C#串口编程计划的最后一篇图文“C#通讯类库框架”还…

Applet相关知识

1、Applet定义 Applet是采用Java编程语言编写的小应用程序&#xff0c;该程序可以包含在 HTML&#xff08;标准通用标记语言的一个应用&#xff09;页中&#xff0c;与在页中包含图像的方式大致相同。含有Applet的网页的HTML文件代码中部带有<applet> 和</applet>这…

伤感的故事

人的生活本来就是哪么的平淡无味。如果是一对老人&#xff0c;我会认为这样的平淡是幸福的&#xff0c;因为他们两个人从年轻一起走到白发苍苍。如果是年轻人&#xff0c;哪么是可悲的。我记得有一个人给我讲了一个故事&#xff0c;故事的内容就是&#xff1a;“有一天一个老和…

ecshop修改注册、增加手机

1.去掉“用户名”注册 a.去掉提交 user_passport.dwt页面去掉 <input name"username" type"text" size"30" id"username" οnblur"is_registered(this.value);" class"input_login" />提交 b.去掉js表单验证…

面经:什么是Transformer位置编码?

↑↑↑关注后"星标"Datawhale每日干货 & 每月组队学习&#xff0c;不错过Datawhale干货 作者&#xff1a;陈安东&#xff0c;中央民族大学&#xff0c;Datawhale成员过去的几年里&#xff0c;Transformer大放异彩&#xff0c;在各个领域疯狂上分。它究竟是做什么…

Twitter 禁止未经用户同意分享照片和视频

整理 | 禾木木 出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09; Twitter 宣布将扩大私人信息政策&#xff0c;包括在未经个人许可的情况下共享的私人媒体&#xff0c;例如照片和视频&#xff0c;因为该社交媒体平台旨在改善用户隐私和安全。 “分享个人媒体&#xff…