- 好好學Java:從零基礎到項目實戰
- 歐陽燊
- 1529字
- 2022-07-27 19:15:03
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判斷,是因為要校驗迭代前后的xm與xm+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次方根,有興趣的讀者可實踐一下。
- Practical Data Analysis Cookbook
- Data Visualization with D3 4.x Cookbook(Second Edition)
- TypeScript入門與實戰
- Learning Apex Programming
- C++面向對象程序設計(微課版)
- MariaDB High Performance
- Podman實戰
- Python應用輕松入門
- Learning ArcGIS for Desktop
- C語言程序設計
- JavaCAPS基礎、應用與案例
- 區塊鏈技術與應用
- 執劍而舞:用代碼創作藝術
- Swift 4從零到精通iOS開發
- 計算機應用基礎教程(Windows 7+Office 2010)