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

今日入門書の例題をやっているときに剰余を求める演算子「%」をdoubleで宣言した変数に使ったらエラーで起こられてしまいました。そこでまた疑問をジェミニ先生に聞いていきたいと思います

30ページ・整数型と浮動小数点型

intで宣言した変数に9.99のような小数を入れたらどうなりますか

intで宣言した変数に9.99のような小数を入れたらどうなりますか

int(整数型)で宣言した変数に「9.99」のような小数を代入すると、一般的には小数点以下が切り捨てられ、整数部分の「9」だけが格納されます

C / C++ / Java などの場合
自動的に小数点以下を切り捨てる「暗黙の型変換」が行われます。ただし、言語の設定やコンパイラによっては「データが失われる可能性がある」という警告(Warning)が出ることもあります

注意点
  • 精度の損失: 小数点以下のデータは完全に消えてしまうため、後で計算し直しても元の 9.99 に戻すことはできません。
  • 四捨五入したい場合: 切り捨てではなく四捨五入したい場合は、言語ごとに用意された round 関数などを使う必要があります。

逆はどうですかdoubleで宣言した変数に整数(int型)をいれたら?

逆はどうですかdoubleで宣言した変数に整数(int型)をいれたら?

逆の場合はさらにスムーズで、基本的には何の問題もなく代入できます

double 型(浮動小数点型)の変数に整数を入れると、自動的に「.0」がついた小数として扱われます。

挙動の詳細
  • 自動的な変換double x = 10; と書くと、コンピュータ内部で自動的に 10.0 に変換されて格納されます。これを「暗黙の型変換(格上げ)」と呼びます。
  • データの損失がない: 整数よりも小数のほうが「表現できる情報の幅」が広いため、整数を入れてもデータが消える(精度が落ちる)心配はまずありません。 
なぜエラーにならないのか?

プログラミングの世界には、「小さい器(int)のものを大きい器(double)に移すのは安全」というルールがあるからです。 

  • int → double: 100%安全なので自動でやってくれる(エラーにならない)。
  • double → int: 小数点以下が消える危険があるため、警告が出たり特別な書き方が必要だったりする。
注意点:非常に大きな整数の場合

普通の int(32ビット)なら大丈夫ですが、long 型などのめちゃくちゃ桁数が多い整数(例えば19桁など)を double に入れようとすると、ごく稀に下のほうの桁がわずかにズレることがあります。これは double が「およその数」を管理する仕組み(有効桁数 約15桁)だからです。 

日常的なプログラミングであれば、「整数を double に入れるのは自由」と考えて大丈夫です

ではdoubleで宣言した変数に、intをで宣言した変数を使った式を代入する場合、エラーにならずにintの変数が型変換されるのですね?

はい、その通りです!エラーにはならず、計算の過程で自動的に double へ型変換されます。

これをプログラミング用語で「暗黙の型変換(格上げ)」と呼びます。

ただし、「計算のタイミング」によっては、初心者がよくハマる落とし穴があるので注意が必要です。

注意すべき「割り算」のケース

例えば、int 同士の計算を double に代入しようとする場合、結果が予想と異なることがあります。

int a = 10;
int b = 4;
double result = a / b; 

この場合、result は 2.5 ではなく 2.0 になります。

  1. まず a / b (int同士)の計算が行われ、この時点で小数点以下が切り捨てられ 2 になります。
  2. その結果の 2 が、代入時に double へ変換されて 2.0 になります。

うわーーー!これ騙された!

double result = 10 / 4;

intで宣言せず、直接整数の割り算をダブルに突っ込んでも切り捨てが起こってしまいますね

そうなんです。たとえ受け取る側(左辺)が double であっても、計算する側(右辺)が「整数のみ」だと、代入する前に決着がついてしまうのがC言語の落とし穴ですね。

イメージとしてはこんな感じです:

  1. 右辺を計算: 10 / 4 を見て、「整数同士だな、よし 2 だ!」と確定させる。
  2. 左辺に代入: 「あ、入れる先は double か。じゃあ 2.0 にして入れよう」

これを防ぐには、最低でも片方の数字に .0 を付けるか、キャストという機能を使って「これは double として扱ってね」と明示する必要があります。

正解の書き方(キャスト)

期待通り 2.5 を得たい場合は、式のどこかに double を混ぜる必要があります。

double result = (double)a / b; // aを強制的にdouble扱いにすれば、結果も2.5になる

直接数字を入れる場合

double result = 10.0 / 2; // どこかに実数を混ぜると、結果も2.5になる

このように、計算式の中に一つでも double が混ざっていれば、式全体が double として扱われるようになります。

演算子で剰余を使うとき、オペランドは整数でないとだめなんですか?

演算子で剰余を使うとき、オペランドは整数でないとだめなんですか?

C言語において、剰余演算子(%)を使用する場合、オペランド(被演算子)は必ず整数型(intlongchar など)でなければなりません。 

浮動小数点数型(floatdouble)を剰余演算子に使用すると、コンパイルエラーになります。

理由と詳細
  • 整数専用: a % b は「aをbで割った余り」を計算しますが、この演算は整数除算に基づいて定義されているためです。
  • 実数には別関数: 浮動小数点数(実数)で剰余を計算したい場合は、<math.h> ヘッダにある fmod() 関数を使用します。
まとめ
  • 整数: 10 % 3 → OK (結果: 1)
  • 実数: 10.0 % 3.0 → NG (コンパイルエラー)
  • 実数用: fmod(10.0, 3.0) → OK (結果: 1.0)

勉強中とのことですので、剰余は「整数のあまり」にのみ使うと覚えておくと良いでしょう。

もし浮動小数点数(double や float)で余りを求めたい場合は、% 演算子の代わりに標準ライブラリの fmod 関数<math.h>が必要)を使います。

#include <stdio.h>
#include <math.h>

int main() {
    // 整数なら % が使える
    int a = 10 % 3; 

    // 小数の場合は fmod を使う
    double b = fmod(10.5, 3.0); 

    printf("余り: %f\n", b); // 1.5 と表示される
    return 0;
}

char型の深堀

char型でもいい意味が分からない。文字の型じゃないの?

C言語では、文字をコンピュータが理解できる文字番号(アスキーコードなど)で管理しています。そのため、算術演算(足し算や割り算、剰余など)においては、単なる「1バイトの整数」として振る舞います。

例えば、以下のようなコードが動きます。

char a = 10;
char b = 3;
int result = a % b; // 10 ÷ 3 の余りである「1」が計算できる

また、文字そのものを使って計算することも可能です。

char x = 'd'; // アスキーコードで 100
int y = x % 30; // 100 ÷ 30 の余りで「10」になる

このように、C言語のルール上、charは「文字を扱うための型」であると同時に、「ごく狭い範囲(-128〜127など)を扱う整数型」の一種として分類されているため、% 演算子が使えるのです。

マニアックな使い方だね

確かに、今のパソコンのようにメモリが潤沢な環境では「マニアックな工夫」に見えますよね!

ただ、C言語が生まれた背景や、今でも活躍している組み込みシステム(家電のマイコンや車の制御など)の世界では、この「charを数値として使う」のはむしろ定番のテクニックだったりします。

理由は主に2つあります:
  1. メモリの節約
    int型は通常4バイト使いますが、char型なら1バイトで済みます。大量のデータを扱う際、数値が0〜255に収まるとわかっていれば、char型(正確にはunsigned char)を使うことでメモリ使用量を4分の1に抑えられます。
  2. 通信や画像データ
    ネット通信で送るデータや、画像の「色の濃さ(0〜255)」などは、1バイト単位で処理するのが効率的です。そのため、これらを扱うプログラムではchar型の配列を数値計算に使いまくります。

現代の一般的なプログラミングでは、わかりやすさ優先で「整数ならとりあえずint」でOKですが、C言語の「ハードウェアを直接いじっている感覚」が、このchar型の扱いに現れていて面白いですよね。

今の現場でもchar使ってるん?

はい、現在の開発現場でも char 型はバリバリ現役で使われています。 

ただ、「文字」としてではなく「数値」として使う場合は、意図を明確にするために signed char や unsigned char、あるいはより現代的な uint8_t といった書き方をするのが一般的です。

1. 組み込み・ハードウェア制御

家電、自動車、センサー機器などの「組み込みシステム」では、今でもメモリが数キロバイトしかない環境があります。 

  • フラグ管理: 0か1かで済む設定値に 4バイトの int を使うのはもったいないため、1バイトの char で節約します。
  • レジスタ操作: ハードウェアのスイッチ状態などを 1ビット単位で読み書きする際、基本単位として char(1バイト)が使われます。 
2. 通信データやバイナリ解析

インターネットでデータを送受信したり、画像ファイルを読み込んだりする際は、データを「1バイトの塊」として扱います。 

  • パケット処理: 通信データのヘッダー(データの宛先やサイズが書かれた部分)を解析する際、unsigned char の配列として読み込み、数値として計算します。
  • 画像処理: RGB(赤・緑・青)の各色の濃さは通常 0〜255 なので、unsigned char で保持するのが最も効率的です。 
3. 現代的な書き方 (uint8_t)

最近の現場では、単に char と書くと「文字なのか数値なのか紛らわしい」ため、stdint.h という標準ライブラリを使って以下のように書くのが主流です。 

  • uint8_t: 「符号なし・8ビット(1バイト)・整数」という意味。
  • int8_t: 「符号あり・8ビット・整数」という意味。 

これなら「これは計算用の1バイト変数だ!」と誰が見ても一目でわかります。

「マニアックな使い方」が、実はシステムの心臓部を支える重要な技術として定着しているのが C言語の面白いところですね。

現場での呼ばれ方

実際の開発現場では、混乱を避けるために char という名前よりも、「中身は数値だぞ」と強調した名前がよく使われます。

  • uint8_t: 「8ビットの符号なし整数」という意味で、画像処理の現場では定番です。
  • BYTE: 古いプロジェクトやWindows系の移植層ではこう定義されていることもあります。

「文字型」という名前からは想像しにくいですが、最先端の高速計算の世界ほど、この「最小の整数」としての char が重宝されています。

名前は違えどcharとuint8_tは同じものなんだろ?

その通りです!中身(データサイズ)は全く同じ「1バイト(8ビット)」です。

正確に言うと、uint8_t(ユーアイエヌティーハチティー)は、char型に別名をつけたものに過ぎません。

なぜ同じものに別の名前がついているかというと、現場での「使い分け」に理由があります。

「文字」か「数値」かをはっきりさせるため
  • char と書くと:「あ」とか「A」とか、文字を入れるんだな、と人間に伝わる。
  • uint8_t と書くと:「0〜255」の数値として計算に使うんだな、と人間に伝わる。
「サイズ」を保証するため

C言語のルールでは、実は int の長さはコンピュータによって変わることがあります(2バイトだったり4バイトだったり)。
一方で、uint8_t はその名の通り「絶対に8ビット(unsigned int 8bit)」であることを保証する名前なので、画像解析のように「1画素=必ず8ビット」という厳密さが求められる現場で好まれます。

結局どう定義されている?

uint8_tは、通常<stdint.h> ヘッダーファイルで定義されます。
もしコード中でuint8_tを使用したい場合は、#include <stdint.h>(stdio.hではない。間違いやすい)を記述してください。 

プログラムの裏側(ヘッダファイル内)では、だいたいこんな感じで定義されています。

typedef unsigned char uint8_t; 
// 「これからは unsigned char のことを uint8_t と呼ぶよ!」という宣言

組み込みの現場でのイメージ(量子化で省エネ)

数値をザックリさせる(量子化する)と、計算の精度は少し落ちますが、その分メモリを節約でき、CPUの計算スピードが劇的に上がり、結果として消費電力を抑えられます。

実際に、「浮動小数点の計算をあえて整数にする(量子化する)計算」のやり方、興味ありますか?

量子化って?

簡単に言うと、「連続した細かい値を、あえてザックリした数値にまとめること」を指します。

なぜ「量子化」が必要なの?

例えば、画像や音声などの自然界のデータは「無限に細かい変化」がありますが、コンピュータのメモリ(特に組み込みの char 型など)には限りがあります。

  • アナログ: 0.123456… という細かい値(メモリを食うし計算も重い)
  • 量子化後: 「123」という整数(unsigned char 1枚に収まり、計算も速い!)

アナログな情報(音や光など)をデジタル(0と1)に変換するとき、実は2つのステップを踏んでいます。

  1. 標本化(サンプリング): 時間をブツ切りにする(横軸)
  2. 量子化: 値をザックリした段階に分ける(縦軸)
メリットは「爆速」と「省エネ」

数値をザックリさせる(量子化する)と、計算の精度は少し落ちますが、その分メモリを節約でき、CPUの計算スピードが劇的に上がり、結果として消費電力を抑えられます。

組み込みの現場でのイメージ

例えば、温度センサーの値を unsigned char(0〜255)で扱う場合を考えてみましょう。

  • アナログ: 今日の気温は 25.32859... 度。
  • デジタル化(量子化): 「0.1度単位で刻む」と決めて、253 という数値にして unsigned char に入れる。

この「0.128…」という細かい部分を切り捨てて、「1バイトの整数(253)」という扱いやすい形に固めることを、専門用語で「量子化」と呼びます。

なぜわざわざ「量子化」と呼ぶのか

特に画像解析や東芝の疑似量子の世界では、「あえてデータの精度を落として(=量子化して)、計算を劇的に速くする」というテクニックが使われるからです。

  • 16ビット(約6万段階)で計算するより、
  • 8ビット(256段階:unsigned char)に「量子化」して計算したほうが、
  • 回路がシンプルになり、省エネで爆速になる!

先生の解説で温度25.32859がなぜ急に253になったかか意味が分からなかったよ

説明不足で失礼しました!

「253」にしたのは、「小数点を無視して、10倍して整数として扱う」という組み込み業界でよく使われるテクニック(固定小数点数といいます)の一例です。

なぜそんなことをするのか?

組み込みの世界で使われる unsigned char は整数しか入りません。でも、温度の「0.3度」のような細かい値も管理したいですよね。

そこで、エンジニアはこう考えます。
全ての値を10倍したことにしよう!

  • 25.3度 → 10倍して 253 として unsigned char に保存。
  • 10.5度 → 10倍して 105 として保存。

こうすれば、本来は小数が必要なデータも、メモリに優しい unsigned char(整数)のまま扱うことができます。

これが「量子化」の正体

本来は「25.32859…」と無限に続く細かい値を、「10倍して小数点以下は切り捨てる」というルールで 253 という「ザックリした段階」に当てはめる。この作業が「量子化」です。

剰余(%)とのつながり

この「253」という数値を使って、「10度を超えた分の端数(0.1度単位)だけ知りたい」と思ったら、こう計算できます。

unsigned char temp = 253; // 25.3度のこと
int hasu = temp % 100;    // 100で割った余りは「53」

このように、「小数をあえて整数に化けさせて、整数用の演算子(%など)で高速に処理する」のが、省エネ・高性能な組み込みプログラムの裏技なんです。

この「あえて10倍や100倍にして整数で扱う」という考え方、納得できそうですか?

昨日勉強したパーセント(割合)表示する課題につながった!

むむ!これは昨日勉強した、int型の割り算の課題で、整数1は整数2の??パーセントですっていうプログラムを書いた時の処理と似てる。%表示は普通少数ですが、イントは小数以下が切り捨てされちゃうから100倍にしてプログラムを作りました

まさにそれです!その感覚、めちゃくちゃ正しいですよ。

昨日の課題で「100倍してから計算した」のは、まさに「小数を整数に化けさせて扱う」という量子化の基礎を実践していたことになります。

組み込みの世界でも、全く同じ理由で100倍(あるいは256倍など)にします。

  • 課題のケース: 0.15(15%)を扱いたいけど、intだと「0」になっちゃう。だから「15」として扱うために100倍した。
  • 現場のケース: 25.3度を扱いたいけど、charだと「25」になっちゃう。だから「253」として扱うために10倍した。

やってることは完全に同じ「整数の世界で小数を表現する工夫」です。

そして、ここからが面白いのですが、東芝の疑似量子やITRONの高速な処理では、100倍の代わりに「256倍」や「65536倍」をよく使います。なぜなら、コンピュータにとって「100」で掛けるより「256(2の8乗)」で掛けるほうが、ビット演算(シフト演算)を使って一瞬で計算できるからです。

昨日の「100倍」という工夫が、実は最先端の高速化技術に片足を突っ込んでいたわけですね。

ちなみにさっきのプログラム
unsigned char temp = 253; // 25.3度のこと
int hasu = temp % 100; // 100で割った余りは「53」

このhasuはunsigned charにしなかったのはなぜ?処理が遅くなるんじゃない?

鋭い視点ですね!「省エネのために小さい型を使う」という流れからすると、確かに unsigned char にした方が良さそうに見えます。

実は、ここには「計算のしやすさ」と「CPUのクセ」という、もう一つの現場のリアルな理由があります。

CPUは int が一番得意

多くのマイコン(CPU)にとって、実は int 型が「最も自然に、一番速く計算できるサイズ」として設計されています。

  • char(1バイト)の計算を命令すると、CPUはわざわざ「1バイト分だけ取り出して、残りの3バイトを無視する」という余計な手間をかけることがあります。
  • 逆に int なら、CPUの計算回路(32bitや64bit)をフルに使ってそのまま計算できるため、一時的な計算結果を保存するだけなら int の方が速い場合が多いのです。
「保存」は char、「計算」は int

現場ではこのように使い分けるのが一般的です。

  • unsigned char: 大量のデータをメモリに置いておくとき(省スペース重視)。
  • int: 数値を一時的に計算したり、比較したりするとき(スピード・正確さ重視)。

今回の例で hasu(端数)を int にしたのは、このあとに「他の値と足し算する」「画面に表示する」といった処理が続くことを想定して、CPUが扱いやすい形にしておいた、という意図があります。

「型変換」のオーバーヘッドを防ぐ

C言語には「小さい型同士で計算しても、勝手に int に変換してから計算する」というルール(汎整数拡張といいます)があります。
結局 int になる運命なら、最初から受け皿を int にしておいたほうが、型を変換する無駄な処理が省けて効率的なこともあるのです。

結論
  • データの塊(画像や設定値のリスト)は、徹底的に unsigned char で節約する。
  • 今まさに計算している「その瞬間」の値は、int でサクッと処理する。

これが組み込み業界の「適材適所」な使い分けです。

ということは計算はダブルでなくイントを使い、保存はイントでなくunsigned char(unit8_t)を使うということですか

まさにそれが、組み込み・画像解析・疑似量子の現場でプロが実践している「三段活用」の考え方です。整理するとこうなります:

  1. 保存(メモリ)は uint8_t (unsigned char)
    • 何十万画素もある画像データを保存するときは、1バイトに詰め込んでメモリを徹底的に節約する。
  2. 計算(演算中)は int
    • 計算する瞬間は、CPUが一番得意なサイズ(32bitなど)に放り込んで、スピード重視でぶん回す。
  3. 浮動小数点(double)は極力避ける
    • double(小数の計算)は専用の回路がないマイコンだと恐ろしく時間がかかり、電池も食うので、「10倍や100倍して整数(int)で計算する」という技で回避する。

なぜこれが「最強の省エネ」なのか

  • double を使わない → 高価で電気を食う浮動小数点演算ユニット(FPU)を使わずに済む。
  • 保存を uint8_t にする → 安くて小さいメモリ容量で済むため、製品のコストとサイズが下がる。
  • 計算を int にする → CPUが最短の手順で計算を終わらせて、すぐに「おやすみ(省エネモード)」に入れる。

昨日の「100倍してパーセントを出す」という工夫は、まさにこの「doubleを封印して、効率のいい計算ルートを通す」というプロの判断そのものだったんですよ。

剰余の演算子%からずいぶん脱線していろいろ聞いちゃいましたが現場でどのように使われているかなどの話が面白く、ためになりました。さらに勉強を続けたいと思います。