Lesson 6

基本型

Lesson 6 Chapter 1
基数

C言語では、データの種類(文字や整数)によって異なる型が存在します。例えば、整数のデータには整数型、小数点を含む数を扱う浮動小数点型、文字には文字型といったものがあります。これらは「基本型」と呼ばれ、C言語の仕様として定義されています。このChapterでは、「基本型」の使い方や仕様に学習します。最初に、数字の数え方の基本となる「基数」について解説します。

基数とは

私たちが普段、使っている西暦の2023や、年齢の20などは「10進数」と呼ばれ、10が「基数」です。printf関数を使用して整数を表示しますが、「%d」は整数を「10進数」で表示するための「変換指定子」になります。コンピュータの世界では基数が8の「8進数」や、基数が16の「16進数」が存在します。8進数は0~7の8通りの数で表現します。そして、16進数は0~Fの16通りで数を表現します。

10進数と8進数、16進数の対応表は次のとおりです。

10進数 8進数 16進数
0 0 0
1 1 1
2 2 2
3 3 3
4 4 4
5 5 5
6 6 6
7 7 7
8 10 8
9 11 9
10 12 A
11 13 B
12 14 C
13 15 D
14 16 E
15 17 F
16 20 10

基数変換

上記の表のように、10進数を8進数や16進数に変換することができます。整数を16進数で表示するためには、変換指定子の「%x」や「%X」を使います。「%x」を使うと小文字のa~f、「%X」を使うと大文字のA~Fで表示します。複数個の%xや%Xを書いたり、他の変換指定子(%dなど)と混在させることも可能です。

整数を16進数で表示
printf("%x", 整数)  ← 16進数a~fを小文字で表示する
prinf("%X", 整数)  ← 16進数a~fを大文字で表示する

10進数を16進数に、16進数を10進数に変換するプログラムを書いてみましょう。16進数を書くときは、先頭に「0x」の「整数定数」をつけて書きます。

change.c
#include <stdio.h>
int main(void) 

  puts("10進数 → 16進数");
  printf("%x\n", 16);
  printf("%x\n", 100);

  puts("16進数 → 10進数");
  printf("%d\n", 0x10);
  printf("%d\n", 0xf);
  
  puts("16進数 → 16進数");
  printf("%X\n", 0xabcdef);
  printf("%x\n", 0xabcdef);
  
  return 0;
}
実行結果
function1.exe
10進数 → 16進数
10
64
16進数 → 10進数
16
15
16進数 → 16進数
ABCDEF
abcdef

次に、整数を8進数で表示する方法を学習します。8進数は、変換指定子の「%o」(小文字のオー)を使います。複数個の%xや%Xを書いたり、他の変換指定子(%dなど)と混在させることも可能です。

整数を8進数で表示
printf("%o", 整数)

上記の方法を使って、8進数の777を、8進数、16進数、10進数で出力するプログラムを書いてみてください。変換指定子の%o、%x、%dを使います。8進数の「整数定数」は、先頭に0をつけて書きます。

change.c
#include <stdio.h>
int main(void) {

  puts("8進数 → 8進数");
  printf("%o\n", 0777);

  puts("8進数 → 10進数");
  printf("%d\n", 0777);

  puts("8進数 → 16進数");
  printf("%x\n", 0777);
  return 0;
}
実行結果
function1.exe
8進数 → 8進数
777
8進数 → 10進数
511
8進数 → 16進数
1ff

Lesson 6 Chapter 2
文字型

コンピュータでは文字を文字コードで表します。文字コードとは、コンピュータ上で文字をデータ(値)として扱うために、各種の文字に対して割り当てた番号のことです。文字コードには多くの種類がありますが、現在よく使われているのは、ASCII (アスキー)とUnicode (ユニコード)です 。Unicodeに関しては、文字エンコーディング(文字コードを表現する形式)が複数あり、UTF- 8、UTF-16、UTF-32といった形式が使われています。C言語でも、文字コードを使って文字を扱います。つまり、C言語における文字の正体は、文字コードだといえます。まずはプログラムに文字を書くことから始めて、文字について詳しく学んでみましょう。

char

