1.はじめに
1-1.記事の目的と対象者
今回の目的はプログラムの処理において重要な位置を占める変数・データ型の扱いについて理解し、プログラムを作成できるようになる事です。
今回の記事には下記の内容が含まれます。
- 基本的なデータ型
- 変数・定数の宣言方法
- 型の変換・キャストについて
前回の記事を読了済み、または既にC言語をコンパイルする環境が整っている方を対象としています。
また、簡易的な2進法の計算については理解していることを前提としています。
2.変数・定数について
2-1.変数とは
プログラムにおいて複雑な処理系をくみ上げるうえで、必ず重要になるのが「変数」です。
一般的な定義として、ある処理系f(X,Y)を定義したときのX,Yのことを変数といいます。
もう少し平たい言い方をするとZを決定するための要素のことを変数、といいます。
2-2.定数とは
定数については、「変動を想定しない数」をさします。例えば、物理計算におけるg(9.8)であったり、光速度c(3.0*10^8)であったりがそれにあてはまります。時代の流れからすると諸説あるかとは思いますが、消費税などもこれらに該当するでしょう。
2-3.変数・定数のデータ型
C言語においては変数・定数を扱う際に「型」を指定し宣言、初期化を行います。
型とは、「コンピュータ上のメモリを確保する範囲」と「コンピュータ上における表現方法」を規定するものであり整数、浮動小数点、倍精度浮動小数点、文字型などがあります。
種類 | 名前 | ビット数 | 表現可能な範囲 |
---|---|---|---|
int | 整数型 | 32bit | -2,147,483,648~2,147,483,647 |
float | 浮動小数点型 | 32bit | 1.175494351×10-38~3.402823466×1038 |
double | 倍精度浮動小数点型 | 64bit | 2.2250738585072014×10-308~1.7976931348623158×10308 |
char | 文字型 | 8bit | -128~127 |
~ここからはコンピュータ内部的な知識となりますので、面倒な方は3節まで読み飛ばしてください。~
整数の表現について
整数の場合は、メモリ上に32bitを確保し、先頭の符号ビット(1/0)と数字を表す31ビットの合計32ビットで数字を表現しています。コンピュータは各ビットを「0か1か」の状態としてしか管理できませんので、当然直接整数を扱うことはできません。そこで、コンピュータ側の解釈に合わせて数を「0,1の数字のみで表現」します。
0~9の数字を使用できるとき(10進法)、0,1,2,3,4,5,6,7,8,9,10といったように「表現可能な数字の最大値になったら桁上がりする」のが10進法の規則です。規則を同様に0,1の場合に当てはめると、0,1,10,11,100,101,110となります。これらの数字列に対して、先頭から順に10進数における整数をあてはめたものが「2進数」であり、コンピュータにおいてはこの規則で「整数」を表現しています。
00000000000000000000000000000000=0
00000000000000000000000000000001=1
00000000000000000000000000000010=2
00000000000000000000000000000011=3
負数を表す場合は、先頭の符号ビットを"1"に変換し、他の31ビットの0,1を反転したのち、1をたすという処理を施します。
- 1→-1への変換
00000000000000000000000000000001=1
↓32ビットを反転
11111111111111111111111111111110
↓下位ビットに1をたす
11111111111111111111111111111111=-1
なぜこのような面倒な手法を取るのかというと、「0の符号に関する複数表現が発生する」のを避けるためです。
※仮に先頭ビットの1/0のみで負/正を決定した場合、下記のような曖昧性が発生してしまいます。
10000000000000000000000000000000 -0?
00000000000000000000000000000000 +0?
しかし、上記の処理をしたものを「負数」として表現すると下記は負数の最低の値として解釈できるため、0の符号の問題がなくなります。
10000000000000000000000000000000(-2,147,483,648)
小数点の表現について
浮動小数点については、「符号1ビット」「指数部(2のn乗)」「仮数部(1<R<2の実数の小数部分を表記したもの)」の掛け合わせとして表現されます。
- ±1.23942×2n
小数部分は少数第一位から順に2-1,2-2,......というような形で表現され、これらの足し合わせによって少数を表現します。指数部は、仮数部に表現された数値に対して、小数点をどれだけ移動するかという数値であるため、移動する範囲として「n」の整数値がメモリに保管されます※この時、「符号ビットを使わずに」マイナス値を表現するために「バイアス値」が加算されます。(floatの場合は127)
例えば7.38という数を浮動小数点として表記すると下記のように記載できます。
ここで、問題となるのが「実数は離散値ではない」ということです。整数群は「離散値」であるため、ビット数による表現幅の問題はあるものの、特に表現に関する問題は発生しませんでした。しかし、コンピュータにおいて「離散値ではない」実数を表現する場合「有効桁数」の制約によりごく小さな値は扱えません(最下位ビットより小さな値)。また、(あまりやらない方がよいパターンですが)floatに整数を入れて整数値として取り扱う場合も、同様の理由により、floatの場合7桁を超えた段階で表現可能な値が飛び飛びとなる(精度が落ちる)ため、注意が必要です。
doubleについては、ビット数がより大きいため、より精度の高い計算が可能です。
3.変数を用いたプログラム
3-1.変数の宣言、値の代入
では、実際に変数値を用いてプログラムを作成していきましょう。例として、「int,float,double,char」を使って値を代入し、表示するプログラムを作成します。※前回説明済みのため詳細なコンパイル方法等については省略します。
まず、下記プログラムを作成し、コンパイルしてください。
#include<stdio.h>
int main(void){
/*変数の宣言*/
int naturalNumber;
float realNumber1;
double realNumber2;
char character;
/*値の設定*/
naturalNumber=5000;
realNumber1 = 5.256;
realNumber2 = 103.254;
character= 'a';
/*表示*/
printf("int:%d\r\n",naturalNumber);/*%d 整数値*/
printf("float:%f\r\n",realNumber1);/*%f 浮動小数点値*/
printf("double:%lf\r\n",realNumber2);/*%lf 倍精度浮動小数点値*/
printf("char:%c\r\n",character);/*%c 文字(Ascii文字)*/
}
変数は 「型 変数名」という形で宣言します。これにより、プログラムはコンピュータのメモリに対して当該の型に対応する領域を確保します。領域を確保した際、メモリの状態は「不定」であるため(コンパイラ仕様により自動的に0が入るようになっている場合もあります。)変数を使用する際は初期値を代入することで値を確定します。
代入の操作を行う際は「代入演算子」である、=を使います。=は、一般的な数学記号とは違い、左辺値に対して、右辺値の値を設定するための演算子です。従って、naturalNumber = n aturalNumber + 500というような、一見矛盾した記載も行うことが可能です。c言語における=は、等しいことを表す記号ではないことを注意してください。
では、プログラムを実際に実行してみましょう。
>gcc ./Example2-1.c -o Example2-1
>./Example2-1
int:5000
float:5.256000
double:103.254000
char:a
それぞれ、設定した値が出力されていることが確認できたかと思います。このように変数を用いることで、値を保持しておくことが出来ます。また、これらの変数に設定した値は、初期化したあとも値を変更することが可能です。
#include<stdio.h>
int main(void){
/*変数の宣言*/
int naturalNumber;
float realNumber1;
double realNumber2;
char character;
/*値の設定*/
naturalNumber=5000;
realNumber1 = 5.256;
realNumber2 = 103.254;
character= 'a';
/*表示*/
printf("int:%d\r\n",naturalNumber);/*%d 整数値*/
printf("float:%f\r\n",realNumber1);/*%f 浮動小数点値*/
printf("double:%lf\r\n",realNumber2);/*%lf 倍精度浮動小数点値*/
printf("char:%c\r\n",character);/*%c 文字(Ascii文字)*/
/*値の設定*/
naturalNumber=4000;
realNumber1 = 1.35644;
realNumber2 = 105.54354;
character= 'b';
/*表示*/
printf("int:%d\r\n",naturalNumber);/*%d 整数値*/
printf("float:%f\r\n",realNumber1);/*%f 浮動小数点値*/
printf("double:%lf\r\n",realNumber2);/*%lf 倍精度浮動小数点値*/
printf("char:%c\r\n",character);/*%c 文字(Ascii文字)*/
}
実行結果
> gcc Example2-2.c -o Example2-2
>./Example2-2
int:5000
float:5.256000
double:103.254000
char:a
int:4000
float:1.356440
double:105.543540
char:b
3-2.変数値の入力
変数を扱う際、利用者が入力したいケースがあるかと思います。
この場合、scanfやfgetsなどの入力を受け付ける関数を用いてキーボード入力を受け付けることとなります。
いっぱんに、入門書においてはscanfを用いて解説することが主流となっておりますが、いっぱんに本記事においては、scanfは使用せず、fgetsを用いて入力を受け付ける形とします。
キーボードから入力を得るプログラムの例は下記のとおりです。本プログラムはfgetsでキーボード入力を待ち受け、入力された値を整数値としてintegerNumberへ代入するプログラムです。
#include<stdio.h>
#include<stdlib.h>/*文字を変換するstrolを含むライブラリ*/
#include<errno.h>/*strolのエラー処理のために使用*/
#include<limits.h>/*int型への入力をいったんlong(64bit整数)で受け付けるため、limits.hで型ごとの上限値を使用する*/
#include<string.h>/*文字列処理のためのライブラリ*/
#define BUFFERSIZE 100
int main(void){
/*変数の宣言*/
int integerNumber;
/*入力用受付用の変数*/
char str[BUFFERSIZE];
long l;/*整数入力受付用の変数*/
char *end;/*不正文字の検出*/
/*値の初期化*/
integerNumber=0;
/*入力受付(整数値)*/
printf("Input IntegerNumber>");
if(fgets(str,sizeof(str),stdin)!=NULL){/*fgetsで入り込む改行文字を除去*/
if(str[strlen(str) - 1 ] == '\n'){
str[strlen(str) - 1] = '\0';
}
l= strtol(str,&end,10);/*入力した文字列を整数変換。エラーがある場合は対象の発生位置のアドレスを取得*/
if(*end != '\0'){/*入力に不正な文字が含まれているかどうかのチェック*/
printf("Error:Invalid Input\r\n");
return -1;
}else if(l ==LONG_MAX || l == LONG_MIN){/*整数値のレンジオーバーチェック*/
printf("Error:Out of Range\r\n");
return -1;
}else if(l >=INT_MAX || l <= INT_MIN){/*整数値のレンジオーバーチェック*/
printf("Error:Out of Range\r\n");
return -1;
}else{/*longを整数値(int)に変換して表示*/
integerNumber = (int)l;
printf("int:%d\r\n",integerNumber);
}
}else{
printf("入力エラーが発生しました。");
return -1;
}
}
実行結果
> gcc Example2-3.c -o Example2-3
> ./Example2-3
Input IntegerNumber>78907432
int:78907432
ただし、都度都度これを入力するのはあまり現実的ではないと思われますので、本記事でC言語を学習する皆様のために、ライブラリを用意しております。下記よりzipファイルをダウンロードし、解凍したのち、中身のファイル2つ(KeyInput.h,KeyInput.o)を作成するプログラムが入っているフォルダと同じフォルダへ入れてください。
では、上記のライブラリを用いて、整数・実数(float,double)・文字・文字列の入力を受け付けるプログラムを例示します。
#include<stdio.h>
#include"KeyInput.h"
int main(void){
/*変数の宣言*/
int integerNumber;
float naturalNumber;
double naturalNumber2;
char character;
char *str;/*文字列入力*/
/*初期化*/
integerNumber=0;
naturalNumber=0.0f;
naturalNumber2=0.0f;
character='\0';
str="";
/*入力受付*/
printf("Input IntegerNumber>");
if(input_int(&integerNumber) !=-1){
printf("int:%d\r\n",integerNumber);
}
printf("Input NaturalNumber>");
if(input_float(&naturalNumber) !=-1){
printf("float:%.9f\r\n",naturalNumber);
}
printf("Input NaturalNumber>");
if(input_double(&naturalNumber2) !=-1){
printf("double:%.19lf\r\n",naturalNumber2);
}
printf("Input Character>");
if(input_char(&character) !=-1){
printf("char:%c\r\n",character);
}
printf("Input Sentence>");
if(input_str(&str) !=-1){
printf("string:%s\r\n",str);
}
return 0;
}
では、こちらをコンパイルし、実行していきます。コンパイル方法については下記記載の通りに行ってください。
>gcc Example2-4.c KeyInput.o -o Example2-4
>./Example2-4
Input IntegerNumber>500
int:500
Input NaturalNumber>204.05
float:204.050003
Input NaturalNumber>4.4
double:4.400000
Input Character>c
char:c
Input Sentence>This is a sentence
string:This is a sentence
これで、キーボードからの入力受付ができるようになります。以降、
入力については同ライブラリを使用することとします。
C言語における大まかな入出力の仕組みの概略図は下記のとおりです。プログラムは標準入力を仲介してキーボードやファイルなどからの入力を受け付けます。逆に、画面やファイルにプログラムで処理した内容を出力したい場合には標準出力や標準エラーを仲介して結果を出力します。
これらをいっぱんに「標準入出力」といい、コンピュータ上の入出力インターフェイス(やファイル)とプログラムを仲介するための仕組みとなっています。
3-3.定数について
定数とは、変数とは違い、「定義後変更することのできない」数のことをいいます。
定義は、下記のような形で行います。
const double g = 9.8;
当該の数値は定義後の変更は行えず、プログラムはコンパイルエラーとなります。
#include<stdio.h>
int main(void){
/*変数の宣言*/
const double g = 9.8;
printf("%lf\r\n",g);
g=2.5;
printf("%lf\r\n",g);
return 0;
}
コンパイル結果
> gcc Example2-5.c -o Example2-5
Example2-5.c: In function ‘main’:
Example2-5.c:10:6: error: assignment of read-only variable ‘g’
10 | g=2.5;
| ^
例えば物理計算における重力定数など、プログラム上において変更を想定しない値を定義する際にconstを利用し、定数を定義してください。
3-3.文字型の実態
文字型は-128~127範囲の整数値として表現されています。C言語において厳密に「文字型」は存在せず、基本的にこの整数値に対応したAsciiコードを「文字」として取り扱います。したがって、下記のような形で、intに文字を代入することも実質的に可能です。但し、いっぱんに実装としてあまりよろしくないものであるため、文字を扱う際はcharを使用するようにしてください。(charとされているが実態は整数値である、ということだけ認識していただければと思います。)
#include<stdio.h>
int main(void){
/*変数の宣言*/
char c = 'a';
int i = 'b';
printf("Number:%d\r\n",c);
printf("Character:%c\r\n",c);
printf("Number:%d\r\n",i);
printf("Character:%c\r\n",i);
return 0;
}
実行結果
> gcc Example2-6.c -o Example2-6
> ./Example2-6
Number:97
Character:a
Number:98
Character:b
3-4.実数表現の限界
先述のとおり、コンピュータにおける実数値は「離散値」である特性上、誤差が生じる場合があります。doubleを使うことである程度防止できるものもありますが、基本的には「誤差の影響が少なくなる」のみです。金額計算など、誤差が許されない場合、丸め誤差に関しては整数型に置き換えて端数を案分する、などの処理も有効です。
丸め誤差
コンピュータが表現可能な範囲を超えた際(floatの場合7桁)、それ以下の有効桁数は捨てられてしまうために
正しい結果との間に誤差が発生します。従って、下記のようなケースでは設定した桁数(7桁)を表示しようとしても
表示した結果に誤差が生じます。前述のとおり、intはfloatよりも、long(64bit整数)はdoubleよりも表現できる桁数が多いため、整数への置き換えを行うことで誤差を極小まで低減できます。また、金額計算など、誤差が許されない場合は、整数を固定小数点とみなして計算し、あまりの分を案分するなどで、端数が発生しないように計算をします。それぞれ、用途や計算の内容に応じて、使い分けることをおすすめします。
#include<stdio.h>
int main(void){
/*変数の宣言*/
float c = 3.14159;
printf("%.5f\n",c);
c=3.141592;
printf("%.6f\r\n",c);
c=3.1415926;
printf("%.7f\r\n",c);
return 0;
}
>./Example2-7
3.14159
3.141592
3.1415925 ←141592以降の値が表現できていない。
#include<stdio.h>
#define FIXED 10000000
int main(void){
/*変数の宣言*/
long d=31415926;
long r2=20000000;
printf("radius:%ld.%07ld\r\n",r2 / FIXED,r2 % FIXED);
printf("pi:%ld.%07ld\r\n",d /FIXED,d%FIXED);
printf("menseki:%ld.%07ld\r\n",((r2 *r2)/FIXED) * d /FIXED/FIXED,((r2 *r2)/FIXED) * d /FIXED%FIXED);
return 0;
}
>./Example2-7sub
radius:2.0000000
pi:3.1415926
menseki:12.5663704
打ち切り誤差
例えば、無限に続く数の近似値を求める、などの計算をする場合、理想的には「無限回」の計算が必要ですが、当然、コンピュータリソースの都合から不可能であるため、どこかで計算を「打ち切る」必要があります。
この際に発生する誤差のことを打ち切り誤差といいます。※手計算でも同様に、基本的にはこのように無限に継続する数においては正確な計算が不可能であるため途中までの計算結果としての近似値を使うこととなります。基本的に完璧な値を算出することは不可能であるため、用途に応じて精度を決定することとなります。
#include<stdio.h>
#include<math.h>
int main(void){
/*変数の宣言*/
double x=2.0;
double epsilon=1e-10;
/*ルート2の近似値を算出*/
double y=1.0;
double error= x-y;
while (fabs(error) > epsilon){/*誤差範囲がepsilonより大きい場合は継続*/
x = ( x + y) / 2.0;
y = 2.0 / x;
error = x - y;
}
printf("%.15lf\n",x);
return 0;
}
> ./Example2-8
1.414213562374690 ←1.4142135637以降の値が異なる
桁落ち
ごく近い値同士で引き算を行った際などに、有効桁数が少なくなる現象です。
有効桁数が少なくなった場合、それより前の桁に含まれる誤差を扱うことができないため、下記のような誤差が発生します。2.123456 - 2.123455= 0.000001の計算そのものは正しいですが、それ以降の桁に誤差を含む場合、(2.123456 - 2.123455) * 10^7 = 9.5367というような直感と異なる結果が出てしまいます。
#include<stdio.h>
#include<math.h>
int main(void){
/*変数の宣言*/
float x=2.123456;
float y=2.123455;
printf("2.123456 - 2.123455= %.6lf\r\n",x-y);
printf("(2.123456 - 2.123455) * 10^7 = %.4lf\r\n",(x-y)*1e7);
}
> ./Example2-9
2.123456 - 2.123455= 0.000001
(2.123456 - 2.123455) * 10^7 = 9.5367
情報落ち
絶対値の大きな値と、ごく小さな値を加算した際に、小さな値が無視される現象です。
下記のような形で、106に対して10-4をたした場合、大きい数字側で表現できる桁の範囲外となってしまい、小さい数字の加算が反映されなくなります。したがって、例えば、「少しずつ加算する」ような処理を作る場合、「小さい数字どうしでたしあわてから計算する」などの工夫が必要となります。
#include<stdio.h>
#include<math.h>
int main(void){
/*変数の宣言*/
float x=1e6;
float y=1e-4;
printf("10^6 + 10^-4= %.6lf\r\n",x+y);
return 0
}
> ./Example2-10
10^6 + 10^-4= 1000000.000000
3-5.型の変換・キャスト
違う型どうしの計算を扱う際、型の変換が行われます。従って、実数値と整数値でも計算結果
型の変換には「暗黙的変換」と「明示的変換」があり、前者に関しては明記しなくても変換されます。具体的には、doubleとfloatで計算をした場合にはdoubleに、intとlongで計算した場合にはlongに収束するような形となります。しかし、intどうしで計算を行ってlongやdoubleに代入する場合などにおいては型変換が行われないため、明示的に計算結果がdoubleやfloatであることを記述する必要があります。その際、(型名) というような表記をして明示的に型変換を行います。この型変換のことをキャストといい、キャストするための演算子をキャスト演算子といいます。例えば、intどうしで大きな数の掛け算を行う場合、longへ格納する必要がありますので(bit数が足りずオーバーフローを起こすため)、基本的にこういった意図をもってキャストを行う必要があります。逆に、意図がない(とりあえずキャストしておく)といったような使い方は危険な作りこみとなる可能性がありますので、極力避けてください。
double > float > long >int >short > char
#include<stdio.h>
#define FIXED 10000000
int main(void){
/*変数の宣言*/
int a=2000000,b=20000000;
long c,d;
double e,f;
/*キャストあり/なしの計算結果比較*/
c= (long)a*b;
d= a*b;
printf("%ld\r\n",c);
printf("%ld\r\n",d);
e=(double)1/3;
f=1/3;
printf("%lf\r\n",e);
printf("%lf\r\n",f);
}
実行結果
>./Example2-5
40000000000000
969572352 ←intの範囲内での計算結果が出力される
0.333333
0.000000 ←int計算として解釈されるため0となる。
4.まとめ
以上、C言語入門第2回変数とデータ型でした。
本日は下記の内容を説明させていただきました。
- 変数と定数
- 変数の定義方法・代入について
- 変数とデータ型
- 実数の取り扱い・誤差について
- char型と文字について
- キャスト演算子・型変換について
5.次回予告
次回は、演算子・制御構造について学びます。今回学習した変数・定数を用いて、計算を行う場合の規則や、値に応じての場合分けをする方法などを解説します。これらを学習することで、プログラムを構築するうえでのおおよその基盤は出来上がります。
次回もお楽しみに!一緒にC言語の世界を深く掘り下げていきましょう。