官术网_书友最值得收藏!

4.4.2 利用牛頓迭代法求大數開方

自從把一段功能獨立的代碼剝離為可以復用的方法,計算機語言的編碼效率頓時得到了飛躍的提升,因為許多數學函數都能書寫為公共方法給外部調用。除了前面介紹的階乘函數之外,開方函數也能編寫為公共的開根號方法,盡管系統自帶的Math庫已經提供了sqrt這么一個開平方函數,但JDK的源碼并非給出該方法的Java實現代碼,故而我們有必要了解一下如何通過Java代碼實現開根號方法。

根據之前介紹的牛頓迭代法,能夠得出求平方根的迭代式子為。由于多次迭代的過程可以借助循環語句完成,因此可通過while關鍵字編寫求方根的方法定義,具體的方法代碼示例如下(完整代碼見本章源碼的src\com\method\BigNewton.java):

    // 計算雙精度數的平方根
    private static double sqrtByDouble(double number) {
        double Xm=number;  // 每次迭代后的數值
        while (true) {
            double lastXm=Xm;  // 上次迭代的平方根
            Xm=(Xm + number/Xm) / 2;  // 本次迭代后的平方根
            // 迭代前后的兩個平方根相等,表示已經達到變量精度,再計算下去就沒意義了
            if (Xm >= lastXm) {
                break;
            }
        }
        return Xm;
    }

上述代碼之所以添加了if判斷,是因為要校驗迭代前后的xmxm+1是否相等,如果二者等值,便說明本次迭代已經達到了雙精度類型的精度范圍,此時繼續迭代也無法獲得更好的方根精度,當然要退出循環以避免無謂的運算操作。注意到if判斷語句位于while循環的末尾,且滿足if條件時就退出循環,因而這段while語句可以改寫為do/while語句,改寫后的方法代碼如下:

    // 計算雙精度數的平方根(使用do/while循環)
    private static double sqrtByDoubleWithDo(double number) {
        double Xm=number;  // 每次迭代后的數值
        double lastXm=Xm;  // 上次迭代的平方根
        do {
            lastXm=Xm;  // 保存上次迭代的平方根
            Xm=(Xm + number/Xm) / 2;  // 本次迭代后的平方根
        } while (Xm < lastXm);  // 只有迭代前后的兩個平方根不等的時候,才要繼續執行循環
        return Xm;
    }

無論是采用while循環,還是采用do/while循環,兩個方法計算出來的方根結果是一樣的。以下是外部調用其中一個開根號方法的代碼例子,準備計算整數2的平方根:

    // 測試雙精度數的開方運算
    private static void testSqrtByDouble() {
        double number=2;  // 需要求方根的數字
        double root=sqrtByDouble(number);  // 計算雙精度數的平方根
        System.out.println("雙精度數開方運算,原始數字=" + number + ", 它的平方根=" + root);
    }

運行上面的開方代碼,觀察到下面的日志信息,可見通過自定義的方法也能正確求得數字的 方根。

雙精度數開方運算,原始數字=2.0, 它的平方根=1.414213562373095

然而受到存儲空間限制,雙精度類型只能表達到小數點后面15位,再往后的小數位就無能為力了。雖然這點誤差在日常生活中算不了什么,但在精細的金融領域和精密的工程領域是不可接受的,為了計算出足夠精確的方根數值,需要采取大小數類型進行開方運算。由于大小數的除法運算不允許出現無限小數,必須在調用相除方法divide時指定保留位數,因此還要引入精度工具MathContext來設定精度參數。同樣利用牛頓迭代法編寫大小數的開方運算,詳細的實現代碼如下(完整代碼見本章源碼的src\com\method\BigNewton.java):

    // 計算大小數的平方根
    private static BigDecimal sqrtByBigDecimal(BigDecimal number, int precision){
        BigDecimal two=BigDecimal.valueOf(2);
        // 指定運算精度,保留若干位數,最后一位四舍五入取整
        MathContext mc=new MathContext(precision, RoundingMode.HALF_UP);
        if (number.compareTo(BigDecimal.ZERO) <= 0) {  // 0和負數不允許開方
            return BigDecimal.valueOf(0);
        } else {
            BigDecimal X=number;  // 上次迭代的平方根
            // 下面利用牛頓迭代法計算某個大小數的平方根
            while (true) {
                // 簡化之后求平方根的迭代式子:Xm=(Xm + number/Xm) / 2
                BigDecimal Xm=(X.add(number.divide(X, mc))).divide(two, mc);
                // 如果運算前后的結果相等,就跳出循環。因為已經達到運算精度,再計算下去也無用
                if (X.equals(Xm)) {
                    break;
                }
                X=Xm;  // 保留本次迭代后的方根
            }
            return X;
        }
    }

仍然以2為底數計算它的方根,調用大小數的求方根方法時,假定保留小數點后面100位,則外部的調用代碼可書寫為下面這樣:

    // 測試大小數的開方運算
    private static void testSqrtByBigDecimal() {
        BigDecimal number=BigDecimal.valueOf(2);  // 需要求方根的數字
        // 求得的平方根保留小數點后面100位
        BigDecimal root=sqrtByBigDecimal(number, 100);  // 計算大小數的平方根
        System.out.println("大小數開方運算,原始數字=" + number + ", 它的平方根=" + root);
    }

運行上面的大小數開方代碼,觀察到下面的日志信息:

大小數開方運算,原始數字=2,它的平方根=1.41421356237309504880168872420969807856967187537694807 3176679737990732478462107038850387534327641573

由日志結果可見,大小數保留了足夠的位數,再也不用擔心那些專業領域的數值偏差了。既然通過大小數能夠求得更精確的平方根,那么也能求得更精確的三次方根、四次方根乃至N次方根,有興趣的讀者可實踐一下。

主站蜘蛛池模板: 偃师市| 双流县| 武夷山市| 霍山县| 保山市| 东丰县| 夹江县| 浦东新区| 正阳县| 马关县| 达日县| 丹东市| 平阳县| 遂平县| 如东县| 印江| 娱乐| 晋中市| 瑞安市| 洪洞县| 泽州县| 平山县| 本溪| 霍州市| 滨州市| 枣庄市| 台湾省| 奇台县| 商丘市| 政和县| 雅江县| 白城市| 大港区| 龙川县| 清苑县| 邛崃市| 喀喇沁旗| 山阳县| 青田县| 六枝特区| 沁水县|