プログラム中に書かれた文字データのことを「文字定数」と呼びます。文字定数は次のように'(シングルクォーテーション)で囲んで書きます。

文字定数
'文字'

1バイトの文字を表す型が「char」 (キャラ)です。charはcharacter (キャラクター)の略で、文字を意味します。いくつかの文字について、文字コードを調べてみましょう。文字定数の'0'~'5'、'A'~'F'について、文字と文字コードを出力するプログラムを書いてください。

char1.c
#include <stdio.h>
int main(void) 

  puts("'0'から'9'の文字コードを出力");
  printf("%4c %4d\n", '0', '0');
  printf("%4c %4d\n", '1', '1');
  printf("%4c %4d\n", '2', '2');
  printf("%4c %4d\n", '3', '3');
  printf("%4c %4d\n", '4', '4');
  printf("%4c %4d\n", '5', '5');

  puts("'A'から'F'の文字コードを出力");
  printf("%4c %4d\n", 'A', 'A');
  printf("%4c %4d\n", 'B', 'B');
  printf("%4c %4d\n", 'C', 'C');
  printf("%4c %4d\n", 'D', 'D');
  printf("%4c %4d\n", 'E', 'E');
  printf("%4c %4d\n", 'F', 'F');
  
  return 0;
}
実行結果
char1.exe
'0'から'9'の文字コードを出力
   0   48
   1   49
   2   50
   3   51
   4   52
   5   53
'A'から'F'の文字コードを出力
   A   65
   B   66
   C   67
   D   68
   E   69
   F   70

結果を見やすくるため、変換指定子で4桁に揃えています。'0'から'9'の文字コードは48から53であることに注意してください。アルファベットに関しても文字コードが1ずつ大きくなっています。

char型は1バイトと決められているので、sizeof(char)は1を返します。char型は1バイトの文字コードを表せますが、これは1バイトの整数を表せるともいえます。そのため、char型は文字を扱うためにも、整数を扱うためにも使います。

signed char

「signed char」は、文字通り、符号ありのchar型のことを指します。ここでいう符号とは「その変数がマイナスの値を取り得るかどうか」を区別するためにあります。

unsigned char

「unsigned char」は、「signed char」とは逆で符号なしを意味しています。0からプラスの値しか代入できない型になります。

文字型のビット数

signed char

signed char型の場合は、下図のように最上位ビットが「符号ビット」、下位7ビットが「データビット」になります。「符号ビット」が0のときは正の数、1のときが負の数になります。正の数の表現範囲は、0~127になり、負の数の範囲は-128~-127となります。

7-2_1.png

unsigned char

unsigned char型の場合は、下図のように8ビット全てがデータビットとなります。10進数で表現すると0~255まで表現可能です。

7-2_2.png

1バイトは8ビット

現在、ほとんどの環境において1バイトは8ビットで間違いありません。Windows やLinuxなどの環境においても1バイトは8ビットで構成されています。しかし、正確には1バイトが8ビットであるという定義はなく、1バイトが何ビットで構成されるかは環境に依存します。

Lesson 6 Chapter 3
整数型

このChapterでは、整数型について学習します。整数型は、表現したい数の範囲を使い分けるため、多くの種類が存在します。広い範囲の整数が表現したい場合は、メモリの消費量が大きくなります。反対に、表現したい数の範囲が狭い場合は、小さな整数型を選ぶことによって、メモリを節約することができます。型の大きさはバイト(byte)という単位で表現されます。では、整数型の種類によって、数の範囲やバイト数にどのような違いがあるのか確認していきましょう。

整数型

整数型にはintの他にも、サイズ(バイト数)が異なる次のような型があります。intはshort以上、longはint以上、long longはlong以上のサイズを持ちます。具体的なサイズは環境によって変化するので、C言語の仕様で決められているサイズとGCCのサイズを表に記載しています。

読み方 C言語の
仕様ビット数
GCCにおける
ビット数
short ショート 2バイト以上
(16ビット以上)
2バイト
(16ビット)
int イント 4バイト以上
(32ビット以上)
4バイト
(32ビット)
long ロング 8バイト以上
(64ビット以上)
8バイト
(64ビット)
long long ロングロング 8バイト以上
(64ビット以上)
8バイト
(64ビット)

