基本的なデータ処理

数値変数・数式・書式付き入出力の適切な使い方を理解しよう. そして,超単純な数値データ処理プログラムを作成してみよう.

教科書の該当範囲:第1章〜第2章

準備

ディレクトリの準備

まずは,作業用ディレクトリを準備しよう:

$ cd
$ mkdir c-0507
$ cd c-0507
# または
$ cd !$
エディタの選択

前回は Vim を使ってみましたが,気に入らない場合, 別のエディタを使って構いません.

選択肢:vim,nano,emacs,gedit,等...

使用方法:

$ vim ソース.c
$ nano ソース.c
$ emacs ソース.c
$ gedit ファイル.c &	# GUIアプリの場合「&」が必要
テンプレートの準備

今後の作業を効率化するため, ソースファイルのテンプレートを ホームディレクトリ等に作成しておこう:

$ vim ~/tmpl.c	# エディタは vim 以外でも OK
		# 「~/」はホームディレクトリを意味する特殊記号
#include <stdio.h>

int main(void)
{

	return (0);
}

ソースファイルを新規に作成する際, このテンプレートをコピーして使えば良い:

$ cp ~/tmpl.c 新ソース.c	# ファイルのコピー
$ vim 新ソース.c
...

これで毎回,同じようなコード (#include とか main とか)を 書き直す手間を節約できる.


本日の作業

タスク1:C言語プログラミングの作業手順を確認せよ.

テンプレートを元にして,ソースファイルを編集:

$ cp ~/tmpl.c hello.c
$ vim hello.c
#include <stdio.h>

int main(void)
{
	printf("Hello World\n");	// 「Hello World」と表示
	return (0);
}
ソースコードの「//」から右側はコメント.入力不要ですよ.

ソースをコンパイルして,実行形式(プログラム)を生成:

$ cc hello.c -o hello
注意しよう.「cc hello.c -o hello.c」は危険. ソースファイルが上書きされ,消滅してしまう. もし心配 or 面倒なら「cc hello.c」だけでも OK. 実行形式のファイル名は自動的に a.out になる.格好悪いけど.
もし,エラーが発生したらデバッグ(ソースコードの修正)も必要.

実行形式が生成されたか?確認:

$ ls
hello	hello.c	
もし,実行形式 hello が無ければ, コンパイルに失敗している.デバッグ!!

プログラムを実行:

$ ./hello	# プログラムを実行
Hello World
$
タスク2:データ処理プログラムの基本形を作成・理解せよ.
Step 0. プログラムの基本構成を理解する.

データ処理プログラムの段取りの基本形を理解しておきましょう. (データ処理に限らず,大抵のプログラムは3段階から構成されます.)

  1. 前処理: 目的とする仕事を始めるための準備に相当する部分. データ処理の場合,データの入力など.
  2. 本処理 or 主処理: 仕事の本体に相当する部分. 数値計算など.
  3. 後処理: 仕事を完了するための仕上げ・片付けに相当する部分. 計算結果の出力など.
基本は3段階だが,複雑なプログラムでは, 各段階がさらに3段階,その各段階がさらに3段階, そのまた各段階がさらに... となる.
とにかく仕事には,準備と始末が伴う. 実世界と同じで,特に始末が重要. 始末なしでも完成したように見える場合もあるが, そのせいで致命的欠陥に気づかなかったりもする. (スタート直後は正常に動作して安心させておいて, 油断した頃に異常が発生する.)
プログラムによっては,前処理が不要とか後処理が不要など, 2段階や1段階の場合もある. よくある1段階のプログラムの例は 上の Hello World は1段階の例ね. まあ,こいつらも3段階パターンの一種だと思ってください.

では,3段構成を意識しながら, 具体例として掛け算プログラム mul.c を作成してみよう.

tmpl.c を元にして編集.
Step 1. 前処理部を作成・理解する.

前処理部のソースコード断片:

#include <stdio.h>	// 入力関数 scanf() の宣言を取り込み
		// 「後で scanf() を使うよー」とヘッダファイル stdio.h 内に書いてある
		// ...前処理部の準備

int main(void)
{
	int	num;	// 整数型の変数 num を宣言
		// 「後で num を使うよ,整数を代入できるよー」
	double	val;	// 実数型の変数 val を宣言
		// 「後で val を使うよ,実数を代入できるよー」

	// 前処理部
	printf("整数と実数を入力せよ >");	// 入力プロンプトを表示
	scanf("%d %lf", &num, &val);
		// 「最初に %d で整数,次に空白,最後に %lf で実数,
		// というパターンでキーボードから入力してねー」
		// 「変数 num に整数,val に実数が格納されるよー」
		// 変数名の前「&」の付け忘れに注意!!

	// 本処理部
	...	// この先は次のステップで作成
}

基礎知識:

Step 2. 本処理部を作成・理解する.

本処理部のソースコード断片:

...
int main(void)
{
	...
	double	total;	// 「後で計算結果を代入するよ」

	// 前処理部
	...

	// 本処理部
	total = num*val;
		// かけ算 num*val を計算し,計算結果を変数 total へ代入してね.

	// 後処理部
	...
}

基礎知識:

Step 3. 後処理部まで作成・理解し,プログラムを実行する.

後処理部のソースコード断片:

#include <stdio.h>	// 出力関数 printf() の宣言を取り込み
		// 「後で printf() を使うよー」とヘッダファイル stdio.h 内に書いてある
		// ... 後処理部の準備

int main(void)
{
	...

	// 後処理部
	printf("%d * %f = %f\n", num, val, total);
		// 「文字や数値を端末画面に表示してねー」
		// 「%d は整数 num,%f は実数 val とtotal の値に置き換わるよー」

	return (0);	// プログラムを終了
}

基礎知識:

後述しますが,printf()scanf() の書式について, 同じ実数型であっても %f%lf のように, 微妙な違いがあることに注意しよう.

はい,ではこのプログラムをコンパイル・実行しましょう:

$ cc mul.c -o mul
$ ./mul
...

正しく掛け算できたでしょうか?

タスク3:整数データの性質を理解せよ.

整数型(int型)データの1個あたりの情報量は, 4 byte(16進数8桁)=32 bit(2進数32桁)であり, 表現可能な数値の範囲は 0 〜 232ー1 ≒ 約40億となります. ただし通常は,負の数も使うので, 範囲はー約20億 〜 +約20億となっています.

「int」は「integer(整数)」の略語.
実際のデータサイズはシステム依存であり, int が 4 byte ではないコンピュータもある.

ソースファイルを int.c としましょう:

今度は tmpl.c ではなく, mul.c を元にすると楽.
#include <stdio.h>

int main(void)
{
	int	x, y, z;		// 整数型の変数 x,y,z の宣言

	while (1) {			// 繰り返し
		printf("整数2個 > ");
		scanf("%d %d", &x, &y);		// 入力
		z = x * y;				// 計算(かけ算)
		printf("%d * %d = %d\n", x, y, z);	// 出力
	}
	return (0);
}

コンパイルと実行:

$ cc int.c -o int

$ ./int
整数2個 > 35 100
35 * 100 = 3500			# そだねー

整数2個 > 3500 1000000
3500 * 1000000 = -794967296	# えっ??
	# 正解は 35億では?しかも,マイナスって何だー?

整数2個 > [Ctrl]+[C]	# 強制終了

はい,今,オーバーフローが発生しました. 「コンピュータは正確」と思っていた人は残念でした. コンピュータの能力には限界があります. 能力の範囲内であれば正確です.

また,コンピュータの間違いには再現性がある. 同じ間違いなら,いつも同じ結果になる. 人間の間違いのようなムラはない.
正数のハズがなぜ負数になるのか? 31bitまでに収まらずに32bit目に溢れると負になる. 32bitを超える部分は無視される.
タスク4:実数データの性質を理解せよ.

実数型(double型)のデータ1個あたりの情報量は 8 byte, 数値範囲は±約1.0×10±308, 有効数字は15桁となっています.

double は double precision(倍精度)を意味するんだが... これだけだとわけわからんよね... 元々,単精度 4 byte の float があり, これが基本の実数型 floating point number(浮動小数点数)だった. 現代では,float じゃ精度が足りないし,メモリが安価で大容量になったので double が主流となっている. それでもやはり限界はあることに変わりはない.

ソースファイルを double.c としましょう:

今度は int.c を元にしよう.
#include <stdio.h>

int main(void)
{
	double	x, y, z;		// 実数型の変数 x,y,z の宣言

	while (1) {
		printf("実数2個 > ");
		scanf("%lf %lf", &x, &y);	// 入力 
		z = x / y;				// 計算(わり算)
		printf("%f / %f = %f\n", x, y, z);	// 出力
	}
	return (0);
}
$ cc double.c -o double
$ ./double
実数2個 > 1 3
1.000000 / 3.000000 = 0.333333

実数2個 > 2.0 1.41421356
2.000000 / 1.414214 = 1.414214

実数2個 > 1 0
1.000000 / 0.000000 = inf	# inf は infinity...無限ね

実数2個 > 0 0
0.000000 / 0.000000 = -nan	# nan って何?not a number...不定値,非数

...
数学的に... 任意の数 a に対して,a ✕ 0 = 0 が成り立つ. この式を変形すると,a = 0÷ 0 となる. つまり,0 ÷ 0 の結果はどんな数にでも一致することになり, 1つの数には定められない.
タスク5:入出力の書式指定の方法を理解せよ.

実験:書式指定をわざと間違えてみよう. ソースファイルは fmt.c

#include <stdio.h>

int main(void)
{
	int	x = 123;
	double	y = 123.45;

	printf("正:\n");
	printf("x = %d\n", x);
	printf("y = %f\n", y);

	printf("誤:\n");
	printf("x = %f\n", x);	// 整数型は %d のハズ
	printf("y = %d\n", y);	// 実数型は %f のハズ

	return (0);
}

コンパイルすると,どうなりますか?

実行結果は,どうなりましたか?

処理系によって挙動が異なるようです. コンパイル時のwarningだと実行できてしまうけど, 結果が常に正しいとは限らない. (正しい結果が出てしまう場合もある.)

さらに実行を繰り返すと?

この意味不明な挙動は, コンピュータ側の間違いではなく, プログラマ(人間)側の間違い. コンピュータは(人間の思い通りではなく)書いた通りにしか動かない. 書かれていなければ,ゴミをそのまま使ってしまう. ゴミが嫌なら,最初に掃除を指示すべき.
タスク6:変数の初期化の必要性を理解せよ.

変数の値を使用して何かを計算する場合, 事前に初期化(初期値の代入 or 入力)が必要です.

実験:変数をわざと初期化せずに使ってみよう.var.c

#include <stdio.h>

int main(void)
{
//	int	x=1, y=2, z=3;	// 初期化すると...
	int	x, y, z;		// 初期化しないと...

	printf("%d %d %d\n", x, y, z);	// どんな値?
	return (0);
}

実行結果はどうなりましたか? 何度も繰り返すと?

タスク7:基数変換(10進→16進)プログラムを作成せよ.

ここまでの知識を使って, 整数を 10進数(decimal)として入力すると 16進数(hexadecimal)に変換して出力するプログラム d2x.c を独自に作成しよう. 本日の課題の重要部分です.

ヒント: 何かを計算させる必要はない. 表示形式を変えるだけで済む.

実行例:

$ ./d2x
10進数 > 255
16進数 = FF

10進数 > 16
16進数 = 10

...
10進数 > [Ctrl]+[C]	# 強制終了
$
ヒント: ソースファイルの基本形は int.c
タスク8:基数変換(16進→10進)プログラムを作成せよ.

タスク7の逆バージョン... 整数を 16進数として入力すると 10進数に変換して出力するプログラム x2d.c も作成しよう.


本日の課題

レポートを提出せよ.

質問 Q1〜Q6 に回答し,電子メールで提出せよ.