要約
このレッスンは、データとその型にあてられた、コースの第二単元の始まりである。まず単元の目的を説明する。すなわち、プログラムのあらゆるデータは型とともにメモリに置かれ、その型が、とりうる値、占める大きさ、許される演算を決めること、そしてそれらの限界には観察できる帰結があることを教えることである。さらに、単元の四つのレッスンと、名前と値をもつ区画としての変数から型どうしの変換と定数までそれらを貫く筋道を示し、単元が第一単元から前提とすること、すなわち読み書きするプログラムを書き、コンパイルし、実行できることを確認する。また、単元が解決する問題、つまり見かけは正しいのに計算が数学の計算と一致しないプログラムという問題を、和 (コンピュータでは正確に にならない)から始めて定式化し、最後に、単元がどこへ通じるか、すなわちこの先の単元と「C++ プログラミング II」コースへの道を示す。
学習目標
- 単元の内容の順序を述べ、各レッスンがそのあとのレッスンに何をもたらすかを説明する。
- 第 1 単元から必要なこと(コンパイルと実行のサイクル、書式付きの出力、
std::cinによる読み取り、コンパイラのメッセージの読み方)を思い出し、必要なら復習する。 - 例を用いて、プログラムの数が数学の数ではない理由を説明し、コンピュータで が正確には にならない理由を予告する。
- 単元の終わりにできるようになるはずのことを確認し、それをこの先の単元で学ぶことと区別する。
導入テスト
単元に取りかかる前に、出発点を測っておくとよい。このテストは自己チェックのためのもので、採点はしない。目的はただ学習の方向づけにある。最初の四問は、単元が第一単元から前提とすることを確かめる。手続きを一歩ずつ追うこと、数を小数第二位まで書くこと、丸め、そしてエラーメッセージを読むことである。続く六問は、レッスンの順に、単元の中心となる考え方を扱う。まだ正解できなくてもかまわないし、そのうちの一問では「まだわからない」が誠実な答えである。正解であれ不正解であれ、どの答えもその要点を説明し、それを扱うレッスンを示すからである。同じ技能は、単元の最後のまとめのレッスンで再び測る。
単元を始める前に、自分の出発点を測りましょう。短い自己評価です。採点はされず、結果はあなたのブラウザーにだけ保存されます。
問 1前の単元からある手続きにはこうある。「1. の値を 17 にする。2. の値を にする。3. の値を にする」。終わったとき の値はいくつか。
各ステップが最初の値から出発すると考えている。二つ目のステップは、一つ目が残した値から出発する。「値を…にする」を数学の等式として読んでいる。これは の値を置き換える命令である。変数を定数と取り違えている。変数の値は、別の値が代入されるたびに変わる。各ステップは、その時点で がもつ値から出発するのであって、最初の値からではない。各ステップのあとの各変数の値を記録しながら手続きを手で追うことは、第 1 単元のトレース表であり、レッスン 2.1 はそれを、プログラムの状態について推論するための道具にする。復習: 問題からアルゴリズムへ
問 2前の単元から文
std::println("{:.2f}", 2.0 / 3);は何を出力するか。書式指定が切り捨てると考えている。実際には最も近い小数に丸める。書式指定を無視している。これは.2fなしの書き方である。割り算が整数の割り算だと考えている。小数部分のある2.0があるので、割り算は実数の割り算になる。書式指定.2fは、数を丸めて小数第二位まで出力する。 は0.67と出力される。保存された値は変わらず、変わるのはその書き方だけである。復習: 入力・出力・エラー
問 3前の単元から を丸めて小数第二位まで書きたい。何が得られるか。
丸めずに切り捨てている。第三位の数字を見ずに捨てている。小数第二位ではなく、小数第一位に丸めている。小数第二位を必要以上に増やしている。 は と のあいだにあり、丸めはこの二つのどちらかを選ぶ。小数第二位までに丸めるとは、小数第二位までの数のうち最も近いものを選ぶことである。 から までの距離は、 までの距離より短い。レッスン 2.3 では、コンピュータも同じように丸めるが、二進法で丸めること、そしてそのために小数部分をもつ数のほとんどが正確には保存されないことを示す。復習: 入力・出力・エラー
問 4前の単元からあるプログラムの 5 行目に
const int pages{12}、6 行目にstd::println("{}", pages);がある。コンパイラは6:5: error: expected ',' or ';' before 'std'と出力する。欠陥はどこにあるか。6 行目は正しい。コンパイラがそこを示すのは、そこで欠落に気づいたからである。stdは認識されている。メッセージは、その前にセミコロンを期待していたと言っている。メッセージにはerrorとあり、warningではない。実行ファイルはできていない。コンパイラは、宣言のあとに続くことのできないものに 6 行目の先頭で出会ったときに欠落に気づき、その場所を示す。示された行が正しければ、欠陥はたいていその前の行の末尾にある。復習: プログラムの解剖
問 5あるプログラムが
int a{2};とint b{7};を宣言し、そのあとa = b;とb = 10;を実行する。最後にaの値はいくつか。代入が二つの変数を結びつけ、aがbの別名になるかのように考えている。代入は値をコピーするだけである。a = bを逆に読み、aをbにコピーするかのように考えている。値は右から左へ移る。二つの値を足している。代入は置き換えるのであって、積み上げるのではない。代入a = bは、aに、その時点でbがもつ値をコピーする。そのあとaとbは独立した区画であり、bを変えてもaは変わらない。復習: 変数とメモリ
問 6数 13 は二進法でどう書くか。
誠実な答えである。レッスン 2.2 で数を二進法で書くことと読むことを学び、まとめテストでもう一度問われる。繰り返しの割り算の余りを、現れた順に読んでいる。余りは最後のものから最初のものへと読む。重みの足し算を間違えている。 である。二進法では、各桁の重みはその右隣の桁の二倍であり、数字は 0 と 1 だけである。レッスン 2.2 でこれを学ぶ。プログラムの整数はすべてこのように保存されるからである。復習: 整数とその表現
問 74 バイトの
intがその最大値である を保持しており、プログラムがそれに 1 を足す。何が言えるか。表現の限界を無視している。この数は 4 バイトのintには収まらない。そのようにふるまうのは符号なし整数であり、その算術は剰余算術である。符号付き整数については、規格はそれを保証しない。定数でない限り、コンパイラはプログラムが実行時にもつ値を知らない。ほとんどの場合、何も言わずにコンパイルする。プログラムの整数は決まった数のビットをもち、そのため値の範囲は有限である。符号付き整数の値の範囲から出ることは、足し算の事前条件に違反する。このコースのサニタイザはプログラムを止めるが、それがなければプログラムは何を出力してもおかしくない。レッスン 2.2 でこれを調べる。復習: 整数とその表現
問 8 は有限の小数で表せるか。
は近似値である。 であって、1 ではない。何桁書いても、必ず余りが残る。この展開は循環し、有限ではない。これは数と基数の性質であって、道具の性質ではない。電卓は近似値を示すにすぎない。既約分数が有限の小数で表せるのは、分母が 2 と 5(基数 10 の素因数)以外の素因数をもたないとき、かつそのときに限る。二進法では条件がもっと厳しく、分母が 2 の累乗でなければならない。そのため は有限の二進表現をもたない。レッスン 2.3 はここからさまざまな帰結を引き出す。復習: 浮動小数点数
問 9
std::println("{}", 0.1 + 0.2);は何を出力するか。コンピュータが正確な小数を足すと思っている。実際に足すのは二進法の近似値である。既定の書き方を、小数第二位に丸める書式指定{:.2f}と混同している。二つのdoubleの和は正当である。驚くべきはその結果であって、書き方ではない。も も二進法では正確に保存されず、それらの近似値の和は の近似値ではない。std::printlnは、保存された値を特定する最短の十進表現を出力し、その表現が差をあらわにする。レッスン 2.3 では、それがどこから来るのかを一桁ずつ説明する。復習: 浮動小数点数
問 10あるプログラムが
const double ratio{7 / 2};を宣言し、ratioを出力する。何が表示されるか。変数の型が割り算のしかたを決めると考えている。決めるのはオペランドの型である。整数の割り算が丸めると考えている。実際には小数部分を捨てる。値 3 は定数であり、doubleで正確に表せる。失われるものはないので、波括弧はそれを受け入れる。割り算7 / 2は整数どうしの割り算であり、整数の商 3 を与える。その 3 がdoubleに変換されるのはそのあとである。どの割り算が行われるかは、結果を保存するときではなく、オペランドの型に従って演算の内部で決まる。3.5 を得るには変換をどこに置けばよいかを、レッスン 2.4 で学ぶ。復習: 文字・真偽値・型変換・定数
コンテンツを読み込んでいます…