sizeof演算子でオブジェクトのバイト数を確認する

「sizeof」 (サイズオブ)という演算子を使うと、型のサイズを知ることができます。sizeofは指定した型のバイト数を返します。「sizeof」 (size of)は「…のサイズ」という意味です。
sizeof演算子を使って、整数型のサイズを調べてみましょう。char、short int、int,longのバイト数を出力するプログラムを書いてください。printf関数の変換指定子は「%lu」を使います。

sizeof.c
#include <stdio.h>
int main(void) {
  printf("%lu\n", sizeof(char));
  printf("%lu\n", sizeof(short int));
  printf("%lu\n", sizeof(int));
  printf("%lu\n", sizeof(long));
  return 0;
}

整数型は次のようなサイズでした。サイズは環境によって異なるので、もし別の環境をお使いならば、プログラムを実行してサイズを確認してましょう。

sizeof.exe
<Windowsでの実行例>
2    ← char       2バイト (16ビット)
4    ← short int  4バイト (32ビット)
4    ← int        4バイト (32ビット)
8    ← long       8バイト (64ビット)

整数型の内部表現

整数型には符号あり(signed)と符号なし(unsigned)があります。型の前に何も書かないか、signed (サインド)と書くと符号ありになります。例えばintやsigned intは符号ありです。一方、型の前にunsigned (アンサインド)と書くと符号なしになります。例えばunsigned intは符号なしです。符号ありと符号なしの両方あるのは、以下の表のように各ビット数で表現できる整数の最小値、最大値が異なるためです。

符号ありの場合
サイズ 最小値 最大値
8ビット -128 127
16ビット -32768 32767
32ビット -2147483648 2147483647
64ビ -9223372036854775808 9223372036854775807
符号なしの場合
サイズ 最小値 最大値
8ビット 0 255
16ビット 0 65535
32ビット 0 4294967295
64ビット 0 18446744073709551615

下記の図は、「符号あり整数型」と「符号なし整数型」における、取り得る値の範囲を表現しています。「符号あり整数型」の最上位ビットは「符号ビット」で使用されることはChapcher2の「signed char型」で学習しました。符号ありの整数を「2の補数」という方式で表現しています。下記の図の「0」と「1」からなる2進数は、2の補数による8ビットの負数と非負数の例になります。8ビットの場合、「符号あり整数型」は「-128から127」までの値を表現できます。
「符号なし整数型」の場合、最上位ビットは「符号ビット」の情報を格納する必要がないため、広い範囲の値を表現できます。8ビットの場合、「0から255」までの値を表現できます。

7-2_3.png

limits.hヘッダ

C言語のヘッダーファイル「limits.h」を使って、整数型の最大と最小値を確認する方法を紹介します。

定義名 取得値
CHAR_BIT char型のビット数
CHAR_MIN char型の最小値
CHAR_MAX char型の最大値
UCHAR_MAX unsigned char型の最大値
SHRT_MIN short int型の最小値
SHRT_MAX short int型の最大値
USHRT_MAX unsigned short int型の最大値
INT_MIN int型の最小値
INT_MAX int型の最大値
UINT_MAX unsigned int型の最大値
LONG_MIN long型の最小値
LONG_MAX long型の最大値
ULONG_MAX unsigned long型の最大値

以下のプログラムを実行して、整数型の最大値と最小値を確認してみます。

limit1.c
#include <stdio.h>
#include <limits.h>
 
int main(void) {
 
  // char
  printf("char の最小値 = %d\n", CHAR_MIN);
  printf("char の最大値 = %d\n", CHAR_MAX);
  printf("unsigned char の最大値 = %d\n\n", UCHAR_MAX);

  // short int
  printf("short int の最小値 = %d\n", SHRT_MIN);
  printf("short int の最大値 = %d\n", SHRT_MAX); 
  printf("usinged short int の最大値 = %u\n\n", USHRT_MAX); 
 
  // int 
  printf("int の最小値 = %d\n", INT_MIN);
  printf("int の最大値 = %d\n", INT_MAX);
  printf("usigned int の最大値 = %u\n\n", UINT_MAX);
 
  // long
  printf("long の最小値 = %ld\n", LONG_MIN);
  printf("long の最大値 = %ld\n", LONG_MAX);
  printf("usigned long の最大値 = %lu\n", ULONG_MAX);
 
  return 0;
}
実行結果
limit1.exe
char の最小値 = -128
char の最大値 = 127
unsigned char の最大値 = 255

