2006年8月28日

          今天在數(shù)值計算時碰到一個問題.程序如下:
          ??double a = (3.3-2.4)/0.1;
          ??System.out.println(a);
          你可能認(rèn)為結(jié)果很簡單,不就是9嘛,是事實上,結(jié)果為:8.999999998,為什么呢?我翻閱了一些資料,終于找出了原因.
          為什么浮點數(shù)會丟失精度??
          1. 十進制數(shù)的二進制表示可能不夠精確

            浮點數(shù)或是雙精度浮點數(shù)無法精確表示的情況并不少見。浮點數(shù)值沒辦法用十進制來精確表示的原因要歸咎于CPU表示浮點數(shù)的方法。這樣的話您就可能會犧牲一些精度,有些浮點數(shù)運算也會引入誤差。以上面提到的情況為例,2.4的二進制表示并非就是精確的2.4。反而最為接近的二進制表示是 2.3999999999999999。原因在于浮點數(shù)由兩部分組成:指數(shù)和尾數(shù)。浮點數(shù)的值實際上是由一個特定的數(shù)學(xué)公式計算得到的。您所遇到的精度損失會在任何操作系統(tǒng)和編程環(huán)境中遇到。
            注意: 您可以使用Binary Coded Decimal (BCD)庫來保持精度。BCD數(shù)字編碼方法會把每一個十進制數(shù)字位單獨編碼。?
          2. 類型失配
            您可能混合了浮點數(shù)和雙精度浮點數(shù)類型。請確定您在進行數(shù)學(xué)運算的時候所有的數(shù)據(jù)類型全部相同。
            注意:float類型的變量只有7位的精度,而double類型的變量有15位的精度。

          如何進行浮點數(shù)精度計算?????????????
          ????? Java中的簡單浮點數(shù)類型float和double不能夠進行運算。不光是Java,在其它很多編程語言中也有這樣的問題。在大多數(shù)情況下,計算的結(jié)果是準(zhǔn)確的,但是多試幾次(可以做一個循環(huán))就可以試出類似上面的錯誤。現(xiàn)在終于理解為什么要有BCD碼了。
          這個問題相當(dāng)嚴(yán)重,如果你有9.999999999999元,你的計算機是不會認(rèn)為你可以購買10元的商品的。
          在有的編程語言中提供了專門的貨幣類型來處理這種情況,但是Java沒有。現(xiàn)在讓我們看看如何解決這個問題。
          ?
          四舍五入
          我們的第一個反應(yīng)是做四舍五入。Math類中的round方法不能設(shè)置保留幾位小數(shù),我們只能象這樣(保留兩位):
          public double round(double value){
          ??? return Math.round(value*100)/100.0;
          }
          非常不幸,上面的代碼并不能正常工作,給這個方法傳入4.015它將返回4.01而不是4.02,如我們在上面看到的
          4.015*100=401.49999999999994
          因此如果我們要做到精確的四舍五入,不能利用簡單類型做任何運算
          java.text.DecimalFormat也不能解決這個問題:
          System.out.println(new java.text.DecimalFormat("0.00").format(4.025));
          輸出是4.02
          ?
          BigDecimal
          在《Effective Java》這本書中也提到這個原則,float和double只能用來做科學(xué)計算或者是工程計算,在商業(yè)計算中我們要用java.math.BigDecimal。BigDecimal一共有4個夠造方法,我們不關(guān)心用BigInteger來夠造的那兩個,那么還有兩個,它們是:
          BigDecimal(double val)
          ????????? Translates a double into a BigDecimal.
          BigDecimal(String val)
          ????????? Translates the String repre sentation of a BigDecimal into a BigDecimal.
          上面的API簡要描述相當(dāng)?shù)拿鞔_,而且通常情況下,上面的那一個使用起來要方便一些。我們可能想都不想就用上了,會有什么問題呢?等到出了問題的時候,才發(fā)現(xiàn)上面哪個夠造方法的詳細說明中有這么一段:
          Note: the results of this constructor can be somewhat unpredictable. One might assume that new BigDecimal(.1) is exactly equal to .1, but it is actually equal to .1000000000000000055511151231257827021181583404541015625. This is so because .1 cannot be represented exactly as a double (or, for that matter, as a binary fraction of any finite length). Thus, the long value that is being passed in to the constructor is not exactly equal to .1, appearances nonwithstanding.
          The (String) constructor, on the other hand, is perfectly predictable: new BigDecimal(".1") is exactly equal to .1, as one would expect. Therefore, it is generally recommended that the (String) constructor be used in preference to this one.
          原來我們?nèi)绻枰_計算,非要用String來夠造BigDecimal不可!在《Effective Java》一書中的例子是用String來夠造BigDecimal的,但是書上卻沒有強調(diào)這一點,這也許是一個小小的失誤吧。
          ?
          解決方案
          現(xiàn)在我們已經(jīng)可以解決這個問題了,原則是使用BigDecimal并且一定要用String來夠造。
          但是想像一下吧,如果我們要做一個加法運算,需要先將兩個浮點數(shù)轉(zhuǎn)為String,然后夠造成BigDecimal,在其中一個上調(diào)用add方法,傳入另一個作為參數(shù),然后把運算的結(jié)果(BigDecimal)再轉(zhuǎn)換為浮點數(shù)。你能夠忍受這么煩瑣的過程嗎?下面我們提供一個工具類Arith來簡化操作。它提供以下靜態(tài)方法,包括加減乘除和四舍五入:
          public static double add(double v1,double v2)
          public static double sub(double v1,double v2)
          public static double mul(double v1,double v2)
          public static double div(double v1,double v2)
          public static double div(double v1,double v2,int scale)
          public static double round(double v,int scale)
          附錄
          源文件Arith.java:
          import java.math.BigDecimal;
          /**
          ?* 由于Java的簡單類型不能夠精確的對浮點數(shù)進行運算,這個工具類提供精
          ?* 確的浮點數(shù)運算,包括加減乘除和四舍五入。
          ?*/
          public class Arith{
          ??? //默認(rèn)除法運算精度
          ??? private static final int DEF_DIV_SCALE = 10;
          ??? //這個類不能實例化
          ??? private Arith(){
          ??? }
          ?
          ??? /**
          ???? * 提供精確的加法運算。
          ???? * @param v1 被加數(shù)
          ???? * @param v2 加數(shù)
          ???? * @return 兩個參數(shù)的和
          ???? */
          ??? public static double add(double v1,double v2){
          ??????? BigDecimal b1 = new BigDecimal(Double.toString(v1));
          ??????? BigDecimal b2 = new BigDecimal(Double.toString(v2));
          ??????? return b1.add(b2).doubleValue();
          ??? }
          ??? /**
          ???? * 提供精確的減法運算。
          ???? * @param v1 被減數(shù)
          ???? * @param v2 減數(shù)
          ???? * @return 兩個參數(shù)的差
          ???? */
          ??? public static double sub(double v1,double v2){
          ??????? BigDecimal b1 = new BigDecimal(Double.toString(v1));
          ??????? BigDecimal b2 = new BigDecimal(Double.toString(v2));
          ??????? return b1.subtract(b2).doubleValue();
          ??? }
          ??? /**
          ???? * 提供精確的乘法運算。
          ???? * @param v1 被乘數(shù)
          ???? * @param v2 乘數(shù)
          ???? * @return 兩個參數(shù)的積
          ???? */
          ??? public static double mul(double v1,double v2){
          ??????? BigDecimal b1 = new BigDecimal(Double.toString(v1));
          ??????? BigDecimal b2 = new BigDecimal(Double.toString(v2));
          ??????? return b1.multiply(b2).doubleValue();
          ??? }
          ?
          ??? /**
          ???? * 提供(相對)精確的除法運算,當(dāng)發(fā)生除不盡的情況時,精確到
          ???? * 小數(shù)點以后10位,以后的數(shù)字四舍五入。
          ???? * @param v1 被除數(shù)
          ???? * @param v2 除數(shù)
          ???? * @return 兩個參數(shù)的商
          ???? */
          ??? public static double div(double v1,double v2){
          ??????? return div(v1,v2,DEF_DIV_SCALE);
          ??? }
          ?
          ??? /**
          ???? * 提供(相對)精確的除法運算。當(dāng)發(fā)生除不盡的情況時,由scale參數(shù)指
          ???? * 定精度,以后的數(shù)字四舍五入。
          ???? * @param v1 被除數(shù)
          ???? * @param v2 除數(shù)
          ???? * @param scale 表示表示需要精確到小數(shù)點以后幾位。
          ???? * @return 兩個參數(shù)的商
          ???? */
          ??? public static double div(double v1,double v2,int scale){
          ??????? if(scale<0){
          ??????????? throw new IllegalArgumentException(
          ??????????????? "The scale must be a positive integer or zero");
          ??????? }
          ??????? BigDecimal b1 = new BigDecimal(Double.toString(v1));
          ??????? BigDecimal b2 = new BigDecimal(Double.toString(v2));
          ??????? return b1.divide(b2,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
          ??? }
          ?
          ??? /**
          ???? * 提供精確的小數(shù)位四舍五入處理。
          ???? * @param v 需要四舍五入的數(shù)字
          ???? * @param scale 小數(shù)點后保留幾位
          ???? * @return 四舍五入后的結(jié)果
          ???? */
          ??? public static double round(double v,int scale){
          ??????? if(scale<0){
          ??????????? throw new IllegalArgumentException(
          ??????????????? "The scale must be a positive integer or zero");
          ??????? }
          ??????? BigDecimal b = new BigDecimal(Double.toString(v));
          ??????? BigDecimal one = new BigDecimal("1");
          ??????? return b.divide(one,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
          ??? }
          };

          posted @ 2006-08-31 11:16 曹青松 閱讀(2945) | 評論 (4)編輯 收藏

          ??? 可以把普通的 Java 程序做成真正的 exe, 也就是單一個 exe 就可以在沒有安裝 JVM 的機器上運行。這樣的工具常見的有 JET gcj. 前者是收費的,而且做出來的 exe 還是需要一堆 dll 。推薦使用 gcj. 他有 windows Linux 版,直接下載 zip 包,不需要安裝,里面有不少例子,一些 build 的批處理文件。從原理來說 gcj 自己實現(xiàn)了 JVM 規(guī)范,也就是你編寫一個 HelloWorld.java, 其中的 main 方法為 System.out.println("foo");
          當(dāng)使用 gcj 把它做成 exe( 大約 2M ) ,運行這個 exe 時,會啟動里面的一個小型 jvm, 在這上面跑 HelloWorld

          其實,把 Java 做成純 exe 實在是吃力不討好,有很多限制,文件又大。

          我比較傾向另幾種做法:

          . 使用 InstallAnywhere 等工具,制作一個 exe 的安裝包
          用戶可以選擇使用他機器上的 JRE 或是這個安裝包內(nèi)的 JRE 來運行程序
          這是很常見的一種做法,如 JBuilder 就是這么做的。
          這樣的好處是不要求對方機器上裝有 JRE ,而且你原來的程序不需要任何改動。
          InstallAnywhere
          中一個壓縮的 JRE 大概是 8M

          . 制作成可執(zhí)行的 jar, 也就是在 META-INF MANIFEST 文件制定 Main-Class
          可以通過命令行 java -jar jarfile.jar 來執(zhí)行, windows 默認(rèn)的把 *.jar 使用 javaw -jar 打開,所以有些機器上可以直接雙擊 jar 運行。

          . 制作偽 exe, 其實和上一種做法是一樣的,只不過做成 exe, 調(diào)用系統(tǒng)的 java.exe 來運行它,這樣的工具有 nativeJ,exe4j


          其實 Java 不像 VB,Delphi 只是一個語言,而是一個平臺。
          jar
          是最常用的部署單元,做成 exe 沒什么意思。
          一、 exe4j
          ???
          說明: exe4j 可以將 Jar 文件制作成 exe 文件,但需 jre 支持,也可將 Jar 文件放在外面。
          ???
          軟件性質(zhì):共享軟件
          ???
          下載地址: http://www.ej-technologies.com/products/exe4j/overview.html
          二、 JBuilder
          ???
          說明:新版本的 JBuilder 可以直接把工程制作成各系統(tǒng)的可執(zhí)行文件,包括 Windows 系統(tǒng)。
          ???
          軟件性質(zhì):商業(yè)軟件
          ???
          下載地址:略。我是從 eMule 下載的。
          三、 NativeJ
          ???
          說明:與 exe4j 功能類似。
          ???
          軟件性質(zhì):共享軟件
          ???
          下載地址: http://www.dobysoft.com/products/nativej/download.html
          四、 Excelsior JET
          ???
          說明:可以直接將 Java 類文件制作成 exe 文件,除 AWT Swing 及第三方圖形接口外可不需 jre 支持( Java5.0 不行)。
          ???
          軟件性質(zhì):共享軟件
          ???
          下載地址: http://excelsior-usa.com/home.html
          五、 jshrink
          ???
          說明:可將 Jar 文件打包進 exe 文件。同時具有混淆功能(這才是它的主要功能)。
          ???
          軟件性質(zhì):共享軟件
          ???
          下載地址: http://www.e-t.com/jshrink.html
          六、 InstallAnywhere
          ???
          說明:打包工具,對 Java 打包最好用。可打包成各操作系統(tǒng)運行包。包括 Windows 系統(tǒng)。
          ???
          軟件性質(zhì):商業(yè)軟件。
          ???
          下載地址: http://www.zerog.com/
          七、 InstallShieldX
          ???
          說明:與 InstallAnywhere 類似,但比 InstallAnywhere 功能強大。相對的,比較復(fù)雜,不易上手,我現(xiàn)在還沒學(xué)會。
          ???
          軟件性質(zhì):商業(yè)軟件。
          ???
          下載地址: http://www.installshield.com/

          posted @ 2006-08-28 20:37 曹青松 閱讀(1102) | 評論 (0)編輯 收藏


          posts - 4, comments - 8, trackbacks - 0, articles - 0

          Copyright © 曹青松

          主站蜘蛛池模板: 沁源县| 休宁县| 冕宁县| 年辖:市辖区| 镇康县| 乳源| 邯郸市| 余庆县| 特克斯县| 大洼县| 连云港市| 宣恩县| 镶黄旗| 金华市| 大悟县| 上饶县| 溆浦县| 崇仁县| 鸡东县| 许昌市| 昌邑市| 怀柔区| 西平县| 疏附县| 闽侯县| 瓦房店市| 秦安县| 陇南市| 锡林郭勒盟| 泽库县| 定安县| 吉木萨尔县| 绵竹市| 吉安县| 共和县| 稷山县| 屯昌县| 新竹市| 永济市| 金川县| 连州市|