遊んでたら出来たので書く。C言語初学者に重要なエッセンスがあったりなかったりする(知らんけど(責任逃れ))
プログラム
解説
#include
- stdio.h ... いつもの
- stdlib.h ... 乱数界隈に必要
- time.h ... 時間界隈
#define
- pow2(x)
#define pow2(x) ((x)*(x))
xの2乗を返す関数形式マクロ。2のx乗ではない。雑な命名許してクレメンス。
プログラム中のpow2(x)
は、x
を変数としてすべからく((x)*(x))
に変換される。別に
int pow2(int x){ return x*x; }
としてもいいのだが無駄だし、いちいち呼び出すほどではない。ということでよく使われる[要出典]関数形式マクロである。
- pow3(x)
#define pow3(x) ((x)*(x)*(x))
xの3乗を返す関数形式マクロ。
プログラム中のpow3(x)
は、x
を変数としてすべからく((x)*(x)*(x))
に変換される。
- TRY 10
#define TRY 10
試行回数を定数にしておく。書き換えればいくらでも(?)増やせる。
自作関数 mazai_susiki
char mazai_susiki(int x){ double a, b, c; a = pow3(x)*13/6.0; b = pow2(x)*17/2.0; c = x*43/3.0; return (char)(a-b+c+97); }
これは単純に言えば、数式
を示している。x
に0から3を入れた時のy
の値は以下の表のようになる。さらにそれぞれASCIIコードで変換すると
x | 0 | 1 | 2 | 3 |
---|---|---|---|---|
y | 97 | 105 | 109 | 122 |
char | a | i | m | z |
となる。
つまり、x
にランダムに0~3を入れるとa, i, m, z
が返ってくるわけで、5回やって全てうまくいけばmazai
*1を出力することができるということだ。
mazai
出力に成功する確率は、(0~3の出かたが「同様に確からしい」として)中学生でもわかるとおり
である。
ポイント
(今のところ)この関数で重要なのは、double
a, b, c; a = pow3(x)*13/
6.0;
である。
ところどころ省くが、C言語の規則として細かい方の型に揃える(雑!)というのがあるので、こう書いておくとdouble型の6.0に引っ張られてa
に代入される値はdouble型で計算される。ここを6(.0なし)にしてしまうとすべてint型で処理されるのでa
に代入される値もint型の範囲になる。ちなみにa, b, c
の計算結果を、double型キャストありとなしで比べると以下のようになった(桁数は環境依存か?)。(a, b, c
はdouble型なので代入する値がintでもa, b, c
では.000000
がつく)
x | 0 | 1 | 2 | 3 |
---|---|---|---|---|
double | ||||
a (~/6.0) | 0.000000 | 2.166667 | 17.333333 | 58.500000 |
b (~/2.0) | 0.000000 | 8.500000 | 34.000000 | 76.500000 |
c (~/3.0) | 0.000000 | 14.333333 | 28.666667 | 43.000000 |
a-b+c | 0.000000 | 8.000000 | 12.000000 | 25.000000 |
int | ||||
a (~/6) | 0.000000 | 2.000000 | 17.000000 | 58.000000 |
b (~/2) | 0.000000 | 8.000000 | 34.000000 | 76.000000 |
c (~/3) | 0.000000 | 14.000000 | 28.000000 | 43.000000 |
a-b+c | 0.000000 | 8.000000 | 11.000000 | 25.000000 |
intで計算してしまうと、いろんなところの値がdoubleで計算した実際の値とずれている(太字)。大体はなんとか誤魔化せているがx=2
の時に計算結果がずれてしまう(太字下線)。
書くのを忘れていたのでちょこっと書くが、a, b, c
の型はdoubleでなくてはいけない。intで宣言すると、/6
と同じことになってしまう(せっかく小数点以下まで計算しても代入する時に切り捨てられてしまうので)。
計算式をこねくり回して切り捨て具合(?)を調整してもいいのだが説得力に欠けるのでやめたほうがいい[要出典]。実際、
int a, b, c; a = pow3(x)*13/6; b = pow2(x)*17/2; c = (x*43+1)/3;
としても
a-b+c = {0, 8, 12, 25}
の欲しい結果は出る。
ちなみに
先頭のchar
について。
計算しただけだとdouble型の変数なのだが、この関数はchar型を返すと決めているのでchar型で返さなくてはならないからchar型で返す(小泉進次郎構文)。
プログラムで使う文字は、見た目はa
とかb
とかの「文字」なのだが、パソコン内部では97
とか98
という風に数字で処理されている。これを定めているのがASCIIコード表である。半角英数くらいの文字と記号とか(省略)はそこに記述されている。
#include <stdio.h> int main(){ char c; c = '1'; printf("%c, %d\n", c, c); //1, 49 c = 64; printf("%c, %d\n", c, c); //@, 64 c = 'A'; printf("%c, %d\n", c, c); //A, 65 c = 97; printf("%c, %d\n", c, c); //a, 97 return 0; }
こういうのをやると実感できる。
試したところ、数字を返していればmainの中のputchar()
で勝手にchar型にキャストするようで、double mazai_susiki()
としてdouble型のまま送りつけても怒られなかった。まあ、charと明示しておいたほうが良いのでは(丁寧で良いと思う)。配列を投げたら怒られるだろうけど(試していない)。
returnも最初return (char)(a-b+c+97);
とご丁寧にキャストしていたがそれもいらないようだ。return a-b+c+97;
でOK。
パワーアップ
勘の良い方はお気づきかと思うが、先ほどの表をよくみると、double型で計算したa-b+c
の小数点以下は.000000
である。今回使っている数式
は、を通る3次曲線をスマホの関数電卓アプリにはじき出させて得られたものなのだが、実はこいつx
にどんな整数を入れてもy
は整数になるのだ。例の式を変形すると(また雑な書き方だが)
となる。残念ながらこれ以上因数分解とか出来ないのだが、を代入すると分かるが右辺が見事に6の倍数になる。(もっとエレガントな方法あったりするんかな?)
というわけで、わざわざdouble型を使わなくても良いのだ。
パワーアップ(というかコンパクト化)した関数mazai_susiki_kai
(魔剤数式改)がこちら
char mazai_susiki_kai(int x){ int ans = (13*pow3(x)-51*pow2(x)+86*x)/6; return ans + 97; }
中身をmazai_susiki
ととっかえるか、これをプログラムの上の方(main関数よりは上)に書き足してmain関数内のmazai_susiki
をmazai_susiki_kai
に書き換えると良い。これならdouble型の心配をせずにmazai
出力チャレンジができる。素晴らしい!なお他の文字を出したい時はそううまくいくとは限らないはずなのでdouble型を使うあたりの話は覚えておいて損はない。
main関数
int main(){ srand((unsigned)time(NULL)); int i, j; for(i=0; i<TRY; i++){ for(j=0; j<5; j++){ putchar( mazai_susiki( rand()%4 ) ); } putchar('\n'); } return 0; }
ここで#includeが役に立ってくる。
時間界隈
まずはtime.h
をインクルードした理由から。それは1行目のtime()
関数を使うのに必要なため。詳しくは省略するがtime(NULL)
とやると呼び出した時の時間が返ってくる(めっちゃ雑)。
乱数界隈
ここではstdlib.h
が使われる。
rand()
これは乱数を生成する関数。どういう仕組みか知らんがrand()
を呼び出すたびに乱数を返してくれる。
srand()
上のrand()
を使う前に、シード(種)値x
をsrand(x)
として入れる。しかしx
が固定だと、rand()
呼び出しn回目の値はこれ、という風に定まってしまう。これまた環境依存だと思うが、srand(2020)
として実行すると、何回やっても
aimma mzzaa mimiz izzmz zzizi aiaaa mzmzm zizii zazia mzzmi
になってしまった。そこで、x
の値を(unsigned)time(NULL)
とすることで、プログラムを実行した時間に応じて毎回バラバラのシード値が入るので「面白い」プログラムになる。(unsigned)
は型変換。
その他
残りは、ループ回しているだけである。最初に定義した定数TRY
を試行回数としてi
のループで使っている。
終わり。
まとめ
最初はただバカなプログラムを書いて遊ぶだけのつもりだったが、なんか面白いことに(数式あたり)なったのでブログにまとめてみた。
つかれた。おしまい。