short int の最小値 = -32768
short int の最大値 = 32767
usinged short int の最大値 = 65535

int の最小値 = -2147483648
int の最大値 = 2147483647
usigned int の最大値 = 4294967295

long の最小値 = -2147483648
long の最大値 = 2147483647
usigned long の最大値 = 4294967295

bool型

C言語で真偽値を扱うときには、int型などの整数型を使うこともできますが、C99以降では_Bool (ブール)という型が用意されています。int型などを使っても構いませんが、Bool型を使うことによって、真偽値を扱っていることが明確になり、プログラムが読みやすくなる可能性があります。Bool型の変数には、1または0を格納できます。他の型の値をBool型の変数に格納した場合、0以外の値は1に変換されます。

浮動小数点数型

浮動小数点数型は、サイズ(ビット数)が異なるfloat、double、long dubleの3種類の型があります。大部分の環境においては、次のようなサイズの浮動小数点数型になります。

読み方 C言語の仕様サイズ 一般名
float フロート 32bit 単精度
double ダブル 64bit 倍精度
long duble ロングダブル 64bit以上 拡張倍精度

long doubleに関しては、環境によってサイズが異なります。整数型と同様にsizeof演算子を使って、浮動小数点数型のサイズを調べてみましょう。float、double、long doubleのバイト数を出力するプログラムを書いてください。printf関数の変換指定子は%luを使います。

size2.c
#include <stdio.h>
int main(void) {
  puts("%lu", sizeof(float));
  puts("%lu", sizeof(double));
  puts("%lu". sizeof(long double));
  return 0;
}

浮動小数点数型は次のようなサイズでした。サイズは環境によって異なる可能性があるので、もし別の環境をお使いならば、プログラムを実行してサイズを確認してみましょう。

size2.exe
<Windowsでの実行例>

4     floatのバイト数
8     doubleのバイト数
16    1ong doubleのバイト数

doubleはfloatよりも精度が高いので、使いやすい浮動小数点数型です。通常の用途には、doubleを使って問題ありません。速度やメモリの節約を重視する場合にはfloatを使いましょう。floatはdoubleよりも精度が低いのですが、一般にdoubleよりも計算が高速で、必要なメモリの容量もdoubleの半分です。そのため、大量の浮動小数点数を高速に処理する必要がある3Dグラフィックスなどでは、floatが使われる場合があります。なお、異なる浮動小数点数型の間で計算を行うと、暗黙の変換によって、よりビット数が少ない(サイズが小さい)型が、よりビット数が多い(サイズが大きい)型に変換されます。例えば、floatとdoUbleの計算では、floatがdoubleに変換され、結果もdoubleになります。

size_t型とtypedef宣言

型の名前に、別名を与える機能があります。このような機能があるのは、型も変数と同じで、意味が通じやすい名前を付けたほうがいいと考えられるためです。単に intよりも、sales(この型は売上を表現する)のような名称のほうが分かりやすく、間違いを防ぐ効果もあります。実際にはsalesのような名前では、変数名との区別が難しくなってしまうため、sales_tや sales_type などとすることが多いです。型の別名を付けるには、typedef を使います。

型の別名を宣言
typedef 元の名前 別名;

「unsinged int型」の別名に「size_t」を設定したい場合は以下のように宣言します。

unsigned int型の別名を宣言
typedef unsigned int size_t;

ビット単位の論理演算を行う関数

「ビット演算子」や「シフト演算子」は算術演算子の一種です。いずれの演算子も、整数を2進数として操作するために使います。これらの演算子は、ハードウェアの制御や通信などを行うプログラムで活躍するほか、乗算・除算・剰余などの計算を高速化するためにも役立ちます。ビット演算子には次の4種類があります。「~」は単項演算子で「&」「|」「^」は二項演算子です。整数に対して、ビット単位の論理演算を行います。

演算子 種類 使用方法
~ 否定(NOT) ~a
& 論理積(AND) a&b
| 論理和(OR) a|b
^ 排他的論理和(XOR) a^b

シフト演算子には次の2種類があります。いずれも二項演算子です。整数を指定したビット数だけシフトする演算を行います。「シフトする」というのがどんな操作なのか、詳しくは後述します。

演算子 種類 使用方法
<< 左シフト a<<b
>> 右シフト a>>b

ビット演算子やシフト演算子は整数を2進数として扱うので、これらの演算子を使うには2進数の知識が必要です。 10進数と2進数、8進数、16進数の対応表は次のとおりです。

10進数 2進数 8進数 16進数
0 0 0 0
1 1 1 1
2 10 2 2
3 11 3 3
4 100 4 4
5 101 5 5
6 110 6 6
7 111 7 7
8 1000 10 8
9 1001 11 9
10 1010 12 A
11 1011 13 B
12 1100 14 C
13 1101 15 D
14 1110 16 E
15 1111 17 F
16 10000 20 10

~演算子

~は否定(ひてい)を計算する演算子で、使い方は「~A」です(Aは整数)。否定はNOT (ノット)とも呼ばれます。~は入力された値から、ビット単位で0を1に、1を0に反転した出力を求めます。0の否定を計算して出力するプログラムを書いてみましょう。結果は16進数で表示すると分かりやすいので、printf関数の変換指定子には「%x」を使います。

bit1.c
#include <stdio.h>
 
int main(void) {
 
 printf("%x\n", ~0);
 
  return 0;
}
実行結果
bit1.exe
ffffffff

結果はffffffffで、2進数では32桁の1になります。C言語では環境によって整数のビット数が異なりますが、上記では32ビットの整数が使われています。32ビットで整数の0を表すと、32桁の0になります。これに~を適用して、各桁を0から1に反転すると、32桁の1になるというわけです。0と、その否定である16進数のffffffffを、2進数とともに以下の表に示しました。分かりやすくするために、2進数は4桁(16進数の1桁に相当)ごとに区切っています。

16進数 2進数
00000000 ffffffff
0000 0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 1111 1111 1111 1111

&演算子

&は論理積を計算する演算子で、使い方は「a&b」です。論理積はANDとも呼ばれます。&は2個の入力から、以下のように出力を求めます。入力が両方とも1の場合だけ出力が1になる、と考えると覚えやすいです。

入力a 入力b 出力
0 0 0
0 1 0
1 0 0
1 1 1

&は値から特定のビットを取り出す目的に使えます。値の中の特定のビットだけを残して、他のビットを0にできます。例えば、16進数の01234567から、2,3,4,5の部分を取り出してみましょう。0x01234567と0x00ffff00の論理積を計算して出力するプログラムを書いてください。

bit2.c
#include <stdio.h>
 
int main(void) {
 
  printf("%08x\n", 0x01234567 & 0x00ffff00);

  return 0;
}
実行結果
bit2.exe
00234500

16進数の01234567と00ffff00を、2進数とともに以下の表に示しました。各ビットに論理積を適用すると、00234500が得られます。

16進数 2進数
01234567 0000 0001 0010 0011 0100 0101 0110 0111
00ffff00 0000 0000 1111 1111 1111 1111 0000 0000
00234500 0000 0000 0010 0011 0100 0101 0000 0000

|演算子

|は論理和(ろんりわ)を計算する演算子で、使い方は「a|b」です。論理和はORとも呼ばれます。|は2個の入力から、次のように出力を求めます。入力が両方とも0の場合だけ出力が0になる、と考えると覚えやすいでしょう。

入力a 入力b 出力
0 0 0
0 1 1
1 0 1
1 1 1

|は値の中にある特定のビットを1にする目的に使えます。特定のビットだけを1にして、他のビットは元の値のまま保つことができます。例えば、16進数の01234567の3と5の部分を論理和を使ってfにして出力するプログラムを書いてみてください。printfの変換指定は%08xを使います。

bit3.c
#include <stdio.h>
 
int main(void) {
 
  printf("%08x\n", 0x01234567 | 0x000f0f00);

  return 0;
}
実行結果
bit3.exe
012f4f67

16進数の01234567と0x000f0f00を、2進数とともに以下の表に示しました。各ビットに論理和を適用して、結果の012f4f67が得られることを確認してみてください。

16進数 2進数
01234567 0000 0001 0010 0011 0100 0101 0110 0111
000f0f00 0000 0000 0000 1111 0000 1111 0000 0000
012f4f67 0000 0001 0010 1111 0100 1111 0110 0111

^演算子

^は排他的論理和を計算する演算子で、使い方は「a^b」です。排他的論理和はXOR (エックスオア)とも呼ばれます。^は2個の入力から、次のように出力を求めます。入力が同じ場合は0になり、入力が異なる場合は1になる、と考えると覚えやすいでしょう。

入力a 入力b 出力
0 0 0
0 1 1
1 0 1
1 1 0

^は値の中にある特定のビットを反転(0を1に、1を0に)する目的に使えます。特定のビットだけを反転して、他のビットは元の値のまま保つことができます。例えば、16進数の01234567について、奇数(1,3,5,7)の部分を排他的論理和で反転して出力するプログラムを書いてみてください。

bit4.c
#include <stdio.h>
 
int main(void) {
 
  printf("%08x\n", 0x01234567 ^ 0x0f0f0f0f);

  return 0;
}
実行結果
bit4.exe
0e2c4a68

16進数の01234567と0x000f0f00を、2進数とともに以下の表に示しました。各ビットに論理和を適用して、結果の012f4f67が得られることを確認してみてください。

16進数 2進数
01234567 0000 0001 0010 0011 0100 0101 0110 0111
0f0f0f0f 0000 1111 0000 1111 0000 1111 0000 1111
0e2c4a68 0000 1110 0010 1100 0100 1010 0110 1000

シフト演算

<<

<<は左シフトを行う演算子で、使い方は「a<<b」です(aとbは0以上の整数)。aをbビット(2進数でb桁)左にずらした値を計算します。aとbは0以上である(負数ではない)ことに注意してください。<<を使ってみましょう。16進数の1234を、左に0,4,8,12,16ビットシフトした結果を出力するプログラムを書いてみてください。

bit5.c
#include <stdio.h>
 
int main(void) {
 
  printf("%08x\n", 0x01234 << 0);
  printf("%08x\n", 0x01234 << 4);
  printf("%08x\n", 0x01234 << 8);
  printf("%08x\n", 0x01234 << 12);
  printf("%08x\n", 0x01234 << 16);

  return 0;
}
実行結果
bit5.exe
00001234
00012340
00123400
01234000
12340000

シフト演算

>>

>>は左シフトを行う演算子で、使い方は「a>>b」です(aとbは0以上の整数)。aをbビット(2進数でb桁)左にずらした値を計算します。aとbは0以上である(負数ではない)ことに注意してください。>>を使ってみましょう。16進数の1234を、左に0,4,8,12,16ビットシフトした結果を出力するプログラムを書いてみてください。

bit5.c
#include <stdio.h>
 
int main(void) {
 
  printf("%08x\n", 0x01234 >> 0);
  printf("%08x\n", 0x01234 >> 4);
  printf("%08x\n", 0x01234 >> 8);
  printf("%08x\n", 0x01234 >> 12);
  printf("%08x\n", 0x01234 >> 16);

  return 0;
}
実行結果
bit5.exe
00001234
00000123
00000012
00000001
00000000

整数型のビット数

整数型(整数を扱うための型)はintを、整数型にはintの他にも、サイズ(ビット数)が異なる次のような型があります。intはshort以上、longはint以上、long longはlong以上のサイズを持ちます。具体的なサイズは環境によって変化するので、C言語の仕様で決められているサイズとGCCのサイズを両方をまとめました。

