2016-10-20 第5回目 数値計算
本日のテーマ
数値計算
ニュートン法による平方根の計算
平方根は Math.sqrt
メソッド呼び出しで求められると述べました.
ここでは,Math.sqrt
を使わず,ニュートン法を用いて平方根を計算してみましょう.
ニュートン法は,関数$f(x)$が与えられた時,その導関数$f’(x)$を用いて,$f(x) = 0$となる解$x$を数値的に求める方法です. $f(x)=x^2 - 2$とすると,$f(x)=$0の解は$x=\sqrt{2}$ ($x > 0$のとき)です. このように,$f(x)=x^2 - n$の解$x=\sqrt{n} (x > 0)$をニュートン法で求めることで,平方根を計算します.
ニュートン法のアルゴリズムは次の通りです.
- 適当な出発点$x_0$から始まります.
- $f(x_0)$の時の$y$座標の値を計算し,$(x_0, y_0)$を求めます.
- $y_0$の絶対値が閾値(threshold; 0.00001程度)より小さければ,$x_0$が平方根の値となります.
- $(x_0, y_0)$における$f(x)$の接線の式$l_0$を求めます.
- $l_0$と$x$軸との交点座標を求めます.この交点を$x_1$とし,手順の最初に戻ります(今までの$x_0$を$x_1$に置き換えて処理を進めてください).
絶対値は,Math.abs
メソッドを利用して求められます。
Double value = -10.5;
Double positiveValue = Math.abs(value); // => 10.5 が代入される.
例題
実際にニュートン法のプログラムを書いてみましょう.
public class SquareRoot{
void run(String[] args){
for(Integer i = 0; i < args.length; i++){
Double value = new Double(args[i]);
Double result = calculate(value);
System.out.printf("sqrt(%f) = %f (%f)%n",
value, result, Math.sqrt(value));
}
}
Double calculate(Double n){
Double threshold = 0.00001;
Double xValue = 10.0; // 初期値 x0
Double yValue = function(n, x);
// ここにニュートン法のプログラムを書きましょう.
// yValue の絶対値が閾値(threshold)よりも小さければループを抜ける。
while(...){
// xValue における放物線f(x)傾きを求める。傾き slant は
// 2 * xValue で求められる。f'(x)=2x であるため.
// 接線は (xValue, yValue) を通り、傾きも求められた。
// 次は、接線が y 軸と交わる切片 b を求めます(y = a x + b)。
// 次に、接線が x 軸と交わるときの xValue の値を求めましょう。
// yValue にそのときの x のときの放物線の y の値を代入しましょう。
}
return x;
}
// x^2 - n を計算するメソッド.
Double function(Double n, Double x){
return x * x - n;
}
}
出力例
$ java SquareRoot 2 3 4 5 6
sqrt(2.000000) = 1.414214 (1.414214)
sqrt(3.000000) = 1.732051 (1.732051)
sqrt(4.000000) = 2.000000 (2.000000)
sqrt(5.000000) = 2.236070 (2.236068)
sqrt(6.000000) = 2.449490 (2.449490)
任意桁の計算
Java言語の Integer
やDouble
型は表せる桁数が決まっています.Integer
は
32ビット,Long
でも64ビットであるため,それぞれ,$-2^{31}〜2^{31} - 1$,$-2^{63}〜2^{63} - 1$
までの値までしか扱えません.
Javaでは任意桁の計算が行える型が存在します.任意桁の整数を表すBigInteger
と任意桁の実数を表すBigDecimal
です.
これらを扱ってみましょう.
BigInteger
任意桁の整数を表す型です.BigInteger
を利用するときは,import java.math.BigInteger;
というimport
文が必要です.BigInteger
型の実体を作成するときは,new BigInteger("表したい数")
のように数値を文字列で指定してください.
この型を扱うとき,通常の四則演算が使えない点に注意してください.
BigInteger value1 = new BigInteger("10");
BigInteger value2 = new BigInteger("20");
BigInteger value3 = value1.add(value2);
// BigInteger value4 = value1 + value2; // => コンパイルエラー.
上記のように,足し算は add
というメソッド呼び出しで実現します.
以下に対応を掲載します.どれも BigInteger
型の変数b1
とb2
を使って計算しているものとします.
- 足し算(
b1 + b2
)b1.add(b2)
- 引き算(
b1 - b2
)b1.subtract(b2)
- 掛け算(
b1 * b2
)b1.multiply(b2)
- 割り算(
b1 / b2
)b1.divide(b2)
- 割った余り(
b1 % b2
)b1.remainder(b2)
- 符号反転(
-b1
)b1.negate()
- 比較
b1.compareTo(b2)
b1
の方が小さければ,負の整数(b1 < b2
ならばb1.compareTo(b2) < 0
とする),b1
の方が大きければ,正整数(b1 > b2
ならばb1.compareTo(b2) > 0
とする),- 同じ値であれば,
0
が返される(b1 == b2
ならばb1.compareTo(b2) == 0
とする).
なお,BigDecimal
も基本的にはBigInteger
と同じです.
メソッド呼び出しで演算を行い,実体を作成するときは,実数を表す文字列を渡せば良いです.
例題 階乗改
第2回目の練習問題 3. 階乗 を改良し,桁あふれを起こさないようにしましょう.
前回作成した階乗のプログラムは,13!
を正確に計算できません.これを BigInteger
を使って,
どんな数値が与えられたとしても計算できるようにしてください.
クラス名をFactorial2
としてください.
コマンドライン引数から値を受け取れるようにしましょう.
また,コマンドライン引数で受け取る値はInteger
型として扱って良いです.
出力例
$ java Factorial 12 # => 前回のプログラム.正しい結果の限界.
12! = 479001600
$ java Factorial 13 # => 前回のプログラム.正しくない結果が出力されている.
13! = 1932053504
$ java Factorial2 11
11! = 39916800
$ java Factorial2 12 13 14 15
12! = 479001600
13! = 6227020800
14! = 87178291200
15! = 1307674368000
複素数 (Complex)
複素数型の定義
複素数型Complex
を作成しましょう.フィールドには,Double
型のreal
,imag
を定義してください.
また,print
メソッドでComplex
を出力するようにしましょう.
public class Complex{
Double real;
Double imag;
void print(){
System.out.printf("%5.2f + %5.2f i", this.real, this.imag);
}
void println(){
this.print();
System.out.println();
}
}
上記のように,独自の型にはフィールドとメソッドを同時に定義できます.
複素数型を利用するプログラムの作成
次に,Complex
を利用するプログラム ComplexCalculator
を作成しましょう.
public class ComplexCalculator{
void run(){
Complex c1 = this.createComplex(3.0, 4.0);
c1.println();
}
Complex createComplex(Double realValue, Double imagValue){
Complex c = new Complex();
c.real = realValue;
c.imag = imagValue;
return c;
}
// mainメソッドは省略.
}
上記のrun
メソッド内で,Complex
型の print
メソッドを呼び出しています.
このように,メソッドを呼び出すにも,変数を経由して呼び出す必要があります.
一方,変数(フィールド)も変数を経由してアクセスします.createComplex
メソッドをみてください.
このように,Complex
型の変数 c
を経由してフィールドにアクセスしています.
このように,メソッドもフィールドも,必ず変数を経由してアクセスする必要があります.
例題 absolute
さて,Complex
型に Double
型を返す absolute
メソッドを追加してください.引数はありません.
複素数の絶対値を返すメソッドです.
$z=a+bi$の絶対値$r$は,$r = |z| = \sqrt{a^2 + b^2}$で求められます.
上記が完成すれば,ComplexCalculator
で absolute
を呼び出し,結果を出力してください.
例題 conjugate
次に,Complex
型に Complex
型の実体を返す conjugate
メソッドを追加してください.
引数はありません.conjugate
は共役複素数を意味します.虚部の符号を逆転させたものであり,
$z=a+bi$ の共役複素数は,$\bar{z}=a-bi$です.conjugate
メソッド内で
新たにComplex
型の実体を作成し,
呼び出されたメソッドがもつ複素数の共役複素数として変数に値を代入し,返してください
(this
の値を元に,共役複素数を作成し,返してください).
上記が完成すれば,ComplexCalculator
で conjugate
を呼び出し,結果を出力してください.
再帰呼び出し(Recursive call)
例題 階乗改2
第2回目の練習問題 3. 階乗を別の書き方をしてみましょう. $n$の階乗は,$n!=n×(n−1)×(n−2)…2×1$ で求められます. この式を漸化式で表すと次の通りです.
\[
n! = \begin{cases}
1 & n=1\\
n \times (n-1)! & n> 1
\end{cases}
\]
Javaプログラムでこの漸化式を表してみましょう.
public class Factorial3{
void run(String[] args){
for(Integer i = 0; i < args.length; i++){
Integer number = new Integer(args[i]);
this.printFactorial(number, this.factorial(number));
}
}
Integer factorial(Integer number){
if(number == 1){
return 1;
}
return number * this.factorial(number - 1);
}
void printFactorial(Integer number, Integer result){
System.out.printf("%d! = %d%n", number, result);
}
// mainメソッドは省略.
}
上記プログラムのfactorial
メソッドに注目してください.
引数のnumber
の値が1
の場合は,返り値として1
を返します.
そして,number
の値が1
以外の場合は,引数として与える数を変更して,factorial
メソッドを再度呼び出しています.
あるメソッドから,自分自身を呼び出すことを再帰呼び出しと呼びます.
上に示した漸化式で考えると理解が容易でしょう.
再帰呼び出しには,必ず再帰からの脱出口を設ける必要があります.
上の例で言えば,if(number == 1){ return 1; }
が脱出口になっています.
もし,脱出口が設けられていない,もしくは,設けられていても,適切な条件でなければ再帰から抜け出せず,StackOverflowException
が発生します.
練習問題
1. Complexの四則演算
ComplexCalculator
に Complex
の四則演算を追加してください.
Complex
同士の足し算(add
)- $(a + bi) + (c + di) = (a + c) + (b + d)i$
Complex
同士の引き算(subtract
)- $(a + bi) - (c + di) = (a - c) + (b - d)i$
Complex
同士の掛け算(multiply
)- $(a + bi)(c + di) = (ac - bd) + (ad + bc)i$
Complex
同士の割り算(divide
)- $(a + bi) / (c + di) = \frac{(a + bi)(c - di)}{(c + di)(c - di)} = \frac{(ac + bd) + (ad - bc)i}{c^2 + d^2}$
- 全て,2つの
Complex
型の値を受け取り,新たなComplex
型を返します.- 引数で受け取った
Complex
型の値は変更せずに,新たなComplex
型の実体を作成してください.
- 引数で受け取った
ComplexCalculator
のrun
メソッドに次の処理を追加してください.- 2つの
Complex
型の実体を作成してください.- 値はプログラム中で適当に指定してください.
- 四則演算を行い,結果を出力してください.
- 2つの
出力例
$ java ComplexCalculator
absoluate( 5.00 + -6.00 i) = 7.810250
conjugate( 5.00 + -6.00 i) = 5.00 + 6.00 i
5.00 + -6.00 i + 3.00 + 2.00 i = 8.00 + -4.00 i
5.00 + -6.00 i - 3.00 + 2.00 i = 2.00 + -8.00 i
5.00 + -6.00 i * 3.00 + 2.00 i = 27.00 + -8.00 i
5.00 + -6.00 i / 3.00 + 2.00 i = 0.23 + 2.15 i
2. GrandTotal改
第1回目の練習問題 4. 総和を求めるを再帰呼び出しを使って計算するプログラムを作成してください.
クラス名は,GrandTotal2
としてください.
また,コマンドライン引数で最大値を与えられるようにしてください.
出力例
$ java GrandTotal2
1から10までの総和は55です.
$ java GrandTotal2 10
1から10までの総和は55です.
$ java GrandTotal2 100
1から100までの総和は5050です.
$ java GrandTotal2 90
1から90までの総和は4095です.
3. Fibonacci数列 改
第2回目の練習問題 5. Fibonacci数列を再帰呼び出しを使って計算してみましょう.
Fibonacci数列の$n$項目の値を出力してください.
クラス名はFibonacci2
としてください.
コマンドライン引数に値が指定されない場合は,10項目が指定されたものとしてください. コマンドライン引数に複数個の数値が与えられた場合,全ての数値に対して結果を出力してください.
出力例
$ java Fibonacci2
55
$ java Fibonacci2 1 2
fibonacci(1) = 1
fibonacci(2) = 1
$ java Fibonacci2 4 10 20
fibonacci(4) = 3
fibonacci(10) = 55
fibonacci(20) = 6765
$ java Fibonacci2 40
fibonacci(40) = 102334155
4. Fibonacci数列 改2
第2回目の練習問題 5. Fibonacci数列を改良し,桁あふれを起こさないようにしてください.
再帰呼び出しではなく,単純な繰り返しでFibonacci数列の$n$項目を求てください(単純な再帰呼び出しは遅いため).
クラス名はFibonacci3
としてください.
Fibonacci数列を Integer
型で扱うと,第47項目の計算で桁あふれを起こします.
コマンドライン引数に値が指定されない場合は,10項目が指定されたものとしてください. コマンドライン引数に複数個の数値が与えられた場合,全ての数値に対して結果を出力してください.
出力例
$ java Fibonacci3
fibonacci(10) = 55
$ java Fibonacci3 10 30 46 47 48 49 50
fibonacci(10) = 55
fibonacci(30) = 832040
fibonacci(46) = 1836311903
fibonacci(47) = 2971215073
fibonacci(48) = 4807526976
fibonacci(49) = 7778742049
fibonacci(50) = 12586269025
5. ニュートン法による立方根の計算
ニュートン法による平方根の計算を参考に立方根を求めてください.
クラス名は,CubicRoot
とします.
出力例
$ java CubicRoot 2 3 4 5 6 8 27
cubic_root(2.000000) = 1.259921
cubic_root(3.000000) = 1.442250
cubic_root(4.000000) = 1.587401
cubic_root(5.000000) = 1.709976
cubic_root(6.000000) = 1.817121
cubic_root(8.000000) = 2.000000
cubic_root(27.000000) = 3.000000
まとめ
- 任意桁の計算には,
BigInteger
,BigDecimal
を利用する.- 利用には,
import
文が必要.BigInteger
を利用するときは,import java.math.BigInteger;
.BigDecimal
を利用するときは,import java.math.BigDecimal;
.
- 通常の四則演算などの演算子(
+
,*
など)は利用できない.- 四則演算などは
BigInteger
で定義されているメソッド呼び出しで計算する.BigDecimal
も同様にメソッド呼び出しで四則演算などを実現する.
- 四則演算などは
- 利用には,
- 独自の型には,変数(フィールド)とメソッドを定義できる.
- あるメソッドの中で自分自身を呼び出せる.
- 再帰呼び出しと呼ぶ.
- 再帰呼び出しのメソッドは,必ず再帰から抜ける脱出口が存在する.
- もし,存在しなければ,
StackOverflowExceptionが発生する
.- メソッド呼び出しが深すぎて,それ以上メソッドを呼び出せない状態.
- もし,存在しなければ,