筆記

          way

          Java Puzzlers(三-二 循環)

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

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

          32
          while (i <= j && j <= i && i != j) {}
          i <= j and j <= i, surely i must equal j?對于實數來說是這樣的。他非常重要,有個名稱: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之前數據比較符號(<, <=, >, and >=) 需要兩邊的操作數必須為基礎類型(byte, char, short, int, long, float, or double).在Java 5變為兩邊操作數為凡是可轉變為基礎類型的類型。java 5引入autoboxing and auto-unboxing 。The boxed numeric types are Byte, Character, Short, Integer, Long, Float, and Double。具體點,讓上面進入無限循環:
          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,所以表達式為true。i != j比較的是對象引用,也為true。很奇怪規范沒有把等號改為比較值。原因很簡單:兼容性。當一種語言廣泛應用的時候,不能破壞已存在的規范來改變程序的行為。System.out.println(new Integer(0) == new Integer(0));總是輸出false,所以必須保留。當一個是boxed numeric 類型,另一個是基本類型的時候可以值比較。因為java 5之前這是非法的,具體點:
          System.out.println(new Integer(0) == 0); //之前的版本非法,Java 5輸出True
          總結:當兩邊的操作數是boxed numeric類型的時候,數字比較符和等于符號是根本不同的:數字比較符是值比較,等號比較的是對象引用

          33
          while (i != 0 && i == -i) {}
          有負號表示i一定是數字,NaN不行,因為他不等于任何數。事實上,沒有實數可以出現這種情況。但Java的數字類型并沒有完美表達實數。浮點數由一個符號位,一個有效數字(尾數),一個指數構成。浮點數只有0才會和自己的負數相等,所以i肯定是整數。有符號整數用的是二進制補碼計算:取反加一。補碼的一大優勢是用一個唯一的數來表示0。然而有一個對應的缺點:本來可表達偶數個值,現在用一個表達了0,剩下奇數個來表示正負數,意味著正數和負數的數量不一樣。比如int值,他的Integer.MIN_VALUE-231)。十六進制表達0x8000000。通過補碼計算可知他的負數仍然不變。對他取負數是溢出了的,不過Java在整數計算中忽略了溢出。
          總結:Java使用二進制補碼,是不對稱的。有符號整數(int, long, byte, and short) 負數值比整數值多一個。

          34
                  final int START = 2000000000;
                  int count = 0;
                  for (float f = START; f < START + 50; f++)
                      count++;
                  System.out.println(count);
          注意循環變量是float。回憶謎題28 ,明顯f++沒有任何作用。f的初始化值接近Integer.MAX_VALUE,因此需要31位來準確表達,但是float類型只提供了24位精度。增加這樣大的一個float值不會改變值。看起來會死循環?運行程序會發現,輸出0。循環中f和(float)(START + 50)做比較。但int和float比較的時候,自動把int先提示為float。不幸的是三個會引起精度丟失的寬基本類型轉換的其中之一(另外兩個是long到float,long到double)。f的初始值巨大,加50再轉換為float和直接把f轉換為float是一樣的效果,即(float)2000000000 == 2000000050,所以f < START + 50 失敗。只要把float改為int即可修正。
               沒有計算器,你怎么知道 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);
          結果是60000,問題在于布爾表達式ms % 60*1000 == 0,最簡單的修改方法是:if (ms % (60 * 1000) == 0)
          更好的方法是用合適命名的常量代替魔力數字

          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);
          }
          絕不要用空格來分組;使用括號來決定優先級

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

          導航

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

          統計

          常用鏈接

          留言簿

          隨筆分類

          隨筆檔案

          收藏夾

          博客

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 六盘水市| 永平县| 靖边县| 通海县| 孟村| 田林县| 西和县| 民丰县| 砀山县| 巨鹿县| 定陶县| 自治县| 永登县| 八宿县| 楚雄市| 凤翔县| 元阳县| 旺苍县| 即墨市| 河曲县| 黄浦区| 丰县| 拉萨市| 佛冈县| 南丰县| 方山县| 永胜县| 额尔古纳市| 辽宁省| 琼海市| 南投县| 黎川县| 古浪县| 阜城县| 信阳市| 龙门县| 湖北省| 平顶山市| 施秉县| 镇宁| 礼泉县|