筆記

          way

          Java Puzzlers(三-二 循環(huán))

          31
          while (i != 0)
              i >>>= 1; //無符號右移,不管正負(fù)左邊都是補(bǔ)0
          為了表達(dá)式合法,這里的i必須是整型(byte, char, short, int, or long)。謎題的關(guān)鍵在于>>>= 是一個復(fù)合賦值操作符,不幸的是復(fù)合賦值操作符會默默的做narrowing primitive conversions,即從一個數(shù)據(jù)類型轉(zhuǎn)換為一個更小的數(shù)據(jù)類型。Narrowing primitive conversions can lose information about the magnitude or precision of numeric values。為了使問題更具體,假設(shè)這樣定義:
          short i = -1; 因?yàn)槌跏贾礽 ((short)0xffff) 非零,循環(huán)執(zhí)行。第一步位移會把i提升為int。short, byte, or char類型的操作數(shù)都會做這樣的操作。這是widening primitive conversion,沒有信息丟失。這種提升有符號擴(kuò)展,因此結(jié)果是int值0xffffffff。無符號右移一位產(chǎn)生int值0x7fffffff。為了把int值存回short變量,Java執(zhí)行了可怕的narrowing primitive conversion,即簡單去掉高十六位。這樣又變回了(short)0xffff。如果定義類似short or byte型的負(fù)數(shù),都會得到類似結(jié)果。你如果定義的是char話,則不會無限循環(huán),因?yàn)閏har值非負(fù),位移之前的寬擴(kuò)展不會做符號擴(kuò)展。

          總結(jié):不要在short, byte, or char變量上使用復(fù)合賦值操作符。這種表達(dá)式進(jìn)行混合類型計(jì)算,非常容易混淆。更糟糕的是隱含的窄映射會丟掉信息。

          32
          while (i <= j && j <= i && i != j) {}
          i <= j and j <= i, surely i must equal j?對于實(shí)數(shù)來說是這樣的。他非常重要,有個名稱:The ≤ relation on the real numbers is said to be antisymmetric。Java's <= operator used to be antisymmetric before release 5.0, but no longer.Java 5之前數(shù)據(jù)比較符號(<, <=, >, and >=) 需要兩邊的操作數(shù)必須為基礎(chǔ)類型(byte, char, short, int, long, float, or double).在Java 5變?yōu)閮蛇叢僮鲾?shù)為凡是可轉(zhuǎn)變?yōu)榛A(chǔ)類型的類型。java 5引入autoboxing and auto-unboxing 。The boxed numeric types are Byte, Character, Short, Integer, Long, Float, and Double。具體點(diǎn),讓上面進(jìn)入無限循環(huán):
          Integer i = new Integer(0);
          Integer j = new Integer(0);
          (i <= j and j <= i) perform unboxing conversions on i and j and compare the resulting int values numerically。i和j表示0,所以表達(dá)式為true。i != j比較的是對象引用,也為true。很奇怪規(guī)范沒有把等號改為比較值。原因很簡單:兼容性。當(dāng)一種語言廣泛應(yīng)用的時候,不能破壞已存在的規(guī)范來改變程序的行為。System.out.println(new Integer(0) == new Integer(0));總是輸出false,所以必須保留。當(dāng)一個是boxed numeric 類型,另一個是基本類型的時候可以值比較。因?yàn)閖ava 5之前這是非法的,具體點(diǎn):
          System.out.println(new Integer(0) == 0); //之前的版本非法,Java 5輸出True
          總結(jié):當(dāng)兩邊的操作數(shù)是boxed numeric類型的時候,數(shù)字比較符和等于符號是根本不同的:數(shù)字比較符是值比較,等號比較的是對象引用

          33
          while (i != 0 && i == -i) {}
          有負(fù)號表示i一定是數(shù)字,NaN不行,因?yàn)樗坏扔谌魏螖?shù)。事實(shí)上,沒有實(shí)數(shù)可以出現(xiàn)這種情況。但Java的數(shù)字類型并沒有完美表達(dá)實(shí)數(shù)。浮點(diǎn)數(shù)由一個符號位,一個有效數(shù)字(尾數(shù)),一個指數(shù)構(gòu)成。浮點(diǎn)數(shù)只有0才會和自己的負(fù)數(shù)相等,所以i肯定是整數(shù)。有符號整數(shù)用的是二進(jìn)制補(bǔ)碼計(jì)算:取反加一。補(bǔ)碼的一大優(yōu)勢是用一個唯一的數(shù)來表示0。然而有一個對應(yīng)的缺點(diǎn):本來可表達(dá)偶數(shù)個值,現(xiàn)在用一個表達(dá)了0,剩下奇數(shù)個來表示正負(fù)數(shù),意味著正數(shù)和負(fù)數(shù)的數(shù)量不一樣。比如int值,他的Integer.MIN_VALUE-231)。十六進(jìn)制表達(dá)0x8000000。通過補(bǔ)碼計(jì)算可知他的負(fù)數(shù)仍然不變。對他取負(fù)數(shù)是溢出了的,不過Java在整數(shù)計(jì)算中忽略了溢出。
          總結(jié):Java使用二進(jìn)制補(bǔ)碼,是不對稱的。有符號整數(shù)(int, long, byte, and short) 負(fù)數(shù)值比整數(shù)值多一個。

          34
                  final int START = 2000000000;
                  int count = 0;
                  for (float f = START; f < START + 50; f++)
                      count++;
                  System.out.println(count);
          注意循環(huán)變量是float。回憶謎題28 ,明顯f++沒有任何作用。f的初始化值接近Integer.MAX_VALUE,因此需要31位來準(zhǔn)確表達(dá),但是float類型只提供了24位精度。增加這樣大的一個float值不會改變值。看起來會死循環(huán)?運(yùn)行程序會發(fā)現(xiàn),輸出0。循環(huán)中f和(float)(START + 50)做比較。但int和float比較的時候,自動把int先提示為float。不幸的是三個會引起精度丟失的寬基本類型轉(zhuǎn)換的其中之一(另外兩個是long到float,long到double)。f的初始值巨大,加50再轉(zhuǎn)換為float和直接把f轉(zhuǎn)換為float是一樣的效果,即(float)2000000000 == 2000000050,所以f < START + 50 失敗。只要把float改為int即可修正。
               沒有計(jì)算器,你怎么知道 2,000,000,050 和float表示2,000,000,000一樣?……
          The moral of this puzzle is simple: Do not use floating-point loop indices, because it can lead to unpredictable behavior. If you need a floating-point value in the body of a loop, take the int or long loop index and convert it to a float or double. You may lose precision when converting an int or long to a float or a long to a double, but at least it will not affect the loop itself. When you use floating-point, use double rather than float unless you are certain that float provides enough precision and you have a compelling performance need to use float. The times when it's appropriate to use float rather than double are few and far between。

          35
          下面程序模擬一個簡單的時鐘
          int minutes = 0;
          for (int ms = 0; ms < 60*60*1000; ms++)
            if (ms %  60*1000 == 0)
               minutes++;
           
          System.out.println(minutes);
          結(jié)果是60000,問題在于布爾表達(dá)式ms % 60*1000 == 0,最簡單的修改方法是:if (ms % (60 * 1000) == 0)
          更好的方法是用合適命名的常量代替魔力數(shù)字

          private static final int MS_PER_HOUR = 60 * 60 * 1000;
          private static final int MS_PER_MINUTE = 60 * 1000;
          public static void main(String[] args) {
             int minutes = 0;
             for (int ms = 0; ms < MS_PER_HOUR; ms++)
               if (ms % MS_PER_MINUTE == 0)
                  minutes++;
              System.out.println(minutes);
          }
          絕不要用空格來分組;使用括號來決定優(yōu)先級

          posted on 2010-11-15 19:59 yuxh 閱讀(281) 評論(0)  編輯  收藏 所屬分類: jdk

          導(dǎo)航

          <2010年11月>
          31123456
          78910111213
          14151617181920
          21222324252627
          2829301234
          567891011

          統(tǒng)計(jì)

          常用鏈接

          留言簿

          隨筆分類

          隨筆檔案

          收藏夾

          博客

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 济宁市| 大化| 四平市| 九龙县| 新竹市| 黔江区| 长阳| 桃源县| 措勤县| 万载县| 双江| 临城县| 泰兴市| 绥阳县| 垫江县| 永顺县| 韶山市| 雅安市| 安乡县| 句容市| SHOW| 拜城县| 龙山县| 黄骅市| 什邡市| 蒙阴县| 宝坻区| 甘孜县| 霍邱县| 碌曲县| 封丘县| 克拉玛依市| 教育| 长汀县| 宁南县| 镇安县| 呼伦贝尔市| 高要市| 洛阳市| 兴仁县| 丹寨县|