\CAPTION\ |
\CAPTION\ |
積分区間をN等分したとして、その区分点をx[0]〜x[N]とします。x[0]は積分区間の下限、x[N]は上限になります。また、その間隔をΔx(=(上限-下限)/N)とします。
(右図では、3等分してあって、x[0],x[1],x[2],x[3=N]に縦線が引いてある)。
また、x[i]に対する関数の値f(x[i])=f[i]と書くことにします。
上の、短冊を組み合わせた積分は、
ところが、この二つの積分は計算対象によっては劇的に精度に違いが出ます。
この式を別に解釈すると、短冊の時は、短冊の左端を目的の曲線にあわせるか、右端にあわせるかすることでもう一方の端の値が無視されるのに対して、両端を半分ずつ反映しましょう、とも見られます。
※上のプログラムをコピーし、numint_dai.cに名前変更、
ここで、新しいキーワードが増えました。
ちなみに、しつこく括弧が書いてあるには訳があります。
なお、このdefineは
もう、説明不要だと思いますが、 f(d)が上の式のf[0]、f(u)がf[N]にあたります。
試してみましょう
です(f(x[0])Δx+f(x[1])Δx+...)。
それに対して、台形でつくるときは、
です。この、Σの中をよく見ると、
となっています。書き換えると、
よって、元々の台形積分は、
となります。先ほどの短冊を組み合わせた積分と見比べると計算に時間がかかりそうなひたすら合計する部分はほとんど変わらず(0...N-1が1...N-1になっただけ)、両端の1/2がくっついただけです。実質的に計算時間はほとんど変わりません。
それを比較してみましょう。
台形積分は短冊の積分の結果に細工をすれば得られます。
※Σに0を含めて、その分を外で-f[0]、計算まとめ
※//★の行に注意して修正すると、手っ取り早い。
※もちろん、「慣れる」には打ち直すのがよい。
求めているのは
です。
これまでのプログラムでは積分したい関数は1回しか書かなかったので、直接書いていましたが、今回は台形積分の計算でもう2回使います。
関数を変えてみたいときに合計3回書き換えると面倒なだけではなく、書き間違いの可能性もあります。
そこで、f(?)と書くだけでいいようにしてしまいます。
その結果、「f(u)」は「((u)*(u))」に、「f(d)」は「((d)*(d))」にそれぞれ置き換えられます。
(UUには影響しません)
プログラムの上の方の u, d, n などはこの#defineで書くこともできます。
(ちなみに、見た目の区別のため、#define を使うときは、#defineを使った、という区別のため、大文字を使うことが一般です。それに従えばF(x)としたほうが無難です。)
参考:
すると差が出ない。0〜4は単調増加の関数なのに対して、-4〜4にすると増減する。
増減する関数は短冊でも案外なんとかなる(不足とはみ出しが適当に帳消ししやすい)ため。
もっと端的には、この場合は f(d)=f(u)なので計算上一致するはず。
短冊と台形の違いは、f[i]と、計算に使うべき値を適当に減らしたことで無視されてしまった、f[i]〜f[i+1]の値を「どう推測したか」の違いでもあります。
短冊は0次関数(一定値)、台形は1次関数(直線)で考えたものです。この先、2次、3次と増やしていくとより精度は上がりますが、短冊と台形ほど近くはなく、計算も面倒になっていきます。
実行すると、
プログラムのポイントを以下に説明します。
ここで、表計算の時の計算誤差で問題になった、c=0にしたときを見てみます。
本来は、xの揺れ幅は大きくなるはずはありません。が、
振り子は、振り子の向いている角度θ(真下で0,真横で±π/2[rad])について、
そこで、計算間隔をdt=0.00001秒(10マイクロ秒)にしてみます。時間が1/100細かくなったので、計算回数は100倍に増えますが、この程度ならすぐに答えが出ます。
もう一つ、後半は時間が「9.399」のように、「9.400」のようなきりのいい数字にはなっていません。
これは、ndが10000ではなく、実は9999になっていることで起きた問題です。
本来、0.1/0.00001=10000のはずですが、0.1も0.00001もコンピュータ的にはきりのいい数字ではなく、その割り算の結果で、わずかに10000には満たず、(int)によって切り捨てられて、9999になってしまっています。
ただの(int)(iv/dt)だと切り捨てられてしまうため、(int)(iv/dt+0.5)とすると、四捨五入になって、無事10000になります。
※なぜ0.5を足して切り捨てすると四捨五入に?
これは、
さらに、「速度が十分小さいなら静止と見なして、そのときは静止摩擦力にする」という書き方も、ifを重ねていけばできます。
または、
(単に角速度ωを用意して、分解しただけ)
という関係が成り立ちます。
この振り子の運動をシミュレートしてください。
なお、振り子の初期角度は「80+学籍番号下一桁[deg]」(80〜89)、振り子の長さは「3+学籍番号百の位」(4or5)でつくってください。
計算の時間間隔(dt)はお任せ、ただし、結果は「0.1秒(くらい)間隔、10秒間の計算結果」を「時刻、角度」の組で表示するようにしてください(角速度などが表示されても可、単位もお任せ)。
なお、まえのレポートの結果と比較してみるとよいでしょう。
異なる場合は、どちらかがなにか違います。
※「θ、ω」は変数名に使えないため、theta, omega などを使用。
※角度はdegからradに変換しないといけない。[rad]=[deg]/180.0*M_PI;
※例:theta=85.0/180.0*M_PI;
結果例:(l=3.123, g=9.80, theta0=90[deg], omega(t=0)=0, dt=0.0001)
※この通りの形式で表示する必要はなく、上記の通り、時刻と角度(この例の最初の二つの数値)が最低限あればよい。
たとえば、theta,omega,domega,l,g にでもしましょう。(各加速度は特に文字がないため、omegaの微分という意味でdをつけたomegaにしてみた)
※自分でプログラムをつったら、まずは、l,g,theta初期値をセットして、同じような値が出るかを確認するとよい。
そこで、グラフ化やデータの扱いについては表計算に任せてみましょう。
できたファイルをフォルダで確認すると、Excelのらしいアイコンが付くので、ダブルクリックして開いてみましょう。
あとは以前と同じで、数値の範囲を選択して、グラフにしてみればOKです。
このように、
「>」をつかうリダイレクトは、Cygwinを含むUNIX関係のOSや、MacOSXの端末、Windowsのコマンドプロンプトなど、あちこちで使えます。使えないときは、プログラムで直接ファイルに書き出す、という操作が必要になりますが、とりあえず、この講義では避けておきます。
C言語の参考書にはふつうは載ってますので参考にしてみてください(printfのかわりにfprintfを使うようになる)。
さて、そのためには、文字で書かれたファイルの内容を解釈しなければなりません。
しかし、このためのプログラムは、今までの数値計算とはまた別のC言語的知識(ファイルの読み込み、文字列操作)が必要となり、またプログラムも長いものとなります。
そこで、CSVファイルを読み込む部分は予め用意しました(ある意味、printfなども、標準的に用意されているもの)。それをつかって試してみましょう。
CSVファイルは、
(デスクトップの)「マイコンピュータ」→
ここにある、csv.cを自分のcygwin/program フォルダにコピーしてください。
「LoadCSV("ファイル名.csv");」 はcsvファイルを読み込みます。
エラーがあればエラーが表示され、問題なければ、「ファイルの行数:」と表示がでます。
「Value(EX?,?)」は読み込み済みのcsvファイルの特定のセルに当たる数値を取得します。
セルを表す「A1」などの「A〜Z」を「EXA〜EXZ」で、行番号を数字で指定します。
なお、元のファイルに存在しないセルを指定したり、あっても数値以外が書き込まれている場合(ここまでの操作通りなら、セルA1は「データ数」という数字ではない文字)、メッセージを表示して、値はゼロになります。
計算は
以前の表計算でやったときの結果と見比べ、同じ値が得られたか確認してみましょう。
まずやってみましょう。
ここの「,」(カンマ)は必ず半角で入れてください。
(ので、前のプログラム simu1.c をコピーして修正した方が早い:フォルダ上でCtrl-C Ctrl-Vしてsimucsv.cに名前変更もしくはVisualStudio上で名前をつけて保存)
さて、実行すると、
ここで、
さっきは画面に表示されたのが、出てこなくなります。
これは「リダイレクト」といって、printfで本来画面に出てくるはずのものを、画面ではなく、>の先、ここではsimu.csvに書き出す、という操作です。
(TeraPadやメモ帳などで開いてみると中身が見えます)。
すると、Excelが起動し、さっき画面に表示されていた値がきれいにセルに入ります。
とすれば値を持って行くことができます。
これはExcelに限らず、普通の表計算ソフトなら使える手です。
この、カンマで区切られた数値を入れた.csvなファイルを「CSV(Comma separated value)」ファイルといいます。一般的なファイル形式の一つで、数値群を扱うソフト、たとえば表計算ソフトの他、グラフ専用のソフト、計測装置なども入出力できる形式です。
で、それをプログラムから直接出させたわけです。
使い道としては、CSVのデータを直接プログラムで処理したり、表計算ソフトなどでシミュレーション条件をいくつも決めた上で、C言語のプログラムに各条件でシミュレーションさせ、さらに結果を表計算に戻す、といったことも考えられます。
1:CSVファイルを用意する。
「データのプレビュー」で数値の間に線が入っていることを確認。
このとき2回ほど「この形式で保存していいか?」と念を押されるけど気にせずOK。
2:csv.cをとってくる。
(ネットワークドライブ)「Users(S:)」→
「Kumagai」フォルダ→
「Computer」フォルダを開いてください。
3:テストのプログラムを動かしてみる。
例:セル「C5」はValue(EXC,5)
4:最小自乗法のプログラムを作ってみる。
です。このプログラムでは、計算に使う
をそれぞれ、sx, sy, sxx, sxy という変数にして、最初にゼロを入れ、データの数だけforで加えています。
データの個数は1行目、B2のところにありますので、それを使っています。
乱数を使うには、ただ「rand()」とします。「rand()」が使われるたび、異なる乱数が得られます。
なお、厳密には、このさいころには偏りがでます。
0を含めてRAND_MAXまで出る種類はRAND_MAX+1=2147483648種類の数値がでることになります。
これは357913941×6+2なので、さいころにするときに4種の目が357913941回、2種が357913942回でることになります(%6+1なので、前者が3〜6、後者は1,2)。
厳密には偏りですが、実際のさいころよりは遙かに高精度なので気にする必要はありません。
(一方で、微妙な周期性が問題になることはあり)。
プログラムの先頭のほうの「srand()」は「乱数の種」と呼ばれる、初期値設定するものです。
乱数に初期値?というのも変ですが、前述の通り、周期性のあるものなので、スタート地点が設定できます。
ランダム性を重視するなら、たとえば現在時刻(time()である起点からの秒数が得られる)のような、実行時で変化するような値を種として使います。
その一方で、「ランダムだけど、再現性のある確認をしたい」という場合は、敢えて特定の適当な数字を指定します。
(srandを使わない場合は、自動的に1がつかわれたりするようです。)
上のプログラムは1225を種にしているので、何度実行しても表示される乱数の順序は変わりません。
1225を1224に変えて再コンパイルすれば、別の順番で表示されますが、それも2回目の実行で変化ありません。
コメントにしてある、srand(time()); にすれば、実行ごとにかわるでしょう。
(ただし、1秒以内に2回実行すると同じになる可能性がある)
いずれにせよ、擬似的ですが、見た目それっぽい乱数が得られます。
正しいさいころは、それぞれの目が出る確率は1/6です。
n回さいころを振ったら、n/6回、特定の目が出るはずです。
これを試してみましょう。
その上で、さいころの目の値 r が3と等しければ「if(r==3)」、変数okに1を加えます(ok++;)。
等しくなければ、変数ngに1を加えます。
ループ終了後、結果を表示します。
ここで、okの率(r==3になった率)は、okを、ok+ng(=ループ数)で割ったものになります。
例によって、「整数/整数」だと結果が切り捨ての整数になるため、最初に%に換算するための100.0をかけてしまっています。
(特に、確率ものの場合は、普通、1未満になるので、ゼロになってしまうことが多い)。
結果は、期待通りなら、16.666...%になるはずですが、微妙に差がでます。
この差は、前述の乱数の偏りというよりは、ランダムだからこそ生じた偏りです。
srandをtime()で行ったり、別の数字に変えてみると、16.66より多くも少なくも変わります。
さて、見たところ、11%くらいですが、実際の期待値も考えてみます。
さいころを2個振ると、それぞれ1〜6がでますが、合計が5になる組み合わせは
(1,4)(2,3)(3,2)(4,1)
の4通りです。全体では6通り×6通り=36通りの組み合わせが等しく出る可能性があるため、
4/36=1/9=11.111..%
が想定される値です。
というわけで、それらしい値が得られました。
このように、「考えればわかるけど、本当に考えたことが正しいか」「考えてもわからなさそう」というときに、おおざっぱにチェックできるのが、コンピュータで乱数、の便利なところです。
なお、今回の程度のチェックでは、実際には、「全組み合わせをチェック」することもでき、その方が精度良く結果がわかります(さいころ3個で6×6×6=216通りにしかならない)。ただ、「さいころを20個ふって合計が○○になる」といった場合は、すべての組み合わせが6の20乗になるため、チェックしきれません。そういうときに非常に効果があります。
問題:
さいころを3こ振って、合計が10以下となる確率を求めてください。
ヒント: