入門書でのおさらいを並行して進める2

今日は演算子の勉強をしました。そこで引っかかったのでジェミニ先生に聞いてみました。

25ページ・除算の商と剰余

整数同士の割り算

「/演算子」は商を求める演算子と習いました。ここには罠があって、整数同士(int型)の割り算では普通の算数と少し違う答えが返ってきます。

例えば「10/3は3」で「2/3は0」という風に小数部分が切り捨てられます。

今日はこの部分に大きく躓いたので紹介します。

実数の割り算

小数点まで計算したい場合は、実数(float型やdouble型)を使います。

実数の場合の割り算は小数第何位まで出してくれるの?

C言語で一般的に使われるdouble型(倍精度浮動小数点数)は、有効数字で約15桁から17桁までを保持できます。 

「小数第何位まで」という決まりではなく、「数字全体で何桁まで正確に表せるか」という数え方をします。

具体的なイメージ

例えば、1.0 / 3.0 を計算した場合、以下のようになります。
0.3333333333333333...(3が15〜16個並ぶ)

これを超えると、計算の限界で誤差が出てきます。

表示をコントロールする方法

printfで表示する際は、何も指定しないと小数第6位まで表示されるのが一般的ですが、自分で好きな桁数に指定できます。

  • %.2f → 小数第2位まで
  • %.10f → 小数第10位まで
  • %.15f → 小数第15位まで
注意点

「15桁もあれば十分!」と思いがちですが、非常に小さな数や巨大な数を扱う計算を繰り返すと、少しずつ「丸め誤差」というズレが蓄積されます。日常的な課題レベルであれば、「だいたい15桁くらいは正確」と覚えておけばOKです。

26ページの演習2-1

演習2-1をやりました。今日はここに大はまりしました。

2つの整数値を読み込んで前者の数字が後者の何%であるかを表示するプログラムを作成せよ。

最初にこんなプログラムを書いてみました。

#include<stdio.h>
int main(void){
    int x, y;
    puts("整数を2つ入力してください");
    printf("整数1:");
    scanf("%d",&x);
    printf("整数2:");
    scanf("%d",&y);

    printf("%dは%dの%d%%です\n",x, y, (x / y) * 100);
    return 0;
}

パーセントの計算は(前者の数字÷後者の数字)×100が算数での計算です。整数値という指定があったので2つの数字はint型で宣言しました。そしてこれを実行すると。。。

なんで0になるんやーーー!答えは10%でないといけません。そういえば整数同士の割り算は小数点以下を切り捨てるんだった。じゃあどうしたらいいの???ジェミニ先生にはまだ聞きたくなかったので、小一時間ほど考えました。

10÷100の答えは0.1だから、割る前に前者の数字を100倍しておこう!

そうすれば(10×100)÷100で整数部分がパーセントになる!!!

数式は (x*100) / y;としてさっきのプログラムを書き換えました。

#include<stdio.h>
int main(void){
    int x, y;
    puts("整数を2つ入力してください");
    printf("整数1:");
    scanf("%d",&x);
    printf("整数2:");
    scanf("%d",&y);

    printf("%dは%dの%d%%です\n",x, y, (x * 100) / y );
    return 0;
}

よし!あってるっぽいぞ!ジェミニ先生にこれで合ってるか聞きに行こう!

作成されたプログラム、拝見しました!
基本的な流れは完璧で、文法的なエラーもありません。しっかり書けていますね。

ただ、実際の課題として提出する場合、以下の2点に気をつけるとより確実です。

「0で割る」エラーへの対策

もし「整数2」に 0 が入力されると、(x * 100) / y の部分で計算ができなくなり、プログラムが強制終了してしまいます。実用的なプログラムでは、if (y == 0) などでチェックを入れるのが一般的です。

小数点以下の扱い

今のコードだと、整数(int型)同士の計算なので、あまり(端数)が切り捨てられます。

  • 例:x=1, y=3 のとき、本来は 33.33...% ですが、結果は 33% と表示されます。

もし課題の指示が「小数まで出しなさい」という場合は、double型などを使って計算する必要がありますが、「整数で表示せよ」という指示なら今のままで正解です。

まだ条件分岐を習っていない状態です。0で割ったらエラーが出ますか?

はい、習っていないのであれば、今のコードが「現時点で出せる正解」ですね。

ご質問の件ですが、0で割るとエラーが発生します。

具体的には、プログラムを実行中に「整数2」に 0 を入力すると、計算の瞬間にプログラムが異常終了(クラッシュ)してしまいます。これはC言語だけでなく、多くのプログラミング言語で共通のルールです。

今の段階では「0を入力しない前提」で進めて大丈夫だと思いますが、もし気になるようであれば、以下の2つのポイントだけ頭の隅に置いておくと良いですよ。

  1. 整数同士の計算のクセ:今の式 (x * 100) / y で x が大きいとき(例えば2,200万以上など)、x * 100 の結果が int 型が扱える最大値を超えてしまい、計算がおかしくなる「オーバーフロー」という現象が起きることがあります。
  2. 実行時の注意:自分で動作確認をする際は、必ず 0 以外の数字を入れて試してみてください。

このプログラムは、学校の教科書にあるような標準的で綺麗な書き方になっています。

次は、小数点まで表示する方法(実数型)などの学習に進む予定ですか?

条件分岐の分はまだ書けないのでここで終わって、実数型でもプログラムを作って試してみます。

実数型の割り算のコードを書いてみる

小数点の割り算の場合はコードはどうしたらいいですか?

小数点まで計算したい場合は、「実数(float型やdouble型)」という箱を使います

今のコードを少し修正するなら、以下の3ステップが基本です。

  1. 変数の型を int(整数)から double(実数)に変える。
  2. scanf で読み込むときを %d から %lf に変える。
  3. printf で表示するときを %d から %f に変える。
覚えておくと便利なポイント
  • double:小数を取り扱える型です。
  • %.2f:これを printf で使うと、「小数第2位まで表示してね」という指示になります。これを使わないと、標準で 33.333333 のようにたくさん数字が出てしまいます。

教科書で「float」「double」という言葉が出てきたら、この「小数の計算」の話が始まります。

printfの中に数式を書くとわかりにくいので z という変数数式の答えを代入し、zをprintfに入れて作ってみました。

#include<stdio.h>
int main(void){
    double x, y;
    puts("整数を2つ入力してください");
    printf("整数1:");
    scanf("%lf",&x);
    printf("整数2:");
    scanf("%lf",&y);
    double z = (x *100) / y;

    printf("%fは%fの%f%%です\n",x, y, z);
    return 0;
}

うわーー長すぎる!最後のprintfで桁を指定しよう

printf("%.2fは%.2fの%.2f%%です\n",x, y, z);

無事に小数以下が2桁になりました!
気になったことがいくつかあったのでジェミニ先生に全部聞いてみます。

整数int同士の割り算では2番目の数が大きいと先にxに100をかけておかないと計算ができませんでしたが、double型の割り算だと割ってからあとで100をかけても大丈夫ですか?

整数int同士の割り算では2番目の数が大きいと先にxに100をかけておかないと計算ができませんでしたが、double型の割り算だと割ってからあとで100をかけても大丈夫ですか?

結論から言うと、double型であれば、あとで100をかけても大丈夫です!

ここが「整数(int)」と「実数(double)」の大きな違いです。

なぜdouble型だと大丈夫なのか?

int型とdouble型では、計算の仕組みが以下のように異なります。

  • int型の場合(1 / 4 * 100
    1. 1 / 4 を計算 → 0.25 ですが、int型は整数しか扱えないので「0」になります(端数切り捨て)。
    2. 0 * 100 を計算 → 結果は 「0」
      ※だから、先に100をかけて 100 / 4 にする必要がありました。
  • double型の場合(1.0 / 4.0 * 100.0
    1. 1.0 / 4.0 を計算 → 「0.25」 として保持されます。
    2. 0.25 * 100.0 を計算 → 結果は 「25.0」

注意点:計算式に「.0」を忘れないこと

double型で計算するときは、数字に .0 をつけて「これは実数ですよ」とコンパイラに教えてあげると安全です。

// どちらでも正しく計算されます
double z1 = (x * 100.0) / y; 
double z2 = (x / y) * 100.0; //x,y両方が整数を入力してしまうと0になる。

ただし、もし x と y が int型のまま であれば、 (x / y) * 100.0 と書くと、カッコ内の (x / y) が先に「整数」として計算されて 0 になってしまうことがあります。

「割り算をするときは、割る数か割られる数のどちらか(あるいは両方)を double型にしておく」 と覚えておくと、計算ミスを防げますよ。

変数をダブル型で宣言したのにscanfで整数を入れていいん?

変数をダブル型で宣言したのにscanfで整数を入れていいん?

結論から言うと、まったく問題ありません!

double 型(実数)の箱に 10 や 100 といった整数を入れても、C言語が自動的に 10.0 や 100.0 として扱ってくれます。これを「暗黙の型変換」と呼びます。

ただし、計算のときには少しだけ「お作法」を知っておくとミスが減ります。

代入するとき
double x = 10; // これは自動的に 10.0 になるのでOK
 計算するとき(ここが重要!)

もし変数が double x, y; で宣言されているなら、以下のどちらでも正解です。

  • z = (x / y) * 100;
  • z = (x / y) * 100.0;

でも、プログラミングに慣れている人は、あえて 100.0 と書くことが多いです。
理由は、パッと見て「あ、これは小数の計算(double)だな」と自分や他の人に伝わりやすくなるからです。

逆に注意が必要なケース

もし変数が int x, y;(整数) のままで、計算結果だけを double z に入れたい場合は注意が必要です。

int x = 1, y = 4;
double z = (x / y) * 100.0; // 答えは 0.0 になっちゃう!

なぜなら、カッコ内の (x / y) が先に「整数同士」で計算されて 0 になり、その後に 100.0 をかけても 0.0 のままだからです。

これを防ぐには、前回の話のように変数自体を最初から double で宣言しておくのが一番シンプルでミスがない方法です。

「整数を入れてもいいけれど、中身は小数として扱われる」とイメージしておけばバッチリです。

さきに100.0を掛けるか、割った後にかけるか

この式ならどう?答えがちゃんと出ますか?

int x = 1, y = 4;
double z = (x*100.0 )/y;

結論から言うと、その式なら大正解です!ちゃんと double 型の計算として動きます。

なぜなら、式の中に一つでも 100.0 のような double型(実数) が混ざっていると、C言語のルールで計算全体が「double型の精度」に格上げされるからです。

計算がどう進むか

z = (x * 100.0) / y; の場合、コンピュータは以下のように動きます:

  1. まず x * 100.0 を計算します。
  2. x は int ですが、相手が 100.0(double)なので、x も一時的に 1.0 として扱われます。
  3. 1.0 * 100.0 = 100.0(double) になります。
  4. 次に、その結果を y(4)で割りますが、これも相手が double なので 100.0 / 4.0 という計算になり、結果は 25.0(double) になります。
  5. 最後に、z に 25.0 がきれいに代入されます。
覚えておくと便利な「型変換」のコツ

このように、あえて計算式の途中に .0 の付いた数字を混ぜて、全体の計算を小数用にするテクニックを「型変換(キャスト)」の応用としてよく使います。

まとめ

  • z = (x / y) * 100.0; → ×(先に 1 / 4 が 0 になっちゃう)
  • z = (x * 100.0) / y; → (先に 100.0 になるのでOK!)

下のコードのようにxをdoubleにキャストしておくのもよいです。

double z = ((double)x / y) * 100.0;//これならよい

ということはdouble型の計算でも数式は先に100をかける式のほうが良いような気がしますがどうですか?

ということはdouble型の計算でも数式は先に100をかけるほうが良いような気がしますがどうですか?

おっしゃる通りです!「先に100(または100.0)をかける」書き方は、非常に賢く、安全な書き方です。

理由は2つあります。

1. 「0」になるリスクを回避できる

今回のやり取りで気づかれた通り、 (x / y) * 100.0 だと、x と y が整数の場合に、割り算をした瞬間に結果が 0 になってしまう「落とし穴」があります。
先に x * 100.0 を計算してしまえば、その時点で計算全体が小数(double)モードに切り替わるので、値が消えてしまう心配がありません。

2. 精度の維持

数学的には「先に割っても、後でかけても同じ」ですが、コンピュータの計算(特に整数が混ざる場合)では、「先に大きくしてから割る」ほうが、情報のこぼれ落ちが少なく、意図した通りの結果になりやすいです。

結論

あなたが考えた z = (x * 100.0) / y; という書き方は、C言語の仕組みを正しく理解した上での「ミスが起きにくいプロの書き方」に近いものです。自信を持ってその考え方を使って大丈夫ですよ!

実数型を扱うとき、scanfでは%lf、printfでは%fでいいんですか

scanfでは%lf、printfでは%fでいいんですか

結論から言うと、scanfは必ず%lfが必要ですが、printf%fを使うのが一般的です。

ここが少しややこしいのですが、以下のようなルールがあります。

scanf の場合:使い分けが必須

読み込む箱(変数)の大きさに合わせる必要があります。

  • float型を読み込むとき:%f
  • double型を読み込むとき:%lf

printf の場合:どちらでもOK(基本は%f)

printfでは、実は%f%lfも同じように動きます。

  • 歴史的な理由で、printfではfloatdouble%fで表示するのが標準的なルールとされてきました。
  • 最近のルール(C99以降)では、printf%lfを使っても間違いではないとされています。
結局どう覚えたらいい?

一番混乱しない覚え方はこちらです。

scanf(入力)printf(出力)
double%lf%f
int%d%d

まずはscanfdoublel(エル)が必要!」とだけ強く覚えておけば、大抵のミスは防げますよ。

たくさんの疑問があり、少しづつしかすすめません。今日はこれで終わりです。