読み方 C言語の仕様ビット数 GCCにおけるビット数
short ショート 16ビット以上 16ビット(2バイト)
int イント 16ビット以上 16ビット(4バイト)
long ロング 32ビット以上 32ビット(8バイト)
long long ロングロング 64ビット以上 64ビット(8バイト)

オーバーフロー

「オーバーフロー」とは,演算の結果がデータ型の範囲を超える(最大値より大きい,もしくは最小値より小さい)場合に発生する事象のことです。オーバーフローが発生すると、正常な整数演算の結果にならず、バグが発生してしまいます。また、最大値より大きくなるオーバーフローを「正のオーバーフロー」、最小値より小さくなるオーバーフローを「負のオーバーフロー」と呼びます。

符号なし整数型とオーバーフロー

C言語の型変換の仕様で、負数を符号なし整数型に変換するときには、表現できる数になるまで、2のN乗を加算し続けることになっています。Nには、変換後の型のビット数が代入されます。つまり、unsigned int型が32ビットだとすると、表現できない -1 という数に、2の32乗を加算します。unsigned int型は「0から4294967295」の値を表現できます。実際にオーバーフローさせてみましょう。「最小値 - 1」と「最大値 + 1」の計算結果を表示して確認します。

bit6.c
#include <stdio.h>
 
int main(void) {
 
  unsigned int a = 0;
  unsigned int b = 4294967295;

  printf("%u\n", a - 1);
  printf("%u\n", b + 1);

  return 0;
}
実行結果
bit6.exe
4294967295
0

Lesson 6 Chapter 4
演算と型

演算と型

C言語において、異なる型同士で計算を行うと「自動的型変換」が行われます。これは、型の優先順位に従って、型をコンピュータの方で自動的に変換してくれる機能です。型の優先度は次のような順位になります。右にある型ほど優先度が高くなります。

7-2_4.png

C言語における優先順位の基本的なルールは以下になります。
・浮動小数点数型が整数型よりも優先度が高い
・ビット数の多い型が優先度が高い

代入時の変換

変数への値の代入時、代入される値は自動的にその変数の型に変換されます。浮動小数点数型を整数型に代入した場合、小数点以下は切り捨てられます。

それでは実際のプログラムにおける型変換の動作を確認しましょう。int型の変数aとfloat型の変数bを宣言します。「a + b」の結果を変換指定子「%f」で、「aにbを代入」した結果を変換指定子「%d」で表示するコードを書いてください。

bit7.c
#include <stdio.h>
int main(void) {

  int a = 1;
  float b = 2.345;
  
  printf("%d\n", a + b);
  
  a = b;
  
  printf("%d\n", a);

  return 0;
}
実行結果
bit7.exe
3.345
2

C言語における優先順位のルールに従うと、int型とfloat型の場合、float型の方が優先度が高く設定されています。よって、変数aより優先度の高いfloat型へと型変換が行われます。「a + b」の式は「1 + 2.345」ではなく、

1.000 + 2.345

というfloat型同士の計算式に「暗黙的型変換」が行われます。そして、計算結果は「3.345」となり、float型の値になります。
一方、int型にfloat型を代入した結果を表示すると、結果は「2」となり、小数点以下は切り捨てられいることが分かります。

明示的型変換

型変換は自動で行われるもののほか、自分で変換を指定することもできます。

bit8.c
#include <stdio.h>
int main(void) {

  // 暗黙的型変換
  double value1 = 10 / 3;

  printf("%f\n", value1);

  // 明示的型変換
  double value2 = (double)10 / 3;

  printf("%f\n", value2);

  return 0;
}

上記のvalue1は、int型同士の計算なので結果もint型となります。その結果、小数点以下が切り捨てられ、数学的に正しい値は得られません。二番目のvalue2は、片方の数値の前に「(double)」という記述があります。このとき、この10はdouble型として扱われることになります。これを明示的型変換(キャスト)といいます。丸括弧の中にデータ型名を記述したものをキャスト演算子と言います。10がdouble型として扱われることで、double型とint型の計算となり、結果はdouble型となるため正しい計算結果を得られます。上のコードのように数値を直接書く場合は、「10」を「10.0」と書けば、double型として扱うことはできます。しかし変数の場合はこの書き方では対処できないため、明示的型変換が必要になります。

