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