ルギア君の戯言

雑多な記事。

第7章 箱の不思議と計算

キノガッサ「何を一生懸命集めているんですか?」
ルギア君「え?」
キノガッサ, ! (ファギクンジェルバ、セタミッセケボセ!)」


すると、ルギア君がかき集めていた小麦粉は消え、出てなくなったはずの小麦粉の箱から再び、別の場所に小麦粉がばらまかれた。


ルギア君「???」
キノガッサ「うふふ、不思議でしょ。」
ルギア君「うん・・・。」
キノガッサ「あっ、そうだ。この箱にもう少しリンゴをもらってきてよ。」
ルギア君「ああ・・・うん。」
キノガッサ「前にもらってきたリンゴも入ったままだから、中身はなくさないでね。」
ルギア君「わかった。」


ルギア君はリザードの部屋へいった。


ルギア君「リザード、この箱にリンゴを足してほしいって言っているんだけど・・・。」
リザード「そうか。じゃあ、どうすればいいか考えてみな。」
ルギア君「そのまま箱にいれるわけにはいかないの?」
リザード「うん。」
ルギア君「どうして?」
リザード「元からはいっている箱に新しいリンゴを入れたらもともと箱にはいっていたリンゴはなくなっちゃうんだ。」
ルギア君「へぇ・・・じゃあ、足すには別の魔法があるの?」
リザード「そういうことだ。」
ルギア君「どう言うの?」
リザード「ここに書いてある。」
ルギア君 − . (ギクンメセケボセ ゾダクセーガ ボジク)」
ルギア君「" (ゾダ)" がついたんだね。」
リザード「そうだ。」
ルギア君, , −−. (ファギクンジェルバ、ギクンメセケボセ、フォーディーンジェニー)」


すると、箱の中からちゃんと16個のリンゴが出てきた。


リザード「じゃあ、持っていってあげな。」
ルギア君「うん。」

まとめ

まずは箱の不思議についてまとめておきましょう。

  1. 箱からリンゴや小麦粉などを出しても中身はなくならない。
    • 本文では出てきていないが、出したものを別の箱に入れてももともと入っていたものは残ります。
  2. 箱に新しいリンゴや小麦粉をいれたら、元から入っていたものはなくなる。


これには当然わけがあります。


パソコンは計算機ですから当然数学的な話になります。難しい内容ではありませんが、覚悟して読んでください。


例えば、数学の世界では

x に 8 を代入する

という記述をしますね。


これとおなじことが C言語 の世界では

    x = 8;

なのです。


もし、不思議その2 がなかったら、数学の表現は

x に 8 を足す

と言わなければいけないはずです。


そしてそれは数式的には

x = x + 8 (x に x + 8 を代入する)

であり、

x = 8

ではありません。


このように見掛けと異なっては、誰もが間違えたり、注意しなければならないという、厄介なことになります。


C言語でも

    x = x + 8;

という記述をすることが可能です(後半で解説)。


が、今回は、

    x += 8;

という記述をリザードは言わせたようです。


これもおなじ意味になります。
キーボードを押す回数は少なくなりますが、上に比べると多少わかりにくいと思います。


それでは、その1の方も解説していきましょう。


数学の世界では

x に 5 を代入すると、y は ・・・ になる。

と書きますが、x に 5 を代入するということは、「x と 5 が同一のもの」になると言うことです。
この「x と 5 が同一のもの」であるという状態は、話が完結するか、x に別の値が代入されるまで将来に渡って有効です。
ですから、x の値を確認したり、x の値を用いて y の値を求めたときに、x の値が変わることはありませんし、変わってしまっては困ります。


それをパソコン上でもその1のようなルールとして再現したのです。




それではせっかく箱にリンゴを足すと言う話が出てきましたので、いろいろと計算してみましょう。


C 言語では 加減乗除だけでなく、いろいろな計算ができます。
それを含めた一覧表が以下の通りです。

記号 意味
* かけ算
/ わり算
+ 足し算
- 引き算
% 剰余算
& ビット論理積
¦ ビット論理和
^ 排他的論理和
~ ビット反転
++ 1を加算
-- 1を減算
>> 右シフト
<< 左シフト
+ 正の数を示すプラス記号
- 負の数を示すマイナス記号


それぞれの記号*1を ope *2とすると ope= もあり、それぞれ a ope= b が a = a ope b とおなじ意味になり、計算の方向が「右→左」になります。
つまり、

    a += b;
    a = a + b;
    c -= d;
    c = c - d;
    e *= f;
    e = e * f;
    g /= h;
    g = g / h;

1行目と2行目、3行目と4行目、5行目と6行目がそれぞれおなじ意味だと言うことです。


また

    a++;
    a = a + 1;
    a += 1;

この3行は全ておなじ意味です|。
大体は 一番上 が多用されます。



代入に関係する演算子です。

記号 意味
= (単純)代入
+= 加算代入
-= 減算代入
*= 積算代入
/= 除算代入
%= 剰余算代入
&= ビット論理積代入
¦= ビット論理和代入
^= 排他的論理和代入
>>= 右シフト代入
<<= 左シフト代入

++= や --= と ~= は作る意味がないのでありません。
++ や -- と ~ は 1 変数に対して作用する演算子ですから、作れないとも言えます。

関係演算子です。
数値を比較したりするときに使います。

記号 意味
== 等値 (代入と間違えないように!)
>= 以下 (右が小)
<= 以上 (右が大)
> 未満 (右が小)
〜を越える (右が大)
!= 等しくない
? : 条件演算子

二つの変数に対して作用する演算子は代入の類以外は単独で使っていも意味がありません。

   3 + 6;