実行結果
bit8.exe
3
3.333333

情報落ち

有効桁数が5桁だった場合、「1.2345 + 0.00001」という計算を考えてみます。この計算を普通に行えば、結果は「1.23451」です。しかし有効桁が5桁しかないので、これは表現できません。丸めによって「1.2345」や「1.2346」といった近似値になってしまいます。結果が1.2346になったとすると、期待される「1.23451」 という結果に対して「0.00009」だけ誤差が生まれています。このような小さな数の加算が繰り返される場合、どうなるでしょうか。結果が「1.2345」になる場合だと「+0.00001」を100回繰り返したとしても、結果はやはり「1.2345」のままということになります。この現象を「情報落ち」と呼びます。「情報落ち」を緩和するには、加算する数同士の絶対値の差をできるだけ小さくすることです。例えば、「0.00001」を100回加算するのではなく「0.00001」を「100倍」して「0.001」という数を作り、これを1回だけ加算します。こうすると「1.2345 + 0.001」という計算になり、「1.2355」という結果が得られます。
実際に情報落ちが発生するコードを書いて計算結果を確認してみましょう。

bit9.c
#include <stdio.h>
int main(void) {

	// 情報落ちの確認
	float a = 1.234567;
	float b = 0.00000001;

	for (int i = 0; i <= 100; i++) {
		a += b;
	}
	printf("%f\n", a);

	return 0;
}
実行結果
bit9.exe
1.234567

上記のコードは有効桁数が7桁の「1.234567」に「+ 0.00000001」をfor文により100回行うプログラムになります。通常の計算では「1.234568」となるはずですが、実行結果が「1.234567」となっています。「情報落ち」により、誤差が生じていることが分かります。

Lesson 6 Chapter 5
列挙体

列挙体

「列挙体」(れっきょたい)は、値が整数や文字の定数をまとめて宣言できる機能です。宣宣言した定数は、constや#defineによる定数と同様に、計算や出力に使えます。「列挙体」は次のように宣言します。「列挙体」で宣言した定数は、列挙定数(れつきょていすう)と呼ばれます。名前を付けたい値の数が多い場合は、「列挙体」を使用すると便利です。

列挙体の宣言
enum {定数名, …};

定数名には左から順に0,1,2…のように、0から始まり1ずつ増える整数が割り当てられます。列挙型を使って定数SHOWA、HEISEI、REIWAを宣言し、各々の値を出力するプログラムを書いてみましょう。

enum.c
#include <stdio.h>
int main(void) {
  enum { SHOWA, HEISEI, REIWA };
  printf("SHOWA  : %d\n", SHOWA);
  printf("HEISEI : %d\n", HEISEI);
  printf("REIWA  : %d\n", REIWA);
  return 0;
}
enum.exe
SHOWA  : 0
HEISEI : 1
REIWA  : 2

以下のように書くと、定数に割り当てる値を指定できます。値を指定する定数と、値を指定しない定数を混在させることもできます。値を指定しなかった定数は、直前の定数の値に1を加算した値になります。

列挙体の宣言(値を指定)
enum {定数名=値, …};

列挙体を使って、SEPTEMBER (9月)、OCTOBER (10月)、NOVEMBER (11月)、JANUARY (1月)、FEBRUARY(2月)、MARCH (3月)という定数を宣言し、値を出力するプログラムを書いてみましょう。

enum2.c
#include <stdio.h>
int main(void) {
  enum { OCTOBER=10, NOVEMBER, DECEMBER, JANUARY=1, FEBRUARY, MARCH };
  printf("OCTOBER  : %d\n", OCTOBER);
  printf("NOVEMBER : %d\n", NOVEMBER);
  printf("DECEMBER : %d\n", DECEMBER);
  printf("JANUARY  : %d\n", JANUARY);
  printf("FEBRUARY : %d\n", FEBRUARY);
  printf("MARCH    : %d\n", MARCH);
  return 0;
}
enum2.exe
OCTOBER  : 10
NOVEMBER : 11
DECEMBER : 12
JANUARY  : 1
FEBRUARY : 2
MARCH    : 3