.. title:: Pythonで心理実験 - 例題3-1 例題3-1:四則演算がなければ始まらぬ ====================================== **A:** お、B君今日はヒマそうだな。 **B:** 先生が出張先から帰れなくなったとかで急に休講になったんですよ。飛行機が欠航したんだそうで。 **A:** あー、飛行機はそういう事があるから厄介だよなあ。じゃあ昼までは時間あるよね? **B:** う。そのニヤリ顔は何かタダ働きさせる気ですね? **A:** 誰がニヤリ顔じゃ。リーディングスパンテストのプログラムを作った時に、pythonの演算子をきちんと教えないといけないなぁっていう話をしただろ。 今ヒマならぱぱっと説明しちゃおうかと思って。 **B:** あ、それは是非お願いします。 **A:** よっしゃ。今回はサンプルプログラムは特に用意していないし、pythonインタプリタから直接入力した方が作業が簡単だから、pythonインタプリタを使ってみよう。 起動方法は覚えているかい? **B:** えーと、WindowsのスタートメニューからPython (command line)を選べばいいんでしたよね? **A:** それかpythonインタプリタの実行ファイルにPATHを通してコマンドシェルからpythonとコマンドを入力してEnterね。詳しくは準備編2を参照。 **B:** よっせ、っと。準備OKです。 **A:** さて、じゃあ早速いきますか。実験プログラムを書くときに、単に今何試行めであるとか、反応時間が何msであるとか、そういった値に何らかの演算をする必要が生じることは多い。 演算っていうのは、例えば値を加えたり割ったりという事だ。値や変数に対してこのような演算を行うための記号を演算子という。 **B:** うわ、いきなり抽象的な感じ。 **A:** 演算子には **算術演算子** と **比較演算子** ( **関係演算子** )、 **論理演算子** などがある。これは用途による分類だね。 演算子が作用する対象の数という観点からは **単項演算子** 、 **二項演算子** 、 **三項演算子** …といった分類ができる。 **B:** あの、すみません。さっぱりわかりません。作用する対象の数ってなんですか? **A:** まず最初に言葉を説明しておかないと、説明しにくいからな。じゃあ具体的な例から行こうか。B君が「これは演算子だと思う」ってのを一つ上げてみな。 **B:** えっと、値を加えるのは演算って言ってたから、「+」は演算子なんじゃないですか? **A:** 正解。この「+」はいわゆる算術的な演算をする演算子なので「算術演算子」、「+」という演算をするためにaとbという二つの対象が必要なので「二項演算子」という事になる。 **B:** ああ。演算の対象というのはそういうことですか。…あれ? 対象が一つしかない演算子とか、三つある演算子なんてありえるんですか? **A:** 単項演算子、すなわち対象が一つの演算子はB君もよく知っているものがあるはずだが。 **B:** ? **A:** たとえば-5の「-」はその直後にある「5」の符号を反転させる演算子だと言える。√は中に入っている数の平方根を得る演算子だと言えるな。 これらの演算子には対象が一つしかないから単項演算子ということになる。 **B:** あー。なるほど。 **A:** 他にも正弦関数sin(x)もxという値に作用してその正弦を返すという意味で、単項演算子の一種と考えることもできる。 **B:** 演算子って+や÷みたいな記号じゃなくてもいいんですか? **A:** 構わないさ。「+」を例にとって言えば、これはaとbという二つの引数を受け取って、aとbを加えた結果を戻り値として返す関数と解釈する事も出来る。「関数」の定義は覚えているか? **B:** むー。なんか屁理屈っぽいですが、そう言われると関数と呼んでも良いような気がします。 **A:** 実際、演算子と関数の間には密接な関係があって、pythonでは自分で新しいクラスを設計する時に、そのクラスのインスタンスに対して「+」という演算子がどういう演算に対応するのかをクラスのメソッドを使って定義する事が出来るんだ。 これを演算子のオーバーロ―ドという。雑な言い方をすれば、クラスと関数を使えば「+」という記号の意味をプログラマが自由に定める事が出来るんだよ。この辺りはいずれクラスの解説をするときにきちんと説明する。 **B:** 自由にって、「+」って書けば引き算になるようにすることも出来るですか? **A:** そんな事をして嬉しいかどうか知らんが、常識的に連想する演算とは全く異なる演算を割り振る事も出来る。まあ、ここでは演算子というのは関数と同じように、「評価」したら「戻り値」が得られるものだと理解してもらえばいい。 じゃあ用語はこのくらいにしておいて、具体的な演算子の解説をしようか。今回は以下の演算子を解説するぞ。 .. csv-table:: :delim: $ a = b$bをaに代入する(aは変数でなければならない) a + b$a足すb a - b$a引くb a \* b$aかけるb a / b$a割るb -a$aの符号反転(単項演算子) a % b$aをbで割った剰余 a \*\* b$aのb乗 a // b$a割るb (割り切れない時は切り上げ) **B:** a = bから-aまでは直感的に分かりますね。剰余ってなんですか? **A:** せっかくpythonインタプリタを立ち上げているのだからやってみたまえ。例えば5 % 3とか。 **B:** どれどれ。 :: >>> 5 % 3 2 **A:** 5を3で割ると商が1で余りが2。剰余は割り算の余りを求める演算子なので、この場合は2が返ってくる。4 % 2なら? **B:** ええと、割り切れるから余りは0ですかね。どれどれ。 :: >>> 4 % 2 0 この演算子、高校までの数学で習ってないと思うんですけどなんて読むんですか? あと何の役にたつんです? **A:** あー。プログラミング言語によっては剰余演算をa **mod** bと書くんで私はmodって読んでるけど、正直なところ正しい読みは知らない。 剰余計算は心理実験プログラムでは一定回数毎に休憩画面を入れるなどの処理の時に便利だな。現在の試行数がtという変数に入っている時、t % nはn試行に一回だけ0になる。だから、if文でt % nが0に等しいかどうかで処理を振り分ければ良い。 これは練習問題にしておこうかな。 **B:** ふーむ。そんな使い道があるんですねえ。 **A:** さて、これらの演算子はだいたい使い方もわかると思うが、二つ注意点を挙げておこう。まずB君、5 / 2の答えはいくらになると思う? **B:** 5割る2だから2.5じゃないんですか? **A:** そうかな? じゃあ試してみたまえ。 **B:** どれどれ。 :: >>> 5 / 2 2 あれ、答えは小数にならないんですね。 **A:** じゃあ5 / 2.0を計算してみなさい。 **B:** ん? 2.0にすると小数の答えが出るんですか? えーっと。 :: >>> 5 / 2.0 2.5 あらら。小数の答えが必要なら小数点付きの数で割らないといけないんですか。 **A:** 5.0 / 2でも同じ結果になる。pythonは式に現れる数が整数か小数かをきちんと区別していて、 **整数同士の除算や剰余では整数の範囲で答えを返す** ようになっているんだ。 **B:** 剰余? 余りも小数で返ってくるんですか? **A:** 疑問に思ったらすぐにインタプリタで試す! **B:** はいはい。えーっと。 :: >>> 5.0 % 2.3 0.40000000000000036 あれ? 2.3×2=4.6だから、余りは0.4だと思うんですが、なんか変なのがくっついてますね。 **A:** それは計算誤差という奴だ。コンピュータは0と1の世界だとか言った表現を聞いた事があるんじゃないかと思うけど、 これはコンピュータが2進数によって数を表現している事を意味している。で、小数の世界では2進数と10進数は完全に一致しないんだ。 例えば10進数では1÷10は0.1と有限の小数で表現できる。一方、2進数ではこの1÷10が循環小数になってしまう。10進数で1÷3が循環小数、すなわち0.3333333333333...となって有限の小数で表現する事が出来ないのと同じことだね。 まあコンピュータに無限の桁を扱う事が出来れば循環小数だろうがなんだろうが正しく計算できるんだが、残念ながらコンピュータは有限桁の数しか扱う事が出来ないので、どこかで打ち切らざるを得ない。その打ち切られたわずかな誤差がこうやって現れるんだ。 **B:** なんだ、コンピュータって計算が得意という印象があったんですけど、案外使えないんですね。 **A:** こういう誤差を可能な限り小さくするための方法はいろいろあって、某統計パッケージなどのバカ高いソフトウェアなんかは 計算誤差を小さくして信頼性の高い結果を出す事が出来る。信頼性のために高い金を払っているようなもんだ。 **B:** な、なんか怨念がこもってません? **A:** お金がなくて苦労してきたからねぇ。っていうか今も苦労の真っ最中だ。まあそれはさておき、pythonでもdecimalパッケージなどを用いると少々面倒だが高い精度で計算できる。こんな感じだ。 :: >>> from decimal import Decimal >>> Decimal("5.0") % Decimal("2.3") Decimal("0.4") **B:** いちいちDecimal(" ")って囲むんですか。面倒くさいですね。 **A:** まあpythonはもともと精密な数値計算をするための言語ではないからな。必要に応じて数値計算用のパッケージをimportすることになる。 python本体はコンパクトで、必要に応じてimportで拡張できるのがpythonのおいしいところだ。 **B:** 最初から何でも出来た方が面倒臭くないような。 **A:** 何でも出来ると言う事は、それだけ実行プログラムが巨大になったり、使用するメモリが増えたりするという事を意味する。何でも出来るのが良いとは限らないのさ。 **B:** はあ。そんなもんなんですか。 **A:** まあ、とにかく除算や剰余の時に整数として計算するのか、小数として計算するのかは、きちんと意識しておかないと思わぬエラーの原因となるから気を付けておくように。 **B:** はーい。 **A:** 小数の話が出たついでに、型変換の話もしておくか。pythonマニュアルにはこれも演算子に分類されている事だし。 これらはいずれも単項演算子と言うことになる。 .. csv-table:: :delim: $ abs(a)$aの絶対値 int(a)$aを整数に変換 long(a)$aを長整数に変換 float(a)$aを浮動小数点数に変換 **B:** なんか関数みたいですね。 **A:** 演算子と関数は似てるって言っただろ。 **B:** あの、長整数と浮動小数点数ってのがよくわからないんですが。 **A:** 長整数っていうのは、long integerの和訳だな。電卓で計算していて、計算結果が大きすぎるとエラーが出るだろう? それと同じ事で、コンピュータが扱える整数には桁の上限がある。2008年現在普及しているPCの多くは2進数で32桁が上限のものが多い。2の32乗、すなわち4294967296だな。 実際には負の数を表現する必要があるので-2147483648~2145483647だ。 **B:** げ、そんな数字暗記してるんですか。 **A:** オーバーフロー、つまり桁を超えてしまうと計算ミスが起こるからね。いろいろプログラムを書いているとこういう数字は覚えてしまうものだ。 心理実験ではあまりないと思うけど、この桁数より大きい数を扱わないといけない分野はいろいろある。そこで桁を増やした整数が必要になるんだが、その時に使う整数がlong intだ。 まあ、よほどの事がない限り使う事はないだろうな。 **B:** うへー。21億で足りないなんて。 **A:** 浮動小数点数というのは、コンピュータによる小数の表し方の一種で、「1.3723×10の-7乗」といった感じに小数を表現する。 後ろの「10の-7乗」の部分を-6乗とか-8乗にすれば小数点を動かす事が出来るので浮動小数点と呼ばれている。 **B:** えーと。10の-7乗ってなんですか。10を-7回掛ける??? **A:** 負の乗数を知らないのか。2の-1乗は1/2、10の-1乗は1/10。10の-2乗は1/10×1/10で1/100。 **B:** あー。そう言われると遠い昔に習ったような。 **A:** 心理学の論文を読む時にも出てくる場合があるから、数学の本を読んで勉強しておくように。1/2乗なんてのもあるぞ。 **B:** はーい。 **A:** プログラム中にa=5とかb=5.72のように数を直接書ける場合はこんな演算子は不要なんだが、すでに変数に放り込んでしまった値を小数にしたり整数にしたい場合がある。そう言う時にはこれらの演算子が便利だ。 **B:** あのー。整数を小数にするのは問題ないと思うんですが、小数を整数にしたら小数点以下の部分はどうなるんですか? あと、floatにはlongはないんでしょうか。 **A:** ふむ。なかなか良い質問をするね。まず小数を整数にする場合は、基本的に小数点以下は切り捨てられる。int(2.9)などを試してみるとわかるが、この場合は2が得られる。 切り上げをしたい場合はmathパッケージをimportしてceil()という関数を使うなどの方法がある。 **B:** またパッケージですか。 **A:** そう言いなさんな。あとfloatのlongはないのかという話だが、C言語などではfloatのlongにあたるdoubleという型が存在している。 けれどもpythonのfloatはもともとC言語のdoubleに相当する桁があるので、floatより桁が多い浮動小数点数は用意されていない。 **B:** 種類が少ないってのは楽でいいですね。C言語って面倒くさそう…。 **A:** さて、整数と実数の話はこのくらいにしておいて、二つ目の注意点にいこう。それは **演算子には優先順位がある** ということだ。3 - 4 \* 5はいくらだ? **B:** (わざわざ聞くと言う事は普通の計算とは違うのかな?) ええと、3-4で-1、5をかけて-5! **A:** ブー。正解は4\*5で20、3から20を引いて-17だ。 **B:** えーっ。騙された~。 **A:** 深読みしすぎだ。でもまあちょっといいポイントも突いているな。演算子には優先順位があって、以下の順に評価されていく。 #. -a #. a\*\*b #. a\*b, a/b, a%b #. a+b, a-b 同じ順位の場合は、左から評価されていく。今のB君の回答は、左から評価するというルールに則っているという点ではなかなか鋭い。 掛け算よりも引き算を優先したいなどの場合には、優先したい演算を( )でくくればいい。これは普通の計算と同じだね。 :: >>> 3-4*5 -17 >>> (3-4)*5 -5 ( )は重ねて使う事も出来る。この場合、一番内側の( )から計算していくことになる。まず2+3=5、それに5を掛けて25、15を引いて10、最後に3をかけて30だ。 :: >>> 3*((2+3)*5-15) 30 同じ順位の場合は左から評価されていくというのは、こういう例を見たらわかるかな? :: >>> 2*7%4 2 >>> 2*(7%4) 6 上の例では\*と%の優先順位が同じだから、左から評価してまず2\*7=14。続いて14%4で余りは2。下の例では( )でくくられている中をまず評価して7%4=3。2\*3で6というわけだ。 **B:** うーん、この優先順位というのも覚えておかないと間違いの元になりそうですね。 **A:** そうだな。もしくは優先順位に頼らずに( )で順番を明示するか、だ。 特に次回以降からいろいろな演算子が出てくるので、ややこしいと思った時は( )を書かなくても優先順位で正しく計算されるような式でも( )を書く事をお勧めする。 **B:** うはー。さらにややこしくなるんですか。 **A:** さて、最後にa=bの話もしておくかな。これは例題1でさんざん出てきたように、変数aにbを格納する。代入するとも言う。 今までの演算子は左側に3などの数が来ても構わないが、=は左側が変数でなければならない。3=1+2と書くと「3に1+2の結果を格納します」という意味になってわけがわからんからね。 **B:** ん? 3=1+2は「3は1+2と等しい」から正しいんじゃないんですか? **A:** こりゃまたありがちな勘違いだな。pythonの場合、=は「等しい」という意味には使わない。次回、比較演算子の話をするときに詳しく説明しよう。 **B:** ここでボケとかないと次回に話が繋がりませんからね。 **A:** ん? 何か言ったか? **B:** いや、別に。 **A:** まあいい。最後に = の話をしておこうと思ったのは、例題1-3でさらっと説明した省略記法をきちんと解説しておこうと思ったからだ。覚えているかい? **B:** ええと、なんでしたっけ。 **A:** a += bと言えばわかるかな? **B:** ああ、a = a + bをa += bと書けるんでしたね。他の演算子でも出来るんですか? **A:** 出来る。\*\*や%でも出来るぞ。 :: >>> a = 7 >>> a %= 4 >>> a 3 >>> a **= 2 >>> a 9 **B:** ええと、aに7を代入してるからa%4の答えは3、この3がaに代入されてa=3。さらにa\*\*2した答えは9、この9がaに代入されてa=9。 **A:** よしよし。 **B:** ええと、これって右辺も数式だったらどうなるのかな? どれどれ。 :: >>> a = 20 >>> a /= 2+3 >>> a 4 ええと、aに20が入っていて、答えが4になるんだから、まず2+3が先に計算されるんですね。/の方が優先順位が高いのに、+が先に計算されるのかあ。 **A:** /=の右側全体が( )に入っていると思えばいいよ。まず右辺を評価してから、/=の評価に入るんだね。 まあ省略形じゃない普通のa=3\*5とかいった表現ではまず右辺の計算が済んでからaに代入されるんだから、/=とかの評価も右辺の評価が済んでからなのは不思議でもなんでもない。 **B:** う~む。難しいなあ。 **A:** 繰り返しになるが、難しいと思った時は( )を使って明示的に計算順序を示すこと。さて、算術演算子の話はこれくらいにしておいて、 次は比較演算子と論理演算子という種類の演算子の話をする。こいつらをマスターしたら、実験プログラムを書く時の場合分けの自由度がぐっと広がる。期待しておくように。