同事把实数作为 HashMap 的key,领导发飙了...
共 12257字,需浏览 25分钟
·
2021-12-12 23:01
上一篇:王小波的计算机水平有多好?
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
这时我心里已经一阵卧槽了,不过我还是写了验证代码:
System.out.println(0.0 == -0.0);
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();
这是一个本地方法,看不到源码了,唔,,那我就使用它看看吧,测试一下不就好了吗,我写了以下的测试代码:搜索公众号互联网架构师复“2T”,送你一份惊喜礼包。
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 !!!
2.实数的hashCode()
在程序执行期间,只要equals方法的比较操作用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法必须始终如一地返回同一个整数。 如果两个对象根据equals方法比较是相等的,那么调用两个对象的hashCode方法必须返回相同的整数结果。 如果两个对象根据equals方法比较是不等的,则hashCode方法不一定得返回不同的整数。——《effective java》
System.out.println(new Float(0.0).equals(0.0f));
System.out.println(new Float(0.0).equals((float) -0.0));
好吧,二者调用equals()
方法不相等,看来是满足了书里说的逻辑的
那我们看看Float底层equals函数咋写的:
public boolean equals(Object obj) {
return (obj instanceof Float)
&& (floatToIntBits(((Float)obj).value) ==
floatToIntBits(value));
}
哦,原来是把Float转换成Bits的时候发生了点奇妙的事,于是我找到了一切的源头:搜索公众号互联网架构师复“2T”,送你一份惊喜礼包。
/**
* 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;
}
这文档挺长的,也查了其它资料,看了半天终于搞懂了
我们可以输出一波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, 因为可能跟我们预想的不一致
正文结束
1.心态崩了!税前2万4,到手1万4,年终奖扣税方式1月1日起施行~