C言語入門

1
C言語入門
第8週
プログラミング言語Ⅰ(実習を含む。),
計算機言語Ⅰ・計算機言語演習Ⅰ,
情報処理言語Ⅰ(実習を含む。)
2
前回の復習
3
訂正
continue 文、break 文によるループの再開と脱出
繰り返し(ループ)の復習
4
ループの再開と脱出
• continue 文
• while, do while, for 文内で使用可能
• 以降の処理を中断してループ末尾から再開する
• for文では後処理(第3パラメータ)も実行する
• break 文
• while, do while, for, switch 文内で使用可能
• 以降の処理を中断してループを脱出する
• switch文の場合はswitch文から脱出する
5
教科書 p.123.
後判定ループ (do while 文)
• 真偽値による繰り返し
continue
break
処理
do {
// 条件式 が真の場合の処理
} while (条件式);
真
条件式
偽
do while 文は、ループ内の処理を
最低1回は実行する。
6
教科書 pp.119-122.
前判定ループ (while 文)
• 真偽値による繰り返し
while (条件式) {
// 条件式が真の場合の処理
}
式2
偽
真
continue
処理
break
7
教科書 pp.124-129.
初期化・更新処理付きループ (for 文)
• 真偽値による繰り返し
式1
for (式1; 式2; 式3) {
// 式2が真の場合の処理
};
式2
偽
真
continue
前判定ループだが
式1による初期化と
式3による更新処理を
ひとまとめにして書ける。
処理
式3
break
8
教科書 pp.123-129.
for文とwhile文 (前判定ループ)
• 以下のループは等価
• continue時の式3の扱いに注意
for (式1; 式2; 式3) {
// 式2が真の場合の処理
};
式1;
while (式2) {
// 式2が真の場合の処理
式3;
};
式1
式2
偽
真
for文の
continue
処理
while文の
continue
式3
break
9
教科書 pp.123-129.
for文とwhile文 (前判定ループ)
• 以下のループは等価
• continue時の式3の扱いに注意
for (i = 0; i < 10; i++) {
// ループ内の処理
};
i = 0;
while (i < 10) {
//ループ内の処理
i++;
};
i = 0
i < 10
偽
真
for文の
continue
処理
while文の
continue
i++
break
10
教科書 pp.119-122.
後判定ループ (do while 文)
• continue 後の動作に注目
looptest_dowhile2.c
mintty + bash
int i = 0, n;
$ ./looptest_dowhile2
n = 1
1: 1st continue
fprintf(stderr, "n = ");
scanf("%d", &n);
do {
i++;
printf("%d: continue\n", i);
continue;
} while (i < n);
もしもcontinue 後に
i < n が判定されないなら
無限ループになってしまうが
そうはなっていないことに注目
11
教科書 pp.119-122.
後判定ループ (do while 文)
• continue, break 後の処理(iの値)に注目
looptest_dowhile.c
mintty + bash
int i = 0, j = 0, n;
$ ./looptest_dowhile
n = 10
0, 1: 1st 2nd 3rd
1, 2: 1st continue
1, 3: 1st 2nd 3rd
2, 4: 1st 2nd break
fprintf(stderr, "n = ");
scanf("%d", &n);
do {
j++;
printf("%d, %d: 1st", i, j);
if (j == 2) {printf(" continue\n"); continue;}
printf(" 2nd");
if (j == 4) {printf(" break\n"); break;}
printf(" 3rd\n");
i++;
} while (i < n);
mintty + bash
$ ./looptest_dowhile
n = 0
0, 1: 1st 2nd 3rd
do while 文は、
ループ内の処理を
最低1回は実行する。
12
教科書 p.123.
前判定ループ (while 文)
• continue, break 後の処理(iの値)に注目
looptest_while.c
mintty + bash
int i = 0, j = 0, n;
$ ./looptest_while
n = 10
0, 1: 1st 2nd 3rd
1, 2: 1st continue
1, 3: 1st 2nd 3rd
2, 4: 1st 2nd break
fprintf(stderr, "n = ");
scanf("%d", &n);
while (i < n) {
j++;
printf("%d, %d: 1st", i, j);
if (j == 2) {printf(" continue\n"); continue;}
printf(" 2nd");
if (j == 4) {printf(" break\n"); break;}
printf(" 3rd\n");
i++;
}
mintty + bash
$ ./looptest_while
n = 0
13
教科書 pp.124-129.
初期化・更新処理付きループ (for 文)
• continue, break 後の処理(iの値)に注目
looptest_for.c
mintty + bash
int i = 0, j = 0, n;
$ ./looptest_for
n = 10
0, 1: 1st 2nd 3rd
1, 2: 1st continue
2, 3: 1st 2nd 3rd
3, 4: 1st 2nd break
fprintf(stderr, "n = ");
scanf("%d", &n);
for (i = 0; i < n; i++)
j++;
printf("%d, %d: 1st",
if (j == 2) {printf("
printf(" 2nd");
if (j == 4) {printf("
printf(" 3rd\n");
}
{
i, j);
continue\n"); continue;}
break\n"); break;}
mintty + bash
$ ./looptest_for
n = 0
14
[1] p.281.
continue 文
• 以下のループ内に更に小さなループが含ま
れない場合の continue は goto contin と同義
for (...) {
// ...
contin: ;
}
do {
// ...
contin: ;
} while (...);
while (...) {
// ...
contin: ;
}
15
[1] p.281.
goto文
• 指定した名札付き文へ移動(ジャンプ)する
• 名札(label)は以下のように設定出来る
ラベル名: 文
goto 文は
余程理由がない限り使わないこと
do while 文相当
while 文相当
for 文相当
loop: ;
{
// something to do
contin: ;
}
if (expr) goto loop;
brk: ;
loop: ;
if (expr) {
// something to do
contin: ;
goto loop;
}
brk: ;
expr1;
loop: ;
if (expr2) {
// something to do
contin: ;
expr3;
goto loop;
}
brk: ;
16
暦(カレンダー)の計算
演習
17
月の日数
• 28,29,30,31日の月がある
• 2,4,6,9,11月が31に満たない月
• 憶え方:「西向く士」という語呂合わせが有名?
月 1 2 3 4 5 6 7 8 9 10 11 12
平年 31 28 31 30 31 30 31 31 30 31 30 31
閏年 31 29 31 30 31 30 31 31 30 31 30 31
18
紀元前の扱い
• 通常の紀年法(1オリジン:one-based)
• 天文学の紀年法(0オリジン:zero-based)
• 1オリジンだと紀元前の計算が面倒
• 暦の計算では天文学の紀年法である
Astronomical year numbering を用いると楽
通常の紀年法
天文学の紀年法
3 BC
-2
2 BC
-1
1 BC
0
AD 1
1
AD 2
2
19
第4週資料: p.39.
ファイルに分離した関数の利用
• 閏年判定は以前作った is_leap_year() 関数を
使おう!
is_leap_year_main.c
#include <stdio.h>
#include <stdlib.h>
#include "is_leap_year_func.h"
ヘッダファイルを includeして
関数の宣言を取り込む
int main()
{
int year;
printf("year = ");
scanf("%d", &year);
外部ファイルで定義した
関数を呼び出し
printf("%d is%s leap year.\n", year, is_leap_year(year) ? "" : " not");
return EXIT_SUCCESS;
}
20
第4週資料: p.40.
分割コンパイル
• コンパイルする複数のCのソースファイルを
コンパイラに与えれば良い
mintty + bash + GNU C
$ gcc
calendar_monthlen_1.c is_leap_year_func_4_2.c
コマンドプロンプト + Borland C++
>bcc32 calendar_monthlen_1.c is_leap_year_func_4_2.c
以下の2ファイルを
予め同じフォルダにコピーしておくこと
• is_leap_year_func_4_2.c
• is_leap_year_func.h
21
演習: calendar_monthlen_1.c
• Astronomical year numberingによるグレゴリ
オ暦year年month月における月の日数を表
示せよ(月の日数の後で改行すること)
• switch文を用いて場合分けせよ
• monthlen に適切な月の日数を代入せよ
• calendar_monthlen_tmp.c を元に指定
の位置に作成せよ。
22
ルックアップテーブル
• 場合分けが必要な値の変換は配列を用いる
とスッキリ書ける場合が多い
mdays with switch
switch (month)
case 2:
monthlen =
break;
case 4: case
monthlen =
break;
case 1: case
case 8: case
monthlen =
break
}
mdays with array
{
leap ? 29 : 28;
6: case 9: case 11:
30;
int daytab[2][13] = {
//0, 1, 2, 3, 中略, 12
{ 0, 31, 28, 31, 中略, 31},
{ 0, 31, 29, 31, 中略, 31},
};
monthlen = daytab[leap][month];
3: case 5: case 7:
10: case 12:
31;
23
演習: calendar_monthlen_2.c
• Astronomical year numberingによるグレゴリ
オ暦year年month月における月の日数を表
示せよ
• 配列によるルックアップテーブルを用いよ
• calendar_monthlen_1.cのswitch文をルック
アップテーブルで置き換える
• calendar_monthlen_tmp.c を元に指定
の位置に作成せよ。
24
演習: calendar_yearday_1.c
• Astronomical year numberingによるグレゴリオ暦
year年month月day日について年の通算日を表
示せよ
• 年の通算日の数え方は1月1日を1日目、12月
31日を356または366日目とする
• calendar_monthlen_2.cで用いたルックアッ
プテーブルを用いてfor文で積算せよ
• calendar_monthlen_tmp.c を元に指定の位
置に作成せよ。月の日数はyeardayに代入せよ。
25
演習: calendar_yearday_2.c
• Astronomical year numberingによるグレゴリオ暦
year年month月day日について年の通算日を表
示せよ
• 年の通算日の数え方は1月1日を1日目、12月
31日を356または366日目とする
• ルックアップテーブルを予め積算しておくことで
ループを用いずに計算せよ
• calendar_monthlen_tmp.c を元に指定の位
置に作成せよ月の日数はyeardayに代入せよ。
26
C言語における丸め
• 丸めとは?
• 小数点以下を処理して整数にする処理
•
•
•
•
切り上げ
切り捨て
四捨五入
等々
27
[1] p.315.
ceil 関数
• double ceil(double x);
• 天井関数(切り上げ): 𝑥
• 引数:
• x: 浮動小数点数
• 戻り値:
• x より小さくない最小の整数を返す
• 例: -0.5→ 0、0.5 → 1
• 要: math.h
28
[1] p.315.
floor 関数
• double floor(double x);
• 底関数(切り捨て): 𝑥
• 引数:
• x: 浮動小数点数
• 戻り値:
• x より大きくない最大の整数を返す
• 例: -0.5→ -1、0.5 → 0
• 要: math.h
29
int 型へのキャスト
• (int) x;
• 小数点以下が取り除かれる
• 元の値:
• x: 浮動小数点数
• 変換後の値:
• 0 への丸めとして働く
• 例: -0.5→ 0、0.5 → 0
30
各丸めイメージ
• ±での動作に注意
floor(x)
-1
ceil(x)
(int) x
floor(x)
(int) x
0
ceil(x)
1
31
round 関数
• double round(double x);
• 四捨五入関数
• 引数:
• x: 浮動小数点数
• 戻り値:
• x を四捨五入した整数を返す
• 0.5は0から遠い方に丸める
• 要: math.h (C99)
32
C89 での四捨五入
• C89 には round 関数がない
• 0.5 を足してから底関数を取れば良い
• 負の数の0.5をどちら方向に丸めるかには注意
0.5を正の無限大方向に丸める四捨五入
x = floor(x + 0.5);
0.5を0から遠い方に丸める四捨五入(C99 互換)
x = (int) (x + (x < 0 ? -0.5 : 0.5));
33
参考
IEEE丸め
• IEEE754 では以下の4つが定義されている
• 最近接偶数への丸め (RN: round to the nearest even)
• round 関数?ではない!
• 0への丸め (RZ: rounding toward zero)
• int 型へのキャスト
• 正の無限大への丸め (RP: rounding toward plus infinity)
• ceil 関数
• 負の無限大への丸め (RM: rounding toward minus infinity)
• floor 関数
34
参考
最近接偶数への丸め
• 0.5を丸める際、最近接偶数へ丸める
• 例: 0.5 → 0.0、1.5 → 2.0
• C99 では以下の方法を用いる
• fesetround 関数で丸めモードを設定
• nearbyint 関数で丸める
35
先発グレゴリオ暦
• グレゴリオ暦のルールですべての年月日を表
現する
• 実際にはグレゴリオ暦は1582年から施行された
• それ以前からグレゴリオ暦が施行されていたと仮
定した仮想の暦
36
先発グレゴリオ暦と閏日の回数
• n年1月1日以前に2月29日(閏日)は何回
あったか?(便宜上0年1月1日時点で0回と
する)
年
0
1
2
3
4
5
6
7
8
9
10
回数
0
1
1
1
1
2
2
2
2
3
3
年
98
99
100
101
102
103
104
105
106
107
108
回数
25
25
25
25
25
25
25
26
26
26
26
年
398
399
400
401
402
403
404
405
406
407
408
回数
97
97
97
98
98
98
98
99
99
99
99
37
先発グレゴリオ暦と閏日の回数
n/4.0 と比べてみる
n
0
1
2
3
4
5
6
7
8
9
10
n/4.0
0.00
0.25
0.50
0.75
1.00
1.25
1.50
1.75
2.00
2.25
2.50
年
0
1
2
3
4
5
6
7
8
9
10
回数
0
1
1
1
1
2
2
2
2
3
3
𝑛/4.0 を取れば良い事が分かる
𝑥 は天井関数で𝑥より小さくない最小の整数
100,400のルールも同様に考えれば答えは
𝑦/4.0 − 𝑦/100.0 + 𝑦/400.0
38
演習: calendar_rdleap.c
• Astronomical year numberingによるグレゴリ
オ暦year年1月1日以前に2月29日(閏日)は
何回あったか表示せよ
• 便宜上0年1月1日時点で0回とする
• calendar_rdleap_tmp.c を元に指定箇所
に作成せよ。変数は用意されている物以外使
わない事。計算結果はrdleepに代入せよ。
39
Fixed Day Numbers (RD: Rata Die)
先発グレゴリオ暦1年1月1日子午を1日目とし
た通算日
𝑅𝐷 = 365𝑦 + 𝑦/4 − 𝑦/100 + 𝑦/400
−366 + 𝑦𝑒𝑎𝑟𝑑𝑎𝑦
𝑥 は天井関数(𝑥より小さくない最小の整数)
C言語では ceil() 関数 (要: math.h)
Edward M. Reingold and Nachum Dershowitz.
Calendrical Calculations: The Millenium Edition
Cambridge University Press; 2nd edition (2001).
ISBN 978-0521777520
pp.11-18.
40
演習: calendar_rd.c
• Astronomical year numberingによるグレゴリ
オ暦year年month月day日はRata Dieの何
日目であるか表示せよ
• calendar_rd_tmp.cを元に指定箇所に作
成せよ。変数は用意されている物以外使わな
い事。計算結果はrdに代入せよ。
41
演習: calendar_wday.c
• Astronomical year numberingによるグレゴリ
オ暦year年month月day日が何曜日か表示
せよ
• 日月火水木金土は英語表記の頭文字を用い
てSu,Mo,Tu,We,Th,Fr,Saと表示せよ
• calendar_wday_tmp.c を元に指定箇所に作
成せよ。変数は用意されている物以外使わな
い事。日月火水木金土に対して0,1,2,3,4,5,6
の値をwday代入せよ。
42
演習: calendar_wday.c
• ヒント:
• RD を7で割った値はどうなるか?
• 例えば今日の日付で確認してみる
43
演習: calendar_1.c
• 1からnまでの整数を横方向に表示せよ
• 数値は幅3桁右詰で表示し、前後に空白を入
れないこと("%3d"を使え)
• 最後は改行すること
• calendar_1_tmp.cを元に指定位置に作成
せよ。変数は用意されている物以外使わない
事。 n=19 の例
$ ./calendar_1
n = 19
1 2 3 4 5
$
6
7
8
9 10 11 12 13 14 15 16 17 18 19
44
演習: calendar_2.c
• calendar_1.c を改造し7日毎に改行せよ
• calendar_2_tmp.cを元に指定位置に作成
せよ。変数は用意されている物以外使わない
事。
n=31 の例
$ ./calendar_2
n = 31
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
$
45
演習: calendar_2.c
• ヒント:
• calendar_1.cから、使える変数が1つ増えてい
ます(変数w)。この変数をどう使うかがポイントで
す。
• w をカウンターとして利用して、dayを7回表示す
る毎に条件分岐して、改行させれば目的を達せ
出来るはずです。
46
演習: calendar_3.c
• calendar_2.c を以下のように改造せよ
• 最初の行の表示を変数wdayに格納された数値
の数だけ3ケタの空白("
")を表示した後、1
日目を表示し始めるようにせよ
• その際、1行目は7-wday日目で改行し、以降7
日毎に改行せよ(例参照)
n=31, wday=2 の例
• calendar_3_tmp.cを元に
$ ./calendar_3
指定位置に作成せよ。
n = 31
変数は用意されている物以外 wday = 21 2 3 4 5
6 7 8 9 10 11 12
使わない事。
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
$
47
演習: calendar_3.c
• ヒント:
• "
"(空白3つ)を表示するループをwday回実
行した後に、calendar_2.c で作成した処理を
実行すれば良いでしょう。
• "
"を表示するループでwをforループのカウ
ンタとして用いると、calendar_2.cの処理と上
手く整合性が取れるでしょう。
48
演習: calendar_4.c
• Astronomical year numberingによるグレゴリ
オ暦year年month月についてカレンダーを表
示せよ
• 週の始まりは日曜日とする
• calendar_4_tmp.cを元に
例
指定位置に作成せよ。
$ ./calendar_4
= 2014
変数は用意されている物以外 year
month = 6
1 2 3 4 5 6 7
使わない事。
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
$
49
演習: calendar_4.c
• ヒント:
• 以下の結果を利用すれば出来るはずです。
• calendar_monthlen_2.c
• calendar_wday.c
• calendar_3.c
• calendar_3.cでは日付を表示するループで上
限の条件指定にnを用いていましたが、
calendar_4.cでは使えなくなっています。代わ
りにdaytabから月の日付を取って来ましょう。
50
ユリウス日(JD: Julian Day)
• ユリウス暦の紀元前4713年1月1日正午を0
日とした経過日数
• 各種暦の変換に用いられる
• Wikipedia
• ユリウス通日
• Julian day
51
ユリウス日0日
• ユリウス暦だと
• 紀元前4713年1月1日正午
• -4712年1月1日正午
• グレゴリオ暦だと
• 紀元前4714年11月24日正午
• -4713年11月24日正午
52
ユリウス暦とグレゴリオ暦
• ユリウス暦
• 閏年は4年に1回
• グレゴリオ暦
• 閏年は400年に97回
• Tropical year(太陽年、回帰年)
𝑡𝑎 = 365.2421896698 FITS Time WCS Draft
−6.15359 × 10−6 𝑇 Ver.0.90
McCarthy & Seidelmann,
−7.29 × 10−10 𝑇 2
2009, p. 18.; Laskar, 1986
−10
3
+2.64 × 10 𝑇
• 𝑇 はユリウス世紀数
J2000.0(2000年1月1日)起点のユリウス世紀(36525日)
53
ユリウス暦
• 紀元前45年1月1日から運用開始
• 閏年は4年に1回
• 8 BC まで 3 年に 1 回の閏年(運用の誤り)
• 6 BC から AD 7 までは閏年を停止(補正のため)
• AD 8 以降は 4 年に 1 回の閏年
• 以下も参考になる
• 国立天文台 / 暦計算室
• トピックス / 1月1日あれこれ
• 暦Wiki / 曜日の始まり
結局この辺りは古い話なの
で実際の暦がどうなってい
たかは良く分からない。
実際、フリーゲルの公式で
は運用ミスは考慮されてい
ないし、あまり深入りしない
方が良さそう
54
ローマ・カトリック教会
ユリウス暦からグレゴリオ暦へ
ユリウス暦1582年10月4日(木曜日)
↓翌日
グレゴリオ暦1582年10月15日(金曜日)
グレゴリオ暦では閏年が4年に1回だったため春
分が3月21日前後から10日程度ずれてしまって
いたのを修正することが目的
ただし宗派により実施時期が異なる
55
日本
天保暦からグレゴリオ暦へ
明治5年(1872年)12月2日
↓翌日
明治6年(1873年)1月1日
政府財政が逼迫する中、明治6年は閏月があるた
め、公務員の月給を13回支払う必要があった。 欧
米と共通の暦を採用するついでに、 12月を2日で
切り上げることで12月と閏月、計2ヶ月分の月給を
カットも狙った。
ただし当時の日本は西暦ではく皇紀(西暦+660年)
ゼロ戦 = 零式艦上戦闘機 = 皇紀2600年式艦上戦闘機
= 西暦1940年式艦上戦闘機
56
修正ユリウス日(MJD: Modified JD)
• 1858年11月17日子午を0日とした経過日数
• MJD = JD - 2400000.5
57
フリーゲルの公式
int型で計算すると
底関数を取る前に
整数になってしまう
ので注意
修正ユリウス日を求める公式
1,2月を前年の13,14月として扱うと
グレゴリオ暦𝑦年𝑚月𝑑日に対して
𝑀𝐽𝐷 = 365.25𝑦 + 𝑦/400.0 − 𝑦/100.0
+ 30.59 𝑚 − 2 + 𝑑 − 678912
ユリウス暦𝑦年𝑚月𝑑日に対して
𝑀𝐽𝐷 = 365.25𝑦
+ 30.59 𝑚 − 2 + 𝑑 − 678914
⌊𝑥⌋は底関数(𝑥を超えない最大の整数)
C言語では floor() 関数 (要: math.h)
58
演習: calendar_mjd.c
• グレゴリオ暦y年m月d日の修正ユリウス日を
フリーゲルの公式を用いて計算し表示せよ
• calendar_mjd_tmp.c を元に指定の位置
に作成せよ。変数は用意されている物以外使
わない事。
59
参考文献
• [1] B.W.カーニハン/D.M.リッチー著 石田晴久
訳、プログラミング言語C 第2版 ANSI 規格準
拠、共立出版(1989)