● 実習内容
【課題】
1.
ユークリッドの互除法のプログラムを書きなさい. 雛形のプログラムはprog07.c.
2.
拡張されたユークリッドの互除法のプログラムを書きなさい. すなわち,正の整数a, b を与えたときに,
ax+by= gcd(a, b), aw+bz= 0
を満たす整数の組(x, y), (w, z)をそれぞれ一組求めるプログラムを書きなさい.
3.
2進互除法のアルゴリズムをプログラムしなさい.
電子メールで「今日の講義の感想や意見」を送ってください.
★
prog07.c
の内容
/* prog07.c */
/* ユークリッドの互除法 */
#include <stdio.h>
int a, b ;
int main(int argc, char **argv) {
a = 125 ; b = 315 ;
printf("gcd(%d,%d) = ", a,b) ; /* この部分で互除法の計算を行う */
return 0 ; }
● 前回の課題の解説
西暦1900年から西暦2003年までの閏年をすべて出力するプログラムを書きな さい.
/* prog06-1.c */
/* 西暦1900年から西暦2003年までの
* 閏年をすべて出力するプログラム
*/
#include <stdio.h>
int year ;
int main(int argc, char **argv) {
for(year=1900;year<=2003;year++) {
if ((year%4 == 0)&&((year%100 != 0)||(year%400 == 0))) { printf("%d\n", year) ;
} }
return 0 ; }
注意1:このようなプログラムでは,「1900」と「2003」という, 対象となる「定数」がプログラ ム内に明示的に書き込んであると,プログラムの意図が明確になる.
C言語の場合には,
for(year=1900;year<2004;year++)
でもある程度意図は伝わるだろう.
注意2:このプログラムでは,変数は「西暦の年数」という明確な意味を持った数値が代入されるので,単 に変数名をiなどとするのではなく,yearという変数名にして,変数の値の意味がわかりやすくすること が望ましい.
注意3:このプログラムで,
(year % 4 == 0)&&((year % 100 != 0)||(year % 400 == 0)) を
(year % 4 == 0)&&(year % 100 != 0)||(year % 400 == 0) と書くと,
prog06-1.c:13: warning: suggest parentheses around && within ||
という「警告」がでる.
for文,while文,do文,それぞれを用いて, 1から10までの和を計算するプログラム を書きなさい.
/* prog06-2-1.c */
/* for 文で 1 から 10 までの和を計算するプログラム */
#include <stdio.h>
int i, j ;
int main(int argc, char **argv) {
j = 0 ;
for(i=0;i<10;i++) { j += i+1 ; }
printf("%d\n", j) ; return 0 ;
}
/* prog06-2-2.c */
/* while 文で 1 から 10 までの和を計算するプログラム */
#include <stdio.h>
int i, j ;
int main(int argc, char **argv) {
i = 0 ; j = 0 ; while(i<10) {
j += i+1 ; i += 1 ; }
printf("%d\n", j) ; return 0 ;
}
/* prog06-2-3.c */
/* do 文で 1 から 10 までの和を計算するプログラム */
#include <stdio.h>
int i, j ;
int main(int argc, char **argv) {
i = 0 ; j = 0 ; do {
j += i+1 ; i += 1 ; } while(i<10) ; printf("%d\n", j) ; return 0 ;
}
注意1:これらすべてのプログラムにおいて,「ループの制御変数」i と,「値を格納する変数」 jの2 つの変数が必要になっていることに注意しよう.
「繰り返し文」を使う場合に,必ずしも「ループ制御変数」が明示的に必要になるわけではないが,この 場合には「制御変数」と「値」を別の変数にする必要がある.
注意2:これらのいずれの場合にでも,「1から10までの和をとる」という意図を明確にするには,「1 0回の繰り返し」という意味または,「10まで」という意味で10が明示的にプログラム中に書き込んで あることが重要である.
注意3:Cでのfor文の使い方として,制御変数の値が0から始まるときには,
for(i=0;i<10;i++)
のように,ループ終了条件を<で書くことが多い. (「for文を使って「配列要素への値の代入」を行う場 合に,このような書き方になるのが自然である」という理由に起因する.)
もし,問題が「11から20までの和」であれば,
for(i=11;i<=20;i++)
という書き方もあり得るだろう.
このような違いは,「どれが正しくて,どれが間違っている」という問題ではなく,「Cのプログラマに とってわかりやすいのは何か」という問題であるので,矛盾する内容も含むが,このような「わかりやすい プログラム」の書き方に習熟する必要がある.
注意4:while文(do文も同様)であれば,以下のようなものも考えられる.
i = 1 ;
while(i<=10) { j += i ; i += 1 ; }
i = 0 ; while(i<10) {
i += 1 ; j += i ; }
注意5:while文,do文はfor文とは異なり,ループ終了後に評価する式が存在しない. したがって,i+=1 などという,ループ制御変数の値を置き換える文を, 明示的にループの内部に書く必要がある.
i = 0 ; while(i<10) {
j += i ; }
のままであると「無限ループ」にはまってしまう.
摂氏の温度を−20◦C から40◦Cまでを10◦C 刻みで,と華氏の温度に変換して表示す るプログラムを書きなさい. ただし,表示は小数点以下2桁目を四捨五入しなさい.
はっきり言って,「出題ミス」である.
華氏の温度を−20◦F から40◦F までを10◦F 刻みで,と摂氏の温度に変換して表示す るプログラムを書きなさい.
と出すべきであった.
/* prog06-3-1.c */
/* 摂氏の温度を -20 C から 40 C までを 10 C 刻みで,
* と華氏の温度に変換して表示するプログラム
* ただし, 表示は小数点以下2桁目を四捨五入
*/
#include <stdio.h>
int cersius ; double fahrenheit ;
int main(int argc, char **argv) {
for(cersius=-20;cersius<=40;cersius+=10) { fahrenheit = cersius*9.0/5.0 + 32.0 ;
printf("%1.f C = %.1f F\n", (double)cersius, fahrenheit) ; }
return 0 ; }
/* prog06-3-2.c */
/* 摂氏の温度を -20 C から 40 C までを 10 C 刻みで,
* と華氏の温度に変換して表示するプログラム
* ただし, 表示は小数点以下2桁目を四捨五入
*/
#include <stdio.h>
int cersius ;
int main(int argc, char **argv) {
for(cersius=-20;cersius<=40;cersius+=10)
printf("%d.0 C = %.1f F\n", cersius, cersius*9.0/5.0 + 32.0) ; return 0 ;
}
これら2つの例では,摂氏の温度をint型変数としてループを実行し,ループ内部でdoubleの演算を行っ ている. (計算の部分で9.0/5.0としていることに注意しよう.)
while文を使うならば次の例のようになる.
/* prog06-3-2.c */
/* 摂氏の温度を -20 C から 40 C までを 10 C 刻みで,
* と華氏の温度に変換して表示するプログラム
* ただし, 表示は小数点以下2桁目を四捨五入
*/
#include <stdio.h>
int cersius ;
int main(int argc, char **argv) {
cersius = -20 ; while(cersius<=40) {
printf("%d.0 C = %.1f F\n", cersius, cersius*9.0/5.0 + 32.0) ; cersius += 10 ;
}
return 0 ; }
これらの例では,摂氏温度を表す変数がint型で,華氏温度を表す変数がdouble型になっている. これ が「美しくない」と考える場合には次のような例もあり得るだろう.
/* prog06-3-2.c */
/* 摂氏の温度を -20 C から 40 C までを 10 C 刻みで,
* と華氏の温度に変換して表示するプログラム
* ただし, 表示は小数点以下2桁目を四捨五入
*/
#include <stdio.h>
double cersius, fahrenheit ; int main(int argc, char **argv) {
cersius = -20.0 ;
while(cersius<40.0+0.005) {
fahrenheit = cersius*9.0/5.0 + 32.0 ;
printf("%.1f C = %.1f F\n", cersius, fahrenheit) ; cersius += 10.0 ;
}
return 0 ; }
ここで,ループ終了条件として
cersius<40.0+0.005
を用いている理由は以下の通りである. 変数 cersiusは double 型の変数であるため, 初期値-20.0か ら 10.0を6回加算したときの値は 40.0と本当に等しいかどうかわからない. そのため, 「少々の誤差」
を見込んで 「40.0 + 0.005未満の場合にはループを実行」する条件文をおくことが望ましい. (実際に は,10.0を加えているため,cersius <= 40.0でも正しく動作するが,0.10を加えていくと正しく動作し なくなる.)なお,「誤差」をどのくらいにとればよいかは難しい問題であるが,doubleが10進13桁程 度の精度を持っているので, 加算する値に比べて数桁小さい値を使えばよいだろう. また,K&Rの第1章 ではcersius <= 40と同値な評価式を用いている(K&R, 15ページ). これは,「初心者のための解 説」,「10.0を加えるので,誤差が生じない」などの理由が裏に隠れていることに注意しよう. すなわち,
以下のprob06.cは switch文を利用して,0から12までの整数が2,3,6の倍数か どうかを判定している. ところが, 実行してみればわかる通り, 正しい結果を出力しな い. どこにバグがあるのかを調べ,正しい結果を出力するように修正しなさい.
バグフィックスしたものは以下の通り.
/* prob06.c */
/* 非負整数が 6, 3, 2 の倍数かどうかを判定する. */
#include <stdio.h>
unsigned int n, mod, mod_2, mod_3 ; int main(int argc, char **argv) {
for(n=0;n<=12;n++) { mod_2 = n&1 ; mod_3 = n%3 ;
mod = !mod_2 + (!mod_3<<1) ; switch(mod) {
case 3:
printf("%d は6の倍数\n", n) ; break ;
case 2:
printf("%d は3の倍数\n", n) ; break ;
case 1:
printf("%d は2の倍数\n", n) ; break ;
default:
printf("%d は3の倍数でも2の倍数でもない\n", n) ; break ;
} }
return 0 ; }
バグは2ヶ所に存在していた.
• case 2: のところにbreakが抜けていた.
• modの計算式に括弧を挿入した.
ここで用いている演算子 !,<<,+の優先順位は, 高い順から!,+,<<となっている.
プログラムの意図は,2の倍数かどうかの判定「フラグ」を modの1ビット目,3の倍数かどうかの 判定フラグをmodの2ビット目にセットすることであったので,上のように括弧をつける必要がある.
なお,次のような書きかえも考えられる. (これなら括弧は必要ない.)
mod = !mod_2 | !mod_3<<1 ;
この他にも,
• default: のところにbreakをいれた方がよい.
という改良を加えている.