1. 為什么不能將實數(shù)作為 HashMap 的 key?

        共 533字,需瀏覽 2分鐘

         ·

        2022-02-09 18:38



        1.起因

        讓我關注到這一點的起因是一道題:??途W(wǎng)上的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坐標,找到過這些點最多的直線,輸出這條線上的點數(shù)量。

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

        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?1;?i++)?{????????????Map?map?=?new?HashMap<>(16);????????????//?記錄垂直點數(shù);?當前和Points[i]在一條線上的最大點數(shù);?和Points[i]垂直的點數(shù)????????????int?ver?=?0,?cur,?dup?=?0;????????????for?(int?j?=?i?+?1;?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;????}}
        這段代碼在天真的我看來是沒啥問題的,可就是沒辦法過,經(jīng)過長久的排查錯誤,我寫了以下代碼加在上面的代碼里運行

        public?static?void?main(String[]?args)?{????int[][]?vals?=?{{2,3},{3,3},{-5,3}};????Point[]?points?=?new?Point[3];????for?(int?i=0;?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) 不同? 什么鬼…

        經(jīng)過debug,發(fā)現(xiàn)上述結果分別是0.0和-0.0

        0.0 難道不等于 -0.0 ?

        這時我心里已經(jīng)一陣臥槽了,不過我還是寫了驗證代碼:

        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?e;????return?(e?=?getNode(hash(key),?key))?==?null???null?:?e.value;}
        唔,先獲得hash()是吧,那我找到了它的hash函數(shù):

        static?final?int?hash(Object?key)?{????int?h;????return?(key?==?null)???0?:?(h?=?key.hashCode())?^?(h?>>>?16);}
        再來,這里是要比較h 和key的hashCode是吧,那我們?nèi)タ磆ashCode()函數(shù)

        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值是不同的 !

        經(jīng)過一番修改,我通過了這道題(其實精度也會有問題,應該使用BigDecimal的,不過牛客網(wǎng)要求沒那么高。后來我想了想只有把直線方程寫成Ax+By+C=0的形式才能完全避免精度問題)

        接下來,探討下實數(shù)的hashCode()函數(shù)是個啥情況:

        2.實數(shù)的hashCode()

        • 在程序執(zhí)行期間,只要equals方法的比較操作用到的信息沒有被修改,那么對這同一個對象調(diào)用多次,hashCode方法必須始終如一地返回同一個整數(shù)。

        • 如果兩個對象根據(jù)equals方法比較是相等的,那么調(diào)用兩個對象的hashCode方法必須返回相同的整數(shù)結果。

        • 如果兩個對象根據(jù)equals方法比較是不等的,則hashCode方法不一定得返回不同的整數(shù)。——《effective java》


        那么我們來看看,0.0和-0.0調(diào)用equals方法是否相等:

        System.out.println(new?Float(0.0).equals(0.0f));System.out.println(new Float(0.0).equals((float) -0.0));
        輸出是True 和 False

        好吧,二者調(diào)用equals() 方法不相等,看來是滿足了書里說的邏輯的

        那我們看看Float底層equals函數(shù)咋寫的:

        public?boolean?equals(Object?obj)?{????return?(obj?instanceof?Float)???????????&&?(floatToIntBits(((Float)obj).value)?==????????????????????floatToIntBits(value));}
        哦,原來是把Float轉換成Bits的時候發(fā)生了點奇妙的事,于是我找到了一切的源頭:

        /*** Returns a representation of the specified floating-point value* according to the IEEE 754 floating-point "single format" bit* layout.**

        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.**

        If the argument is positive infinity, the result is* {@code 0x7f800000}.**

        If the argument is negative infinity, the result is* {@code 0xff800000}.**

        If the argument is NaN, the result is {@code 0x7fc00000}.**

        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浮點數(shù)的語義一般遵循IEEE 754二進制浮點算術標準。IEEE 754標準提供了浮點無窮,負無窮,負零和NaN(非數(shù)字)的定義。在使用Java過程中,一些特殊的浮點數(shù)通常會讓大家很迷惑

        當浮點運算產(chǎn)生一個非常接近0的負浮點數(shù)時,會產(chǎn)生“-0.0”,而這個浮點數(shù)不能正常表示

        我們可以輸出一波0.0和-0.0的數(shù)據(jù):

        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-21474836480-2147483648
        就是說,存儲-0.0, 竟然用的是0x80000000

        這也是我們熟悉的Integer.MIN_VALUE

        3.總結

        java中浮點數(shù)的表示比較復雜,特別是牽涉到-0.0, NaN, 正負無窮這種,所以不適宜用來作為Map的key, 因為可能跟我們預想的不一致
        來源:blog.csdn.net/qq_30219017/article/details/79689492

        (完)

        PS:如果覺得我的分享不錯,歡迎大家隨手點贊、在看。

        ?關注公眾號:Java后端編程,回復下面關鍵字?


        要Java學習完整路線,回復??路線?

        缺Java入門視頻,回復?視頻?

        要Java面試經(jīng)驗,回復??面試?

        缺Java項目,回復:?項目?

        進Java粉絲群:?加群?


        PS:如果覺得我的分享不錯,歡迎大家隨手點贊、在看。

        (完)




        加我"微信"?獲取一份 最新Java面試題資料

        請備注:666不然不通過~


        最近好文


        1、再見了,收費的XShell,我改用國產(chǎn)良心工具!

        2、給IDEA換個酷炫的主題,真的太好看了!

        3、SpringBoot快速開發(fā)利器:Spring Boot CLI

        4、基于SpringBoot 的CMS系統(tǒng),拿去開發(fā)企業(yè)官網(wǎng)

        5、本機號碼一鍵登錄原理與應用



        最近面試BAT,整理一份面試資料Java面試BAT通關手冊,覆蓋了Java核心技術、JVM、Java并發(fā)、SSM、微服務、數(shù)據(jù)庫、數(shù)據(jù)結構等等。
        獲取方式:關注公眾號并回復?java?領取,更多內(nèi)容陸續(xù)奉上。
        明天見(??ω??)??
        瀏覽 61
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 太粗太深了太紧太爽了国产 | 天天摸天天日天天操天天摸天天爱 | 免费黄片网站 | 穴穴自拍九九综合 | freeavhd |