2017-05-11 第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, xValue);
// ここにニュートン法のプログラムを書きましょう.
// yValue の絶対値が閾値(threshold)よりも小さければループを抜ける.
while(...){
// xValue における放物線f(x)傾きを求める.傾き slant は
// 2 * xValue で求められる.f'(x)=2x であるため.
// 接線は (xValue, yValue) を通り,傾きも求められた.
// 次は,接線が y 軸と交わる切片 b を求めます(y = a x + b).
// 次に,接線が x 軸と交わるときの xValue の値を求めましょう.
// yValue に 放物線の y の値(xValueを元に求める)を代入しましょう.
}
return xValue;
}
// 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
を使って,
どんな数値が与えられたとしても計算できるようにしてください.
クラス名をBigFactorial
としてください.
コマンドライン引数から値を受け取れるようにしましょう.
また,コマンドライン引数で受け取る値はInteger
型として扱って良いです.
出力例
$ java Factorial 12 # => 前回のプログラム.正しい結果の限界.
12! = 479001600
$ java Factorial 13 # => 前回のプログラム.正しくない結果が出力されている.
13! = 1932053504
$ java BigFactorial 11
11! = 39916800
$ java BigFactorial 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 Factorial2{
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
が発生します.
また,動作を確認するために,上記プログラムのfactorial
メソッドの先頭にSystem.out.printf("factorial(%d)%n", number);
という一文を入れて実行してみても良いでしょう.
練習問題
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}$
- 上記4つのメソッドは2つの
Complex
型の値を受け取り,新たなComplex
型の実体を返します.- 引数で受け取った
Complex
型の値は変更せずに,新たなComplex
型の実体を作成してください.
- 引数で受け取った
ComplexCalculator
のrun
メソッドに次の処理を追加してください.- 2つの
Complex
型の実体を作成してください.- 値はプログラム中で適当に指定してください.
- 上記の4つのメソッドを呼び出し,四則演算の結果を出力してください.
- 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$項目を求めてください(単純な再帰呼び出しにすると,非常に遅くなるため).
クラス名はBigFibonacci
としてください.
Fibonacci数列を Integer
型で扱うと,第47項目の計算で桁あふれを起こします.
コマンドライン引数に値が指定されない場合は,10項目が指定されたものとしてください. コマンドライン引数に複数個の数値が与えられた場合,全ての数値に対して結果を出力してください.
出力例
$ java BigFibonacci
fibonacci(10) = 55
$ java BigFibonacci 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. 線形合同法による擬似乱数列
線形合同法(Linear Congruential Generators)を用いて,0〜1の範囲の乱数をコマンドライン引数で指定された数だけ求めてください.
コマンドライン引数で何も指定されなかった場合は,10が指定されたものとしてください.
クラス名は LinearCongruentialGenerator
としてください.
このプログラムも,素数の一覧
と同じように ArrayList<Double>
を返すメソッドを作成し,
返されたArrayList<Double>
の実体をそのままSystem.out.println
に渡してください.
線形合同法は,擬似乱数を発生させるアルゴリズムです.以下の漸化式で求めます.
$X_{n+1}=(A\times X_n + B) \mod M$
$A$,$B$,$M$は定数です.$A$は自分の誕生日(月日.3桁もしくは4桁),$B$は1, $M$は65536,$X_0$は自分の年齢としてください. 完成すれば,$A$,$B$,$M$,$X_0$の値を変更して結果がどのように変わるかを確認しましょう. ただし,$A<M$,$B<M$である必要があります.
この問題は,必ずしも再帰呼び出しで作成する必要はありません.
なお,C言語の rand
関数は,この線形合同法を用いて計算されています.
5月10日(水) 11:00 に問題文($A$,$B$の初期値)を変更しています. 今までの条件であれば,桁あふれを起こすためです. 上記時間以前に作成した人は,$A$,$B$の初期値を修正してください.
この練習問題で作成した乱数の安全性について
また,ここで作成した乱数は安全ではありません(未来の出力が容易に予測できます). そのため,自分自身で作成するプログラムに,このようなアルゴリズムで作成した乱数は使わないようにしてください. 少なくともライブラリとして用意されている乱数を利用してください.出力例
各自の出力結果は,以下のものと異なる値になります.
$ java LinearCongruentialGenerator
[0.019073486328125, 0.308502197265625, 0.329376220703125, 0.855133056640625, 0.471710205078125, 0.077545166015625, 0.383575439453125, 0.413238525390625, 0.002471923828125, 0.299713134765625]
$ java LinearCongruentialGenerator 2
[0.019073486328125, 0.308502197265625]
$ java LinearCongruentialGenerator 5
[0.019073486328125, 0.308502197265625, 0.329376220703125, 0.855133056640625, 0.471710205078125]
ヒント
乱数値はDouble
型で求めますが,$X_{n+1}$の計算式はInteger
型で計算する必要があります.
つまり,結果を保存するとき,$X_{n+1}$ を $M$で割り,Integer
型からDouble
型に変換する必要があります.
ArrayList<Double> random(Integer max){
ArrayList<Double> results = // 結果を格納するリストを作成する.
Integer a, xn, b, m;
xn = 20; // X0(自分の年齢)
// a, b, m にも初期値を代入する.
// 以下の2行を指定回数繰り返す.
xn = // 線形合同法の計算式に従い,X_n+1 を求める.
results.add(1.0 * xn / m); // xnを0.0〜1.0の範囲に変換してリストに追加する.
return results;
}
6. ニュートン法による立方根の計算
ニュートン法による平方根の計算を参考に立方根を求めてください.
クラス名は,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が発生する
.- メソッド呼び出しが深すぎて,それ以上メソッドを呼び出せない状態.
- もし,存在しなければ,