って書いても、(場合によっては)パソコンは計算してくれますが、結果がどこにも残らないので、無意味な式になってしまいます。


論理演算子です。
数学にも論理は付き物。
よってプログラムにも論理は付き物。

記号 意味
&& 論理積 「かつ」
¦¦ 論理和 「または」
! 否定 「でない」


なんで「積」と「和」なんだろう・・・
なんとなくそんな感じはするけど。


その他の演算子
こんなものも C 言語では 演算子とみなされ(る場合があり)ます。

記号 意味
( ) 計算の優先順位変更、関数呼び出し、データ変換
[ ] 配列添字
sizeof( ) データのサイズを調べる
, 連文化、引数列、データ列

「計算の優先順位変更」は丸括弧 ( ) でしかできません。
つまり、丸括弧をさらに囲む場合も丸括弧で囲めばいいのです。


例えば、

    d = a * (6 + b * (7 + c));

とすれば

d=a \times \{6 + b \times (7 + c)\}

が表現できます。


最後に難しい用語がいくつか出てきましたね。確認しておきましょう。

剰余算

a % b と書かれたとき、a / b を一の位までわり進めた余りを算出する計算です。


例えば、

25 % 5 = 0
22 % 7 = 1
112200 % 500 = 200

となります。


実用的にも、一の位までの余りが求められればよいのですが、以下のようにすれば一の位以外の位での余りも求められます。


例えば、 22 \div 7を小数第一位までの余りを求めてみましょう。
小数第一位まで・・・ですから、22 を 10 倍すると、商が 10 倍されますね。そこで一の位までわり進めた上で余りを求めると、商が 31、余りは 3 になります。
商も余りもそれぞれ 10分の1 にすると 商が 3.1、余りが 0.3 と、 22 \div 7を小数第一位まで求めることができました。


ですから、プログラム上では、

    d = (22 * 10) % 7 / 10.0;

と書けばいいんですよ。
最後の ".0" には「リンゴ」を「小麦粉」に化けさせる効果があります。
そうしないと、小数が表現できませんよね。


そういえば 22 \over 7って昔に円周率として使ってた数字だよ。知ってた?

ビット論理積

正確には「ビットごとの論理積」と言います。


コンピュータは2進数の世界ですから本来数字は 0 と 1 しかありません。
したがって、数学での「真」を「1」、「偽」を「0」とすることができます。
また、コンピュータでは2進数の位取りをビットと呼びます。


論理積とは「かつ」のことですから、「ビットごとの論理積」を計算すると、それぞれの位での「かつ」が計算結果になるわけです。

      1111 0000 0101 1010
 AND) 1010 0101 0000 1111
-----------------------------
      1010 0000 0000 1010
ビット論理和

同様に「ビットごとの論理和」といいます。

論理和とは「または」のことです。

      1111 0101 0000 1010
  OR) 1010 0000 0101 1111
-----------------------------
      1111 0101 0101 1111
排他的論理和

これも正確には「ビットごとの排他的論理和」といいます。


これは真偽に問わず、互いに同じなら偽、違えば真となる不思議なものです。
まあ、「真」同士、「偽」同士は仲が悪いって言う感じですかね。

      1111 0101 0000 1010
 XOR) 1010 0000 0101 1111
-----------------------------
      0101 0101 0101 0101

XOR とは eXclusive OR 「排他的 OR」の略です。

反転

全てのビットの真偽をひっくりかえします。

 NOT) 1111 0101 0000 1010
----------------------------
      0000 1010 1111 0101

ですから、作用素がひとつしかありません。


C言語では 使いたい変数の前に付けます。


なお、++ と -- は前後どちらに付けても動きます(細かい動作が異なります)。

左シフト

全てのビットを左にずらして行くものです。


左からはみでたものは切り捨てて、右から 0 を足していきます。


0110 1100 1010 1101 << 2 = 1011 0010 1011 0100

右シフト

左シフトの逆なのですが、多少複雑なところがあります。


右を捨てることには変わりありませんが、左から入ってくる数字には場合によっては 0 でないことがあります。


ただの「リンゴの箱(整数型)」には、正の数も負の数も入れることができます。
この箱の場合、多くは、箱に入っている数字が正の数の場合には 0 が、負の数の場合には 1 が入ってきます。
この「多くは」って言うのがミソで、そうじゃない場合があります。
それは、C言語ではその部分が明確に定められていないからです。


しかし、細工をして正の数しかいれられないようにした「リンゴの箱」の場合は左から入ってくるのは必ず 0 です。


0110 1100 1010 1101 >> 2 = 0001 1011 0010 1011
(細工済の箱の場合)

等しくない

!= と言う記号は ≠ から連想されるものです。


¦= と言う記号が既にあります(ビットごとの論理和代入)ので、¦ ではなく ! を使ったわけです。

最後に

今回プログラムは省略しますが、いろいろ計算してみて計算結果を画面に表示してみてください。

    printf("%d", 4 * 2);

などのようにしても計算結果が表示されます。


C言語では 2進数 を表現するための機構がありませんので変わりに 16進数 を使います。

2進数 16進数 10進数
0000 00 0
0001 01 1
0010 02 2
0011 03 3
0100 04 4
0101 05 5
0110 06 6
0111 07 7
1000 08 8
1001 09 9
1010 0A 10
1011 0B 11
1100 0C 12
1101 0D 13
1110 0E 14
1111 0F 15

16進数であることをパソコンに伝えるためには、16進数の前に 0x を付けます。

    printf("%x", 0xAF50 << 2);

%d だと 10進数 で表示されてしまいますので 16進数 で表示したいときは %x を使います。
%X にすると、16進数 の英字部分が大文字になります。

*1: ++ と -- をのぞく。

*2:operator :演算子