2項定理を扱うためのruby用ライブラリ binomer.rb に修正を加え、メソッドを追加した。2項定理とそのプログラミング処理と合わせて参照されたい。
2項定理に直接関係する部分というよりは、級数の四則演算等のメソッドを追加したのが主な改良点。
プログラム本体と関連のサンプルプログラムをbinomer.zipという圧縮ファイルに同梱した。
たとえば、各項積分の処理を施すための integral メソッドは、以前は自分自身に変更を加えるものになっていたが、これを改め、積分した結果を別の新たなオブジェクトとして返すようにした。
そして、自分自身を変更するメソッドとして、integral! を設けた。
それぞれの別名は、integral → inte, integral! → inte! となっている。
微分処理を施す differential(別名 diff)も同じように修正した。
また、オブジェクトの内部変数を更新するための inner() メソッドも、自分自身に変更を加えるのでなく、新たなオブジェクトを返すようにし、自分自身を変更するメソッドとして inner!() を設けた。
なお、2項定理の展開を行う expansion()、与えられた配列を材料としてオブジェクトを生成する from_a() は、従来どおり自分自身に変更を加える。
別名として expansion!, from_a! を一応設けたが、感嘆符が付かないものと仕様は全く同じ。
級数を扱う上で、階乗および2項係数を生成するメソッドがあると便利なので、Bnmモジュールを設けて、その中でその二つのメソッドを定義した。
○ Bnm::fact(n)
階乗を返す。引数nは、0以上の整数。
○ Bnm::ncr(n, r)
2項係数を返す。つまり、n個からr個を取り出す場合の組み合わせの数。あるいは、その有理数拡張版の値。
nは任意の有理数、rは0以上の整数を想定。
戻り値は、[2項係数の分子, 2項係数の分母, 2項係数] の形式の配列。
第1・第2要素の分子・分母は、既約分数に変換する前の値。なので、分母はrの階乗になっている。
第3要素の2項係数の値は、既約分数にした上で、分母が1の時は整数値、それ以外は有利数値(Rational型)。
2項定理と直接は関係ないが、自然対数にかかわる e, log、三角関数にかかわる sin, cos の級数を簡単に得るためのメソッドを設けた。
いずれも Binomer::e, Binomer::log, Binomer::sin, Binomer::cos として呼び出すことができる。たとえば次のようにして呼び出す。
require "binomer"
p Binomer::e.cx # => ["1", "x", "1/2x^2", "1/6x^3", "1/24x^4", ……]
引数として、第何項まで得るかを1以上の整数値で指定できる。指定しない時は 10 が指定されたものと見なされる。
たとえば、Binomer::sin(5) とすれば、第5項まで得られる。
e, sin, cos については e(x), sin(x), cos(x) の展開式になっているが、log の場合は log(1+x) の展開式なので注意されたい。
ちなみに、logの展開式は、下のように2項定理を用いて得ることもできる。
require "binomer"
log = Binomer.new(-1).integral
p log.cx # => ["x", "-1/2x^2", "1/3x^3", ……]
割り算の '/' については後述するが、タンジェントの展開式は下のようにして得られる。
require "binomer"
tan = Binomer::sin / Binomer::cos
p tan.cx # => ["x", "1/3x^3", "2/15x^5", "17/315x^7", ……]
級数の掛け算の '*'、べき乗の 'power'、割り算の '/' などを行うメソッドを新たに設けた。
これから述べるメソッドのうち級数の演算にかかわるものは、xの指数が0以上の整数であるとの前提に立っているので留意されたい。
Binomerクラス自体は、xの指数が-1などの負の数でも支障なく記録できる。具体的な値を代入して計算を行う calc() メソッドも問題なく使える。
ただ、以下で述べる演算関係のメソッド('*', '/', power, reciprocal)を用いる場合は、xの指数が0以上になるよう事前に調整し、得られた結果に対して事後の調整を施して、つじつまを合わせる必要がある。
たとえば、csc(x) = 1/sin(x) の展開式を求める場合等に、その事前・事後の調整が必要になる(詳しくは後述)。その調整はサポートしていないので自前でやってほしい。
'+', '-' については 2項定理とそのプログラミング処理 で既に触れた。二つのBinomerオブジェクトについて、xの指数が共通する「項」の係数の足し算・引き算を行う。
それに加えて、Binomerオブジェクトに対し数値を足し算または引き算した場合に、定数項にその数値を加算または減算するようにした。
変数bnrにBinomerオブジェクトが代入されているとき、「bnr + 1」とすれば、bnrの定数項に1を加算することになる。加算した結果を新たなBinomerオブジェクトとして返す。bnrに定数項がない時は、定数項1を付け加える。
「bnr - 1」とすれば、bnrの定数項から1を減算する。定数項がなければ、定数項 -1 を付け加える。
なお、「1 + bnr」といった記述はできないので注意されたい。'+', '-' は、あくまでBinomerクラスで定義されているメソッド。「bnr + 1」を別の書き方にすれば、「bnr::+(1)」である。
bnrにBinomerオブジェクトが代入されている場合、bnr.unshift(1) とすれば、bnrに定数項1を付け加える。
戻り値は新たなBinomerオブジェクト。bnr自身に変更が加えられるわけではない。
bnrに既に定数項がある時は、エラーメッセージを出して何も処理は加えられない。その時の戻り値は不定。
引数には、整数、有理数(Rational)、実数(Float)の数値を指定する。サンプルは次のとおり。
require "binomer"
arcsin = Binomer.new(-Rational(1,2), 50, -2).integral
arccos = arcsin.inner("c *= -1").unshift(Math::PI/2.0)
puts arccos.calc(0.5) * 3.0 # => 3.141592653589794
bnrにBinomerオブジェクトが代入されている場合、bnr.first とすると、内部配列 @bary の最初の要素のコピーを返す。bnr.last なら最後の要素。
この戻り値を変数 ary に代入したとすると、ary[0]: 係数の値、ary[1]: aの指数の値、ary[2]: xの指数の値となる。
bnrが中身のない状態であれば、bnr.first, bnr.last の戻り値は不定。
bnrにBinomerオブジェクトが代入されている場合、「bnr2 = bnr.round(10)」とすると、bnr2ではxの指数が10を超える「項」が切り捨てられる。
引数にBinomerオブジェクトを与えると、その与えられたオブジェクトのxの指数を超える「項」を切り捨てる。
この後で述べる掛け算の '*' を行うと、xの指数が大きなものになるが、無限級数であるはずのものを有限個の級数の形で操作する場合は、元々の級数のxの指数を超える「項」を切り捨てないと、つじつまが合わない部分が目立ってしまう。そのような時に、このroundメソッドを適用する。
たとえば、下のように記述する。この場合、bnr2の最大のxの指数がbnrと同じになる。
bnr2 = (bnr * bnr).round(bnr)
なお、元来が無限でなく有限個の級数であるものを演算操作した場合は、この round を適用すると、逆に不都合な結果になるので注意されたい。
二つのBinomerオブジェクトを掛け合わせるメソッドとして '*' を定義した。掛け合わせた結果を新たなBinomerオブジェクトとして返す。たとえば下のように記述する。
cos = Binomer::cos
cos2 = (cos * cos).round(cos)
また、'*' の後に数値を置くと、級数の全部の係数にその数値を掛け合わせる。たとえば下のとおり。
sin2 = cos2 * -1 + 1
それから、'*' の後に “x” とか “x^2” などの文字列を置くと、級数全体にx や x^2 を掛け合わせる。つまり、級数全体を通して xの指数を1(または2など)だけ加算する。たとえば下のように書く。
cosx = Binomer::cos * "x"
puts cosx.cx(:string) # => x - 1/2x^3 + 1/24x^5 - 1/720x^7 + ……
bnrにBinomerオブジェクトが代入されている場合、bnr.power(3) とすれば、bnrを3乗したものを新たなオブジェクトとして返す。たとえば下のように書く。
sin2 = (cos = Binomer::cos).power(2).round(cos) * -1 + 1
power() の引数は、1以上の整数。負の数や有理数には対応していない。
bnrにBinomerオブジェクトが代入されている場合、bnr.reciprocal とすれば、1/bnr に相当する新たなオブジェクトを返す。たとえば次のように書く。
sec = Binomer::cos.reciprocal
p sec.cx # => ["1", "1/2x^2", "5/24x^4", "61/720x^6", ……]
reciprocalを適用する場合、Binomerオブジェクトの定数項が1でなければならない。この条件が満たされていなければ、reciprocalメソッドは、エラーメッセージを出して何も処理を行わない。その場合の戻り値は不定。
たとえば、三角関数の sin には定数項がない。なので、そのままでは reciprocal を適用できない。
sinは、級数全体をxで割ると、定数項が1になる。そこで、そのxで割ったものにreciprocalを適用し(事前の調整)、得られた結果をxで割るという処理を施す(事後の調整)。面倒だが、こうした調整を自前で行う必要がある。
参考まで csc(x) = 1/sin(x) を得るためのプログラムを掲げる。
require "binomer"
csc = Binomer::sin.inner("x -= 1").reciprocal.inner("x -= 1")
p csc.cx # => ["x^-1", "1/6x", "7/360x^3", "31/15120x^5", ……]
p csc.calc(Math::PI / 6.0) # => 1.999999999999999
蛇足だが、cot(x) = 1/tan(x) を得る場合、ほんとは cos * csc でいいのだが、上の csc は初項のxの指数が-1なので、Binomerクラスの掛け算 '*' を適用できない(xの指数が0以上でないと駄目)。事後調整のタイミングを工夫する必要がある。
なお、reciprocal には引数として1以上の整数値を与えることができる。省略すると 10 が与えられたものとみなされる。
reciprocal は、内部で「1/(1-r) = 1 + r + r^2 + r^3 + r^4 + ……」を利用している。
bnr.reciprocal(5) とすると、1/(1-r) の r^5 まで展開処理を行う。
通常はデフォルトの 10 で十分だと思うが、必要なら別の値を指定されたい。
それから、reciprocalは、roundによってまるめた結果を返す。つまり、bnr.reciprocal で得られる結果は、bnrのxの指数を超える「項」が切り捨てられたものになっている。
roundを適用しない結果を得たい時は、reciprocalx(別名 recix)を用いる。このメソッドの使い方は reciprocal と同じ。
Binomerオブジェクトに対して割り算を行う '/' を設けた。たとえば、三角関数のタンジェントを得るには下のようにする。
tan = Binomer::sin / Binomer::cos
'/' は、内部で前述の reciprocal を呼び出している。なので、'/' の後ろにくるBinomerオブジェクトは、定数項が1でなければならない。
そのため、cot(x) = 1/tan(x) を得る際には事前・事後の調整が必要になる。次のようにする。
require "binomer"
sin = Binomer::sin
cos = Binomer::cos
cot = (cos / sin.inner("x -= 1")).inner("x -= 1")
p cot.cx # => ["x^-1", "-1/3x", "-1/45x^3", "-2/945x^5", ……]
val = Math::PI / 6.0
p cot.calc(val) # => 1.7320508075688787
p cos.calc(val) / sin.calc(val) # => 1.7320508075688774
p Math::sqrt(3) # => 1.7320508075688772
'/' は、その後ろに置かれたオブジェクトに対して reciprocal を適用し、その結果を '/' の前に置かれているオブジェクトに掛け合わせる。
「tan = sin / cos」の場合でいうと、「tan = sin * cos.reciprocal」と同じである。
このとき、tanは、'/' の前に置かれている sin に合わせる形でroundが適用される。つまり、sinのxの指数を超える「項」は、tanには含まれない。roundによって切り捨てられた結果が tan に代入される。
それから、'/' の後に数値を置いた場合は、級数全体を通して係数をその数値で割り算する。たとえば下のように書く。
bnr2 = bnr / 2
また、'/' の後に “x” とか “x^2” などの文字列を置くと、級数全体を x や x^2 で割る。つまり、級数全体を通して xの指数を1(または2など)だけ減算する。たとえば下のように書く。
cot = (cos / (sin / "x")) / "x"
Binomerオブジェクトを操作するための '+', '-', '*', '/' は、加減より乗除を優先するといった一般的なルールは適用されない。左から右に順番に処理される。
なので、優先的に処理したい部分は、カッコでくくるなどの工夫が必要。
圧縮ファイルに含まれている bnr05.rb に、これまで例として掲げた tan, sec, csc, cot に関する記述が含まれている。
bnr06.rb には arcsin, arctan の展開式を表示する例が書かれている。
エッセンスを抜き出すと次のとおり。
sin = Binomer::sin
cos = Binomer::cos
tan = sin / cos
sec = cos.reciprocal # sec = 1/cos
csc = (sin / "x").reciprocal / "x" # csc = 1/sin
cot = (cos / (sin / "x")) / "x" # cot = 1/tan = cos/sin
arcsin = Binomer.new(-Rational(1,2), nil, -2).integral
# arcsin = integral(1 / sqrt(1 - x^2))
arctan = Binomer.new(-1, nil, 2).integral
# arctan = integral(1 / (1 + x^2))
〜 「2項定理とそのプログラミング処理・追加編」おわり 〜
Copyright (C) T. Yoshiizumi, 2015 All rights reserved.