今回はC言語で「負の整数」を10進数から2進数へ変換する方法やプログラムについて解説していきたいと思います。
負の整数を2進数に変換するためには、まず正の整数を変換する方法を理解しておく必要があります。正の整数を2進数変換する方法をご存知無い方は、まずは下記のページを読んでいただければと思います!
【C言語】10進数から2進数への変換(正の整数編)実は、C言語の場合は正の整数を2進数変換するプログラムで負の整数を2進数変換できてしまうことがあります。
例えば下記は「正の整数」を2進数変換することを目的としたプログラムです。その証拠に負の整数が扱えないように、全て変数の方は unsigned int
になっています。
#include <stdio.h>
#define MAX_BIT 32
int main(void) {
unsigned int x; /* 10進数格納用 */
unsigned int bin[MAX_BIT]; /* 2進数格納用 */
unsigned int n;
unsigned int i;
/* 10進数を格納 */
x = -123456789;
/* 第0桁から値を算出していく */
n = 0;
while (n < MAX_BIT) {
/* 剰余算で2進数の第n桁を算出 */
bin[n] = x % 2;
/* 除算で第n桁目を切り捨て */
x = x / 2;
/* 次の桁へ */
n++;
}
/* 求めた2進数を表示 */
for (i = 0; i < MAX_BIT; i++) {
/* 第i桁を表示 */
printf("%u", bin[MAX_BIT - 1 - i]);
}
printf("\n");
return 0;
}
しかしこれでも、2進数に変換したい値を格納する変数 x
に負の値を格納すれば、正常に2進数変換ができてしまうのです。
なんか不思議…
たまたま変換できてるだけじゃ無いの…?
このページでは、単に2進数変換を行う方法だけでなく、上記のようなソースコードでなぜ負の整数の2進数変換が行えるのかについても解説していきたいと思います。
Contents
負の整数の2進数への変換の基本的な考え方
では負の整数を10進数から2進数へ変換する方法の考え方を整理していきたいと思います。
正の整数と負の整数の関係
まずは正の整数と負の整数の関係について考えていきましょう。
例えば -100
とはどのような整数でしょうか?この質問にはいろんな答えがあると思いますが、その答えの一つは下記になります。
100
に足し合わせると0
になる整数
どんな正の整数にも上記のような関係を満たす負の整数が存在します。
例えば正の整数を 87
とすると、下記の式を満たす x
が 87
に対する負の整数になります。
87 + x = 0
当然この式を見ると x = -87
であることがすぐに分かりますね!
こんな感じで、負の整数とは「その負の整数の符号を反転したもの」と足し合わせた時に結果が 0
となる数であると考えることができます。
スポンサーリンク
2進数の負の整数とは
では、次は2進数で考えてみましょう。
2進数においても10進数と同じで、正の2進数に対して足し合わせることで 0
になる2進数が存在します。
例えば、正の整数を 01010111
とすると、下記の式を満たす2進数 x
が 01010111
に対する負の整数になります。
01010111 + x = 00000000 (= 0)
01010111
は10進数で 87
ですので、x
は -87
を2進数に変換した数になります。つまり、上記の式を満たす x
を求めることができれば -87
の2進数変換が行えたことになります。
では、この式を満たす x
は2進数でいくつになるでしょうか?
すぐ思いつくのは -01010111
だと思います。ただ、2進数ではマイナス符号は使えない(演算としての減算は存在する)ので、マイナス符号を使わないで x
を求めてください。
うーん、そんな x
存在しないんじゃ無い?
どんな x
を足しても 01010111
よりも小さい数にはならないよ…
正解!
実は、ここまで示した情報だけだとそんな x
は存在しないんだ
そうなんです。実はそんな x
は存在しないんです。
どんな x
を足したとしても絶対に 01010111
よりも小さい数にはなりません。到底足し算結果が 0
になる x
なんて見つけられないんです。
ここで、下記の条件を加えたいと思います。
- 2進数の桁数は
8
桁 - 桁上がりで
8
桁を超えた場合、その桁上がりは捨てられる
例えば足し算結果が 100000000
になった場合、最上位の桁は捨てられるので結果は 00000000
になります。
これなら分かるよ!
10101001
だ!
つまり、-87
を2進数変換した結果は 10101001
ってことだね!
正解!
バッチリ足し合わせた時の計算結果も 0
になってるね!
桁数を決めることで無事に負の値を2進数に変換することが出来ましたね!
求め方の詳細は次の2の補数を計算して2進数変換を行うで後述するとして、ここで「負の整数を10進数から2進数に変換する」とはどういうことなのかを整理しておきたいと思います。
負の整数を2進数に変換するとは、その負の整数を符号反転した整数に対して「足し合わせた時に 0
になる2進数」を求めることになります。
符号反転とは、単純に ± を逆転させることです。
ここで重要なのは「2進数の桁数が決められていないとそのような値は見つけられない」ことです。
そして「桁あふれ(足し算で桁数が超えた部分を捨て去ること)を考慮する」ことで初めて負の整数の2進数を求めることができるという点も重要です。
また、負の整数を2進数変換した結果は桁数によって異なります。
例えば同じ-87
でも桁数が異なると下記のように2進数変換した結果も異なります。
桁数 | 2進数 |
8 | 10101001 |
10 | 1110101001 |
14 | 11111110101001 |
16 | 1111111110101001 |
なので、負の整数を2進数に変換する時はとにかく「桁数」が重要です。
基本的に負の整数を2進数に変換する問題では桁数が明示されるはずなので、その桁数を意識して解くようにしましょう!
特にコンピュータの世界では、この桁数をビット数と呼ぶことが多いです
2の補数を計算して2進数変換を行う
考え方を整理したところで、次は実際の変換方法を解説していきたいと思います。
2の補数
前述の通り、負の正数を2進数に変換するとは、その負の整数を符号反転した整数に対して「足し合わせた時に 0
になる2進数」を求めることになります。
このような、ある2進数に足し合わせた結果が 0
になる2進数を、その2進数に対する「2の補数」と呼びます。
つまり、負の整数の10進数を2進数に変換するためには、下記を行えば良いことになります。
- 負の整数の符号を反転させる(正の整数になる)
- その正の整数の2進数を求める
- その2進数に対する「2の補数」を求める
スポンサーリンク
基本的な2の補数の求め方
では2の補数はどうやって求めれば良いでしょうか?
この2の補数は、正の整数の2進数変換さえできれば簡単に求めることができます。87
の例と一緒に2の補数を求める手順を確認していきましょう。
2の補数を求める一番基本的な手順は下記のようになります。
- 整数を2進数変換する
- 各桁の
0
と1
を反転する - 反転後の2進数に
1
を足す
整数を2進数変換する
まず整数を2進数に変換します。
87
を8桁の2進数に変換すると 01010111
になります。
各桁の 0
と 1
を反転する
続いてその2進数と足算することで、全桁が 1
となるような2進数を用意します。
これは、整数を2進数変換した結果の「各桁の 0
と 1
を反転させる」ことで求めることができます。
87
を8桁の2進数に変換した結果は 01010111
ですので、ここでは 10101000
を用意することになります。
これにより、用意した2つの2進数を足し合わせることで、全桁が 1 になります。
反転後の2進数に 1
を足す
次に、後から用意した方の2進数に 1
を足します。
先ほど用意した2進数は 10101000
ですので、1
を足すことで 10101001
が求まることになります。
これにより2つ2進数の和が 0
になります(桁あふれが発生するので)。
つまり、後から用意した2進数は、最初に用意した整数に対する2の補数になります。
87
を8桁の2進数に変換した結果は 01010111
ですので、これに 10101001
を足せば、その結果は (1)00000000
になります。括弧内の 1
は桁あふれが発生するので捨て去られるので、足し算結果は結局 00000000
になります。
ですので、87
に対する2の補数は 10101001
となります。すなわち、-87
を8桁の2進数に変換した結果は 10101001
です。
プログラム1
ここまで解説してきた「2の補数を計算して2進数変換を行う」方法で整数の10進数を2進数に変換するプログラムのソースコードは下記のようになります。
変換後の2進数の桁数は 32 桁としています。
#include <stdio.h>
/* 2進数の桁数 */
#define MAX_BIT 32
int main(void) {
int x; /* 10進数格納用 */
unsigned int pos_x; /* xを正の値に変換した値格納用 */
unsigned int pos_bin[MAX_BIT]; /* 正の値の2進数格納用 */
unsigned int bin[MAX_BIT]; /* 2進数格納用 */
unsigned int n;
unsigned int i;
unsigned int c; /* 桁上がり格納用 */
unsigned int b;
/* 変換したい10進数を格納 */
x = -123456789;
/* 負の値の場合は符号を反転 */
if (x >= 0) {
pos_x = x;
} else {
pos_x = -x;
}
/**** 正の整数の2進数変換 ****/
/* 第0桁から値を算出していく */
n = 0;
while (n < MAX_BIT) {
/* 剰余算で2進数の第n桁を算出 */
pos_bin[n] = pos_x % 2;
/* 除算で第n桁目を切り捨て */
pos_x = pos_x / 2;
/* 次の桁へ */
n++;
}
if (x >= 0) {
/* xが正の整数の場合はbinにそのまま格納 */
for (i = 0; i < MAX_BIT; i++) {
bin[i] = pos_bin[i];
}
} else {
/* xが負の整数の場合はpos_binの2の補数をposに格納 */
c = 0;
for (i = 0; i < MAX_BIT; i++) {
/* 0/1を反転 */
if (pos_bin[i] == 1) {
b = 0;
} else {
b = 1;
}
if (i == 0) {
/* 第0桁には1を足す */
bin[i] = 1 ^ b;
c = 1 & b;
} else {
/* 第0桁以外は下位の桁の桁上がりを考慮して計算 */
bin[i] = c ^ b;
c = c & b;
}
}
}
/* 求めた2進数を表示 */
for (i = 0; i < MAX_BIT; i++) {
/* 第i桁を表示 */
printf("%u", bin[MAX_BIT - 1 - i]);
}
printf("\n");
return 0;
}
x
の正の整数の場合は、通常の正の整数の2進数変換のみを行うようにしています。
MAX_BIT
は求める2進数の桁数になります。
このページで紹介する他のプログラムでも同様に MAX_BIT
を変更することで求める2進数の桁数を変更することができます
また、2の補数の計算ではビット演算を利用しています。
ビット演算については下記ページで解説していますし、
C言語のビット演算(論理演算)について解説ビット演算を利用した足し算については下記ページで解説していますので、何をやっているのかよくわからない方はこの辺りのページを参考にしていただければと思います。
論理演算(ビット演算)を使って四則演算を行う方法を解説その他の2の補数の求め方
プログラム1だと、2進数を配列に格納してから2の補数を求める必要がありました。
各桁の桁上がりを考慮しながら演算を行う必要があるのでちょっと難易度が高くなっています。
次は2の補数の求め方を工夫して、もうちょっとシンプルにプログラミングできるようにしてみましょう!
2の補数の一番基本的な求め方の手順は、前述の通り下記の通りです。
- 整数を2進数変換する
- 各桁の
0
と1
を反転する - 反転後の2進数に
1
を足す
ですが、2の補数は下記の手順でも求めることが可能です。
- 整数から
1
を引く - 引いた後の整数を2進数変換する
- 各桁の
0
と1
を反転する
2進数変換後は各桁を反転すれば良いだけなのでプログラムが簡単になります。
スポンサーリンク
プログラム2
上記の手順で2進数変換を行うプログラムは下記のようになります。
#include <stdio.h>
/* 2進数の桁数 */
#define MAX_BIT 32
int main(void) {
int x; /* 10進数格納用 */
unsigned int pos_x; /* xを正の値に変換した値格納用 */
unsigned int pos_bin[MAX_BIT]; /* 正の値の2進数格納用 */
unsigned int bin[MAX_BIT];/* 2進数格納用 */
unsigned int n;
unsigned int i;
/* 変換したい10進数を格納 */
x = -123456789;
/* 負の値の場合は符号を反転 */
if (x >= 0) {
pos_x = x;
} else {
pos_x = -x;
/* 事前に -1 する */
pos_x = pos_x - 1;
}
/**** 正の整数の2進数変換 ****/
/* 第0桁から値を算出していく */
n = 0;
while (n < MAX_BIT) {
/* 剰余算で2進数の第n桁を算出 */
pos_bin[n] = pos_x % 2;
/* 除算で第n桁目を切り捨て */
pos_x = pos_x / 2;
/* 次の桁へ */
n++;
}
if (x >= 0) {
/* xが正の整数の場合はbinにそのまま格納 */
for (i = 0; i < MAX_BIT; i++) {
bin[i] = pos_bin[i];
}
} else {
/* xが負の整数の場合はpos_binを反転してposに格納 */
for (i = 0; i < MAX_BIT; i++) {
/* 0/1を反転 */
if (pos_bin[i] == 1) {
bin[i] = 0;
} else {
bin[i] = 1;
}
}
}
/* 求めた2進数を表示 */
for (i = 0; i < MAX_BIT; i++) {
/* 第i桁を表示 */
printf("%u", bin[MAX_BIT - 1 - i]);
}
printf("\n");
return 0;
}
結果はプログラム1のものと同じになります。ですが、2進数の足し算を行わなくて良くなったので、幾分かプログラムが簡単になったと思います。
ビット演算を利用して2進数変換を行う
続いてビット演算を利用して負の整数の10進数を2進数に変換する方法について解説します。
コンピュータ内部で扱われるデータ
下記ページで解説しているように、コンピュータ内部で扱われるデータは全て 0
もしくは 1
になります。
例えば、下記のように変数に値を代入した場合、ソースコードとしては 87
を格納していますが、実際のプログラム実行時にはこの変数に2進数の 01010111
が格納されることになります。
char x;
x = 87; /* 01010111を格納 */
ここで、変数に格納される2進数の桁数は変数の型のサイズ(ビット数)によって決まります。
1ビットは2進数1桁分を表現することができる情報の単位になります。
char
型のサイズは1バイトであり、ビット数で考えると8ビットですので、この型の変数に格納される2進数は8桁になります。
スポンサーリンク
負の整数も各桁の値を調べるだけで2進数変換できる
では変数に負の整数を格納するとどのような2進数が格納されるでしょうか?
実は、この時に格納される2進数は「負の整数の符号を反転した整数」に対する2の補数が格納されることになります。
char x;
x = -87; /* 10101001を格納 */
ですので、わざわざ自分で2の補数を計算しなくても、正の整数のときと同様に、ビット演算で各桁の値を調べるだけで2進数変換することができます。
プログラム
ここまで解説してきた「ビット演算を利用して2進数変換を行う」方法で整数の10進数を2進数に変換するプログラムのソースコードは下記のようになります。
変換後の2進数の桁数は 32 桁としています。
#include <stdio.h>
#define MAX_BIT 32
int main(void) {
int x; /* 10進数格納用 */
unsigned int bin[MAX_BIT]; /* 2進数格納用 */
unsigned int n;
unsigned int i;
unsigned int a; /* 特定の桁のみ1の値格納用 */
/* 10進数を格納 */
x = -123456789;
/* 第0桁から値を算出していく */
n = 0;
while (n < MAX_BIT) {
/* 第n桁のみ1の値を算出 */
a = (unsigned int)1 << n;
/* &演算で第n桁の値取得 */
if ((x & a) == 0) {
bin[n] = 0;
} else {
bin[n] = 1;
}
/* 次の桁へ */
n++;
}
/* 求めた2進数を表示 */
for (i = 0; i < n; i++) {
/* 第i桁を表示 */
printf("%u", bin[n - 1 - i]);
}
printf("\n");
return 0;
}
ほぼ正の整数の10進数から2進数変換を行うプログラムと同じです。
ビット演算を利用した正の整数を2進数変換するプログラムは下記ページで紹介しています。
【C言語】10進数から2進数への変換(正の整数編)そのプログラムと上記のプログラムの違いは、10進数を格納する変数 x
の型を負の整数を扱えるように unsigned int
から int
に変換したことと、x
に格納する値を負の整数にしただけです。
int x; /* 10進数格納用 */
〜略〜
/* 10進数を格納 */
x = -123456789;
負の整数は変数には2の補数として格納されることを理解しておけば、上記のように正の整数同様に10進数から2進数への変換を行うことができます。
使用しているビット演算やシフト演算の詳細は下記ページを参考にしていただければと思います。
【C言語】10進数から2進数への変換(正の整数編)型の性質を利用して2進数変換を行う
コンピュータ内部で扱われるデータが2進数であること、さらに負の整数が2の補数として扱われることを利用すれば、他の方法で2進数変換することも可能です。
いきなりですが、下記のプログラムは正常に負の整数の10進数を2進数に変換することはできるでしょうか?
#include <stdio.h>
#define MAX_BIT 32
int main(void) {
int x; /* 10進数格納用 */
unsigned int bin[MAX_BIT]; /* 2進数格納用 */
unsigned int n;
unsigned int i;
/* 10進数を格納 */
x = -87;
/* 第0桁から値を算出していく */
n = 0;
while (n < MAX_BIT) {
/* 剰余算で2進数の第n桁を算出 */
bin[n] = x % 2;
/* 除算で第n桁目を切り捨て */
x = x / 2;
/* 次の桁へ */
n++;
}
/* 求めた2進数を表示 */
for (i = 0; i < MAX_BIT; i++) {
/* 第i桁を表示 */
printf("%u", bin[MAX_BIT - 1 - i]);
}
printf("\n");
return 0;
}
x
に正の整数を格納した場合は正しく10進数から2進数に変換することができます。
では上記のように x
に負の整数は格納した場合はどうでしょう?
実行結果は下記のようになります。
00000000000000000000000004294967295042949672950429496729542949672954294967295
#include <stdio.h>
#define MAX_BIT 32
int main(void) {
unsigned int x; /* 10進数格納用 */
unsigned int bin[MAX_BIT]; /* 2進数格納用 */
unsigned int n;
unsigned int i;
/* 10進数を格納 */
x = -87;
/* 第0桁から値を算出していく */
n = 0;
while (n < MAX_BIT) {
/* 剰余算で2進数の第n桁を算出 */
bin[n] = x % 2;
/* 除算で第n桁目を切り捨て */
x = x / 2;
/* 次の桁へ */
n++;
}
/* 求めた2進数を表示 */
for (i = 0; i < MAX_BIT; i++) {
/* 第i桁を表示 */
printf("%u", bin[MAX_BIT - 1 - i]);
}
printf("\n");
return 0;
}
もっとダメじゃね?
unsigned int
型の x
に負の整数を格納しちゃってるじゃん
上記ソースコードのプログラムを実行すると次のように結果が表示されます。
11111111111111111111111110101001
上手く負の整数が2進数変換されてる!
な、なぜ…?!
このプログラムが上手く動作する理由は型にあります。この辺りを解説していきます。
スポンサーリンク
符号ありの型と符号なしの型
C言語における整数型には「符号ありの型」と「符号なしの型」が存在します。
符号あり | 符号なし |
char |
unsigned char |
int |
unsigned int |
short |
unsigned short |
long |
unsigned long |
符号ありの型では、その型における最上位の桁(最上位ビット)が符号として扱われ、0
の場合は正の整数、1
の場合は負の整数を表現することになります。
他の桁は値を表します。
一方で符号なしの型では、全桁が値を表します。符号を表す桁はありません。
これはつまり、最上位の桁が 1
の場合、つまり負の整数の場合、同じ2進数であっても「2つの10進数」を表すことができるということになります。
具体的には、符号ありで考えた場合と符号なしで考えた場合の2つの10進数で表すことができます。
前者の場合は負の整数、後者の場合は正の整数を表すことになります。
符号なし型への負の値の代入
ここで下記の変数への値の格納の動作を考えてみましょう。
int x;
unsigned int y;
x = -87;
y = -87;
まず、ポイントになるのは、コンピュータ内部では 0
と 1
のデータのみ扱う点です。
つまり、右辺の -87
は 10101001
として扱われることになります(桁数は8として考えてます)。
そして、この 10101001
が x
にも y
にも格納されることになります。
変数 x
は符号ありの型である char
型ですので、10進数で考えると -87
、変数 y
は符号なしの型である unsigned char
型ですので、10進数で考えると 169
となります。
つまり、負の整数を符号なしの型の変数に代入をした場合、「その負の整数と同じ2進数の正の整数」を求めることができます。
負の整数と同じ2進数の正の整数が求めることができたのであれば、後は正の整数を2進数変換すれば、負の整数の2進数変換が行えることになります。
ただし、負の整数を符号なしの型の変数に代入するようなソースコードは、ぱっと見だと間違ったソースコードのようにも見えてしまいます。
もしこのような代入を行う場合は、ちゃんと意図した代入であることを読み手に理解させるためのコメントを書いておいたほうが良いと思います。
プログラム
ここまで解説してきた考え方で2進数変換を行うプログラムは下記になります。
#include <stdio.h>
#define MAX_BIT 32
int main(void) {
unsigned int x; /* 10進数格納用 */
unsigned int bin[MAX_BIT]; /* 2進数格納用 */
unsigned int n;
unsigned int i;
/* 10進数を格納 */
x = -123456789;
/* 第0桁から値を算出していく */
n = 0;
while (n < MAX_BIT) {
/* 剰余算で2進数の第n桁を算出 */
bin[n] = x % 2;
/* 除算で第n桁目を切り捨て */
x = x / 2;
/* 次の桁へ */
n++;
}
/* 求めた2進数を表示 */
for (i = 0; i < MAX_BIT; i++) {
/* 第i桁を表示 */
printf("%u", bin[MAX_BIT - 1 - i]);
}
printf("\n");
return 0;
}
下記で unsigned int
型の変数 x
に負の整数を格納していますが、実際にはここで格納される値は負の整数と同じ2進数の正の整数です。
x = -123456789;
あとはその正の整数の2進数変換を行っているだけです。
ちなみに x
の型を符号ありの int
にしてしまうと上手く動きません。この章の最初に紹介した「上手く2進数変換できない例」でも紹介しましたね!
int
型にしてしまうと、負の値が格納された時にその後の演算が負の値であることを考慮して演算が行われて話が合わなくなります。
例えば下記では負の値に対して剰余算が行われますので、結果も負の値になってしまいます。
そうなると2進数で扱える 0
と 1
以外の値が配列に格納されてしまうことになってしまい、結果が意図しないものになってしまいます。
/* 剰余算で2進数の第n桁を算出 */
bin[n] = x % 2;
スポンサーリンク
まとめ
このページでは負の整数を2進数変換する方法について解説しました。
負の整数変換を行う方法としては大きく3つの方法を紹介しました。
基本の変換方法は2の補数を求めるやり方です。演算さえできれば実装することができます。
ただし、変数には負の値が2の補数の形式の2進数が格納されていることを知っていれば、単にビット演算で各桁の値を調べる方法でも2進数変換を行うことが可能です。
また、符号ありと符号なしの型の性質を理解していれば、正の整数変換さえできれば負の整数変換も行うこともできます。
こんな感じで、特にC言語の場合は、コンピュータでどのようにデータが扱われているかを理解しておくとプログラミングに役立つことが多いです。
「コンピュータでのデータの扱い方を理解する」ためにも、このページで紹介した2つ目と3つ目に紹介した実装方法も是非理解しておいてください!
オススメの参考書(PR)
C言語学習中だけど分からないことが多くて挫折しそう...という方には、下記の「スッキリわかるC言語入門」がオススメです!
まず学習を進める上で、参考書は2冊持っておくことをオススメします。この理由は下記の2つです。
- 参考書によって、解説の仕方は異なる
- 読み手によって、理解しやすい解説の仕方は異なる
ある人の説明聞いても理解できなかったけど、他の人からちょっと違った観点での説明を聞いて「あー、そういうことね!」って簡単に理解できた経験をお持ちの方も多いのではないでしょうか?
それと同じで、1冊の参考書を読んで理解できない事も、他の参考書とは異なる内容の解説を読むことで理解できる可能性があります。
なので、参考書は2冊持っておいた方が学習時に挫折しにくいというのが私の考えです。
特に上記の「スッキリわかるC言語入門」は、他の参考書とは違った切り口での解説が豊富で、他の参考書で理解できなかった内容に対して違った観点での解説を読むことができ、オススメです。題名の通り「なぜそうなるのか?」がスッキリ理解できるような解説内容にもなっており、C言語入門書としてもかなり分かりやすい参考書だと思います。
もちろんネット等でも色んな観点からの解説を読むことが出来ますので、分からない点は別の人・別の参考書の解説を読んで解決していきましょう!もちろん私のサイトも参考にしていただけると嬉しいです!
入門用のオススメ参考書は下記ページでも紹介していますので、こちらも是非参考にしていただければと思います。
https://daeudaeu.com/c_reference_book/