2項定理とそのプログラミング処理・追加編

2015/12/16

    

 2項定理を扱うためのruby用ライブラリ binomer.rb に修正を加え、メソッドを追加した。2項定理とそのプログラミング処理と合わせて参照されたい。

 2項定理に直接関係する部分というよりは、級数の四則演算等のメソッドを追加したのが主な改良点。

 プログラム本体と関連のサンプルプログラムをbinomer.zipという圧縮ファイルに同梱した。

    


《目次》


    

1. 自分自身に変更を加えるメソッドを別に設定

 たとえば、各項積分の処理を施すための integral メソッドは、以前は自分自身に変更を加えるものになっていたが、これを改め、積分した結果を別の新たなオブジェクトとして返すようにした。

 そして、自分自身を変更するメソッドとして、integral! を設けた。

 それぞれの別名は、integral → inte, integral! → inte! となっている。

 微分処理を施す differential(別名 diff)も同じように修正した。

 また、オブジェクトの内部変数を更新するための inner() メソッドも、自分自身に変更を加えるのでなく、新たなオブジェクトを返すようにし、自分自身を変更するメソッドとして inner!() を設けた。

    

 なお、2項定理の展開を行う expansion()、与えられた配列を材料としてオブジェクトを生成する from_a() は、従来どおり自分自身に変更を加える。

 別名として expansion!, from_a! を一応設けたが、感嘆符が付かないものと仕様は全く同じ。

    


2. Bnmモジュールの追加

 級数を扱う上で、階乗および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型)。

    


3. 初等関数の級数を返すメソッド

 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", ……]

    


4. 級数の操作にかかわるメソッド

 級数の掛け算の '*'、べき乗の 'power'、割り算の '/' などを行うメソッドを新たに設けた。

 これから述べるメソッドのうち級数の演算にかかわるものは、xの指数が0以上の整数であるとの前提に立っているので留意されたい。

 Binomerクラス自体は、xの指数が-1などの負の数でも支障なく記録できる。具体的な値を代入して計算を行う calc() メソッドも問題なく使える。

 ただ、以下で述べる演算関係のメソッド('*', '/', power, reciprocal)を用いる場合は、xの指数が0以上になるよう事前に調整し、得られた結果に対して事後の調整を施して、つじつまを合わせる必要がある。

 たとえば、csc(x) = 1/sin(x) の展開式を求める場合等に、その事前・事後の調整が必要になる(詳しくは後述)。その調整はサポートしていないので自前でやってほしい。

    

(1) 足し算の '+' と引き算の '-'

 '+', '-' については 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)」である。

    


(2) 定数項を付け加える unshift()

 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

    


(3) 最初の要素を取り出す first(), 最後の要素を取り出す last()

 bnrにBinomerオブジェクトが代入されている場合、bnr.first とすると、内部配列 @bary の最初の要素のコピーを返す。bnr.last なら最後の要素。

 この戻り値を変数 ary に代入したとすると、ary[0]: 係数の値、ary[1]: aの指数の値、ary[2]: xの指数の値となる。

 bnrが中身のない状態であれば、bnr.first, bnr.last の戻り値は不定。

    


(4) 級数の「項」を切り捨てる round(別名 cut)

 bnrにBinomerオブジェクトが代入されている場合、「bnr2 = bnr.round(10)」とすると、bnr2ではxの指数が10を超える「項」が切り捨てられる。

 引数にBinomerオブジェクトを与えると、その与えられたオブジェクトのxの指数を超える「項」を切り捨てる。

 この後で述べる掛け算の '*' を行うと、xの指数が大きなものになるが、無限級数であるはずのものを有限個の級数の形で操作する場合は、元々の級数のxの指数を超える「項」を切り捨てないと、つじつまが合わない部分が目立ってしまう。そのような時に、このroundメソッドを適用する。

 たとえば、下のように記述する。この場合、bnr2の最大のxの指数がbnrと同じになる。

  bnr2 = (bnr * bnr).round(bnr)

 なお、元来が無限でなく有限個の級数であるものを演算操作した場合は、この round を適用すると、逆に不都合な結果になるので注意されたい。

    


(5) 掛け算の '*'

 二つの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 + ……

    


(6) べき乗の展開式を得るための power()

 bnrにBinomerオブジェクトが代入されている場合、bnr.power(3) とすれば、bnrを3乗したものを新たなオブジェクトとして返す。たとえば下のように書く。

  sin2 = (cos = Binomer::cos).power(2).round(cos) * -1 + 1

 power() の引数は、1以上の整数。負の数や有理数には対応していない。

    


(7) 逆数に当たる級数を返す reciprocal(別名 reci)

 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 と同じ。

    


(8) 割り算の '/'

 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"

    


5. その他

 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.


「数学散歩の小道」のページへ

トップページへ