このページでは、10進数を N 進数に変換するプログラムの紹介及びプログラムの解説を行います。
今回紹介するプログラムでの変換では、10進数は “正の整数” にしか対応していないのでご了承ください(0 にも対応しています)。
また、N 進数とは具体的に “2進数 〜 36進数” になります。
36進数までにしているのは、数字の数+アルファベットの数が36個だからです。アルファベットを大文字 / 小文字で区別したりすれば37進数以降も表現できるのですが、今回は36進数までとしたいと思います。
10進数から N 進数への変換は、基本的には10進数から2進数へ変換する際と同じ考え方で行うことができます。2を N に置き換えて考えてやれば良いだけです。
10進数から2進数への変換については下記で詳しく解説していますので、2進数への変換方法を詳しく知りたい方は下記ページをご覧ください。
【C言語】10進数から2進数への変換(正の整数編)このページでは、上記ページの剰余算と除算を繰り返す方法で解説している内容を N 進数に置き換えて考えることで、10進数から N 進数への変換を行っていきたいと思います。
Contents
10進数から \(N\) 進数に変換する方法
まずは10進数を \(N\) 進数に変換する方法について解説しておきます。
この方法を利用すれば、\(N\) が \(2\) 以上の整数全てのケースにおいて、\(N\) 進数への変換を行うことが可能です。
\(N\) 進数への変換:剰余算と除算の繰り返し
結論としては、10進数から \(N\) 進数への変換は、\(N\) 進数に変換したい値 \(x\) に対して下記のように剰余算(%
)と除算(/
)を繰り返すことで実現することができます。
i = 0
とするx % N
を \(N\) 進数変換結果の第i
桁とするx = x / N
とするi = i + 1
とするx > 0
であれば 2. に戻る
上記においては、第 0
桁を数値の最下位の桁としています。つまり、下位の桁から順に \(N\) 進数に変換した結果を求めていることになります。
スポンサーリンク
剰余算と除算の繰り返しで変換できる理由
続いては、上記のような、剰余算と除算の繰り返しにより10進数から \(N\) 進数に変換できる理由を解説しておきます。
理由に興味のない方は10進数から N 進数への変換プログラムをクリックして、10進数から N 進数への変換プログラムまでスキップしていただければと思います。
10進数は 係数 * N の累乗
の和で表すことができる
まず前提として、全ての正の整数(\(0\) も含めて)の10進数は、下記のように 係数 * N の累乗
の和によって表すことができます(画面小さいとはみ出るかも…)。
\(10進数 = a_{M – 1} \times N^{M-1} + a_{M – 2} \times N^{M-2} + ・・・+ a_{1} \times N^{1} + a_{0} \times N^{0} \)
係数
の並びが \(N\) 進数への変換結果となる
上記の式が成立するような係数 \(a_{M – 1}\) 〜 \(a_{0}\) の組み合わせは実はたくさん存在します。
ですが、下記の条件を加えると、係数 \(a_{M – 1}\) 〜 \(a_{0}\) の組み合わせは必ず1パターンのみとなります。
- 係数 \(a_{M – 1}\) 〜 \(a_{0}\) は \(0\) 〜 \(N – 1\) の整数である
さらに、その時の “\(a_{M – 1}\) 〜 \(a_{0}\) の並び” が、10進数を \(N\) 進数に変換した結果となります。
ですので、この係数を求めることができれば、10進数からの \(N\) 進数への変換を実現することができます。
例えば10進数の \(103\) を9進数に変換することを考えてみましょう。
この場合、上記の式において \(N = 9\) とした時の各項の係数を求めることで、10進数から9進数への変換を実現することができます。
\(103 = a_{M – 1} \times 9^{M-1} + a_{M – 2} \times 9^{M-2} + ・・・+ a_{1} \times 9^{1} + a_{0} \times 9^{0} \)
\(103\) は \(1 \times 81 + 2 \times 9 + 4 \times 1\) で表すことができますので、\(103\) を 係数 * 9 の累乗
の和の形で表せば下記のようになります。
\(103 = 1 \times 9^{2} + 2 \times 9^{1} + 4 \times 9^{0} \)
さらに、上式の係数を左から順に並べたものが10進数 \(103\) を9進数に変換した結果となります。
つまり、10進数 \(103\) を9進数に変換すると \(124\) になります。
こんな感じで、係数 * N の累乗
の和で表した時の各項の 係数
を求めさえすれば、10進数から \(N\) 進数への変換を行うことができます。
剰余算により \(N^{0} \) の項の 係数
が求められる
では、その各項の 係数
はどうやって求めれば良いでしょうか?
これは、剰余算と除算により求めることができます。
まず下記の式において、\(N\) での剰余算を行うことを考えてみましょう。
\(a_{M – 1} \times N^{M-1} + a_{M – 2} \times N^{M-2} + ・・・+ a_{1} \times N^{1} + a_{0} \times N^{0} \)
\(N\) での剰余算とはつまり、\(N\) で割った時の余りを求める演算ですね!
で、上式について考えると、\(a_{0} \times N^{0} \) 以外の項は全て \(N\) で割り切ることが可能です。これは、\(a_{0} \times N^{0} \) 以外の項には全て \(N\) が掛けられているからです。
つまり、上式に対して \(N\) で剰余算すれば、\(a_{0} \times N^{0} \) 以外の項は全て \(0\) になります。さらに、\(N^{0} \) は \(1\) ですので(どんな値でも \(0\) 乗すれば \(1\) になる)、上式に対して \(N\) で剰余算すれば、結局 \(a_{0} \) のみが残り、\(a_{0} \) の値を求めることができることになります。
これはすなわち、\(N\) による剰余算は、\(N^{0} \) の項の係数を求める演算であるということができます。
また、\(a_{0} \) は10進数を \(N\) 進数で表した時の第 \(0\) 桁の値となりますので、要は10進数を \(N\) で剰余算すれば、その10進数を \(N\) 進数に変換した際の第 \(0\) 桁を求めることができることになります。
除算により各項の冪数を減らすことができる
さらに、今度は次の式に対して \(N\) で除算を行うことを考えてみましょう。
ただしこの除算では、小数点以下の値は切り捨てするものとします(要は、C言語における整数同士の割り算です。C言語では整数同士の割り算では自動的に小数点以下が切り捨てられます)。
\(a_{M – 1} \times N^{M-1} + a_{M – 2} \times N^{M-2} + ・・・+ a_{1} \times N^{1} + a_{0} \times N^{0} \)
まず \(a_{0} \times N^{0} \) 以外の項に関しては、\(N\) で除算することで冪数が \(1\) 減ることになります。
例えば \(a_{M – 1} \times N^{M-1} \) の項であれば、\(N\) で除算することで \(a_{M – 1} \times N^{M-2} \) に変化します。
さらに、\(a_{0} \times N^{0} \) の項に関しては \(0\) になります。これは、この項が \(N\) 未満の値のため、除算を行うと切り捨てされるからです(係数の値は全て \(0\) 〜 \(N – 1\) の整数)。
ですので、上式を \(N\) で除算すれば、次の式に変化することになります。
\(a_{M – 1} \times N^{M-2} + a_{M – 2} \times N^{M-3} + ・・・+ a_{2} \times N^{1} + a_{1} \times N^{0} \)
で、上式について考えると、\(N\) で除算したことによって各項の冪数が \(1\) 減り、これにより \(N^{0} \) の項が \(a_{1} \times N^{0} \) に変化することになります。
ですので、上式に対して再度 \(N\) で剰余算をすれば、\(a_{1}\) を求めることができることになります(\(N\) での剰余算により \(N^{0} \) の項の係数を求めることができる)。この \(a_{1}\) は、10進数を \(N\) 進数に変換した際の第 \(1\) 桁の値です。
さらに、上式に対して再び \(N\) で除算した後に \(N\) で剰余算すれば、次は \(a_{2}\)、つまり10進数を \(N\) 進数に変換した際の第 \(2\) 桁の値を求めることができます。
この後も同様で、要は10進数を \(N\) で \(m\) 回除算した後に \(N\) で剰余算すれば、10進数から \(N\) 進数へ変換した際の第 \(m\) 桁の値を求めることが可能です。
つまり、剰余算と除算を繰り返し実行することで、10進数から \(N\) 進数へ変換した際の各桁の値を求めていくことができます。
さらに除算を繰り返すことで、除算結果がいずれは必ず \(0\) になります(\(N >= 2\) なので)。この時点で10進数から \(N\) 進数への変換は完了したことになります。
なぜなら、これ以降の剰余算結果(除算結果も)は全て \(0\) になるからです(つまり、これ以降に求められる桁の値は全て \(0\) になる)。
ですので、アルゴリズム的に考えると、下記のように剰余算と除算を繰り返し行うことで、10進数から \(N\) 進数への変換を実現することが可能ということになります(下記では x
を \(N\) 進数に変換)。
i = 0
とするx % N
を \(N\) 進数変換結果の第i
桁とするx = x / N
とするi = i + 1
とするx > 0
であれば 2. に戻る
\(N^{0} \) の項の係数を求めるために剰余算(%
)を実行し、各項の冪数を \(1\) 減らすために除算(/
)を行なっています。
ただし、桁数指定で \(N\) 進数への変換結果を求める場合は、x
が 0
になったかどうかではなく、その桁数分上記の 2. 3. 4. を繰り返す必要があります
例えば8桁の2進数に変換したいような場合は、8桁分の x % N
の結果が必要になるため、上記の 2. 3. 4. 処理は8回実行されるようにする必要があります
つまり、5. の判断は “x > 0
であれば 2. に戻る” ではなく、”i < 8
であれば 2. に戻る” にする必要があります
10進数から \(N\) 進数への変換プログラム
ここまで解説してきた通り、10進数 x
は下記の処理により \(N\) 進数に変換することが可能です。
i = 0
とするx % N
を \(N\) 進数変換結果の第i
桁とするx = x / N
とするi = i + 1
とするx > 0
であれば 2. に戻る
基本はこれだけ行えば良いのですが、それでもプログラムにするときはいくつかポイントになる点があります。
その点については、実際のソースコードを見ながら確認していきたいと思います。
プログラムのソースコード
10進数から \(N\) 進数に変換するプログラムのソースコードは下記のようになります。実際に10進数から \(N\) 進数に変換する処理を行なっているのは関数 DectoN
になります。
#include <stdio.h>
/* unsigned intのビット数 */
#define MAX_NUM_DIGITS 32
/* 係数を文字に変換するテーブル */
const char coef[36] = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z'
};
/* num文字の文字列の並びを逆順にする */
void reverse(char ans[], int num) {
int i;
char tmp;
for (i = 0; i < num / 2; i++) {
tmp = ans[i];
ans[i] = ans[num - 1 - i];
ans[num - 1 - i] = tmp;
}
}
/* 10進数decをn進数に変換して結果をansに格納する */
void DectoN(char ans[], unsigned int dec, unsigned int n) {
unsigned int i;
unsigned int digit_num;
if (n > 36 || n < 2) {
/* 対応しているのは2進数から36進数まで */
return;
}
i = 0;
while (dec > 0) {
/* i桁目の係数を求める */
digit_num = dec % n;
/* 文字に変換 */
ans[i] = coef[digit_num];
/* n進数で1桁分桁下げ */
dec = dec / n;
/* 次に求める係数の桁 */
i++;
}
/* 最後にヌル文字をつける */
ans[i] = '\0';
/* 文字列の並びを逆順にする */
reverse(ans, i);
}
int main(void) {
char ans[MAX_NUM_DIGITS + 1];
unsigned int dec;
unsigned int n;
printf("10進数を入力:");
scanf("%u", &dec);
for (n = 2; n <= 36; n++) {
DectoN(ans, dec, n);
printf("%2u進数:%s\n", n, ans);
}
return 0;
}
スポンサーリンク
プログラムの実行
上記ソースコードをコンパイルして実行すれば、下記のように10進数の入力が促されます。
10進数を入力:
10進数を入力してエンタキーを押せば、下記のように2進数から36進数に変換した結果が表示されます。
2進数:1100100 3進数:10201 4進数:1210 5進数:400 〜 略 〜 33進数:31 34進数:2W 35進数:2U 36進数:2S
入力できる10進数の最大値は、unsigned int
型の最大値、つまり 4294967295
になりますので注意してください。
プログラムの解説
まず DectoN
関数の仕様について解説しておくと、この DectoN
関数は引数 dec
で指定された10進数を n
進数に変換し、その結果を文字列として ans
に格納する関数になります。
引数 n
は 2
〜 36
にのみ対応しており、その他の値が引数 n
に指定された場合は何もせずにすぐに関数を終了するようにしています。
この DectoN
関数のポイントは下記の3つです。
- 10進数を \(N\) 進数に変換する
- 値に対応した文字に変換する
- 上位の桁から表示できるようにする
10進数を \(N\) 進数に変換する
これに関してはここまで解説してきた通りで、\(N\) 進数への変換自体は DectoN
関数で下記に基づいて行なっています(変数名等は DectoN
関数で使用しているものに記述を変更しています)。
i = 0
とするdec % n
をn
進数変換結果の第i
桁とするdec = dec / n
とするi = i + 1
とするdec > 0
であれば 2. に戻る
値に対応した文字に変換する
上記の処理でポイントになるのが 2. の処理です。
dec % n
をn
進数変換結果の第i
桁とする
dec % n
の結果は 0
〜 n - 1
の整数になりますので、n
が 10
よりも大きい場合、つまり 10
よりも大きな進数に変換する場合、各桁の値が 10
以上になってしまう可能性があります。つまり、1桁の値が2桁になってしまいます。
ちょっとこれだと気持ち悪いので、通常の16進数同様に、10
以上の値はアルファベットに置き換えるようにしています。
それを行なっているのが下記部分になります。
/* i桁目の係数を求める */
digit_num = dec % n;
/* 文字に変換 */
ans[i] = coef[digit_num];
上記で使用している配列 coef
は下記のように宣言しています。
/* 係数を文字に変換するテーブル */
const char coef[36] = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z'
};
この coef
の添字に dec % n
の結果を指定すれば、その結果に対応する文字を取得することができるようになっています。0
〜 9
の値を添字に指定した場合は、それらの値を数字の文字の変換した結果が得られます。
また、10進数から \(N\) 進数に変換した結果は複数桁となる場合がありますので、複数の文字を扱うために、結果を格納するための引数 ans
の型は char
型の配列としています。
さらに、その結果を文字列として扱えるように、変換完了後に ans
の最後にヌル文字('\0'
)を付加するようにしています。
ans[i] = '\0';
上位の桁から表示できるようにする
前述でも解説した通り、下記の処理では n
進数への変換結果を下位の桁から求めることになります。
i = 0
とするdec % n
をn
進数変換結果の第i
桁とするdec = dec / n
とするi = i + 1
とするdec > 0
であれば 2. に戻る
その一方で、DectoN
関数で行っている下記の処理では、その下位の桁を文字列の先頭から順に格納することになります。
i = 0;
while (dec > 0) {
/* i桁目の係数を求める */
digit_num = dec % n;
/* 文字に変換 */
ans[i] = coef[digit_num];
/* n進数で1桁分桁下げ */
dec = dec / n;
/* 次に求める係数の桁 */
i++;
}
ですので、このまま printf("%s\n", ans);
などで変換結果を表示すると、各桁の並びが逆順に表示されることになってしまいます。
そのため、変換完了後に文字列の文字の並びを逆順にするようにしています。この処理は reverse
関数で行っています。
以上が、プログラムのソースコードで紹介したプログラム(特に DectoN
関数)のポイントの解説になります。
文字列ではなく整数で結果を返却する
プログラムのソースコードで紹介したプログラムでは、\(N\) 進数への変換結果は文字列となります。
ただ、変換結果を整数として得たい場合もあると思いますので、その場合に向けての解説もしておきます。
前述の通り、10を超える進数に変換すると文字を利用する必要があります。
そのためプログラムのソースコードで紹介したプログラムでは、変換結果を文字列として扱う必要がありました。
ただし、10以下の進数の場合は文字を利用する必要はなく、変換結果も整数(int
型や unsigned int
型など)として扱うことが可能です。
下記は、\(N\) 進数への変換結果を整数として扱う時のプログラムのソースコード例になります。
#include <stdio.h>
#include <math.h>
/* 10進数decをn進数に変換して結果を返却する */
unsigned int DectoN(unsigned int dec, unsigned int n) {
unsigned int i;
unsigned int ans;
unsigned int digit_num;
unsigned int shifted_digit_num;
if (n > 10 || n < 2) {
/* 対応しているのは2進数から10進数まで */
return 0;
}
i = 0;
ans = 0;
while (dec > 0) {
/* i桁目の係数を求める */
digit_num = dec % n;
/* digit_numをi桁目まで桁上げする */
shifted_digit_num = digit_num * pow(10, i);
/* これまでの変換結果に足し合わせる */
ans = ans + shifted_digit_num;
/* n進数で1桁分桁下げ */
dec = dec / n;
/* 次に求める係数の桁 */
i++;
}
return ans;
}
int main(void) {
unsigned int dec;
unsigned int n;
unsigned int ans;
printf("10進数を入力:");
scanf("%u", &dec);
for (n = 2; n <= 10; n++) {
ans = DectoN(dec, n);
printf("%2u進数:%u\n", n, ans);
}
return 0;
}
プログラムのソースコードのものと同様に、10進数から \(N\) 進数に変換を行う関数は DectoN
になります。
また、プログラムの動作確認方法についてもプログラムのソースコードのものと同様になります。
DectoN
関数の引数と戻り値
ただし、プログラムのソースコードのものとは引数と戻り値が異なります。
今回の場合は \(N\) 進数への変換結果は整数として扱うため、DectoN
関数の戻り値の型は unsigned int
とし、戻り値としてその変換結果を返却するようにしています。
その変換結果は、\(N\) 進数で利用できる数字(0
〜 N - 1
)のみから構成される値になります。例えば 99
を2進数に変換した場合の結果は 1100011
に変換して返却します(全ての桁が2進数で扱える 0
or 1
の値になります)。
また、今回は変換結果を関数の戻り値として返却するため、プログラムのソースコードで変換結果を格納するために用意していた配列 ans
は不要になります。ですので、ans
は引数から削除しています。
さらに、今回は文字が扱えないので、引数 n
に 10
を超える値が指定された場合は、変換を行わずに0
を返却するようにしています。
DectoN
関数で行う処理
さらに、今回の DectoN
関数においても、主な処理は下記の通りであり、プログラムのソースコードで紹介したものと変わりません。
i = 0
とするdec % n
をn
進数変換結果の第i
桁とするdec = dec / n
とするi = i + 1
とするdec > 0
であれば 2. に戻る
ただ、プログラムのソースコードでは桁ごとに配列の異なる位置に格納すれば良かったのですが、今回は変換結果の各桁は全て unsigned int
型の ans
に格納する必要があります。
そのため、上記の 2. で第 i
桁の変換結果を求めた後、その変換結果を “桁を考慮して” ans
に格納する必要があります。
具体的にいうと、上記の 2. で求めた第 i
桁の変換結果が、ans
の第 i
桁の値になるように処理する必要があります。
これは、上記 2. での第 i
桁の変換結果を、10
の i
乗の値に掛けた後に ans
に足し合わせることで実現することができます。
上記のように、桁を考慮しながら ans
に足し合わせる処理を行なっているのが DectoN
関数の下記部分になります。
/* i桁目の係数を求める */
digit_num = dec % n;
/* digit_numをi桁目まで桁上げする */
shifted_digit_num = digit_num * pow(10, i);
/* これまでの変換結果に足し合わせる */
ans = ans + shifted_digit_num;
ここで使用している pow
関数は、第1引数の値を第2引数の値で冪乗する関数になります。つまり上記の pow
関数では、10
の i
乗の値を求めています。
オーバーフローに注意
今回のように、\(N\) 進数への変換結果を整数として扱う際には、特に変換後の整数の桁数に注意が必要です。
特に2進数など、小さな進数へ変換した際には、桁数が多くなりがちです。
例えば、10進数 99
を2進数に変換すると 1100011
になりますので、2
桁から一気に桁数が 7
桁に増えることになります。
一方で、DectoN
関数の戻り値の型は unsigned int
ですので、扱える値は unsigned int
型の最大値、つまり 4294967295
になります。
これを超えるとオーバーフロー(桁あふれ)が発生するので、上手く変換結果を得ることができません。
例えば2進数に変換する場合、変換結果が 1111111111
を超えるとオーバーフローが発生することになります(次の値は 10000000000
なので 4294967295
を超える)。
つまり、1111111111
は10進数では 1023
ですので、1024
以上の値は変換に失敗することになります。
多分、思ったよりも変換可能な最大値が小さいと感じた人が多いと思います。
もっと大きな値を扱えるようにしたいのであれば、配列に変換結果を格納する・ソースコード内で使用する変数の型をもっと大きなものに変更する等の対応が必要です。
前者に関してはプログラムのソースコードで紹介したソースコードが参考になると思いますし、後者に関しては、例えばソースコードを下記のように変更することで対応できます。
- 整数を扱う型を
unsigned int
からunsigned long long
に置き換える printf
の変換指定子を%u
から%llu
に置き換える
スポンサーリンク
まとめ
このページでは、10進数を \(N\) 進数に変換する考え方と、それに基づいた10進数から \(N\) 進数への変換プログラムの紹介を行いました!
10進数から \(N\) 進数への変換は、”\(N\) での剰余算” と “\(N\) での除算” を繰り返し行うことで実現することが可能です。
実装する上では、10を超える進数に変換する場合に、剰余算結果を “文字に置き換える必要がある” ところがポイントだと思います。
また、求められるのは “下位の桁の値から” になるので、最終的な結果の各桁の並びが逆順にならないように注意してください。
おそらく、10進数を \(N\) 進数に変換する際には剰余算と除算を行えば良いことを知っている人は多くても、なぜそれで変換できるかの理由を知らない人は多いと思います。
このサイトでは、他のページにおいても、そういった処理が実現できる “理由” についても詳しく解説していますので、どうすれば良いかだけでなく、”なぜそれで良いのか?” について詳しく知りたい方は、是非いろんなページを読んでみてください!
オススメの参考書(PR)
C言語学習中だけど分からないことが多くて挫折しそう...という方には、下記の「スッキリわかるC言語入門」がオススメです!
まず学習を進める上で、参考書は2冊持っておくことをオススメします。この理由は下記の2つです。
- 参考書によって、解説の仕方は異なる
- 読み手によって、理解しやすい解説の仕方は異なる
ある人の説明聞いても理解できなかったけど、他の人からちょっと違った観点での説明を聞いて「あー、そういうことね!」って簡単に理解できた経験をお持ちの方も多いのではないでしょうか?
それと同じで、1冊の参考書を読んで理解できない事も、他の参考書とは異なる内容の解説を読むことで理解できる可能性があります。
なので、参考書は2冊持っておいた方が学習時に挫折しにくいというのが私の考えです。
特に上記の「スッキリわかるC言語入門」は、他の参考書とは違った切り口での解説が豊富で、他の参考書で理解できなかった内容に対して違った観点での解説を読むことができ、オススメです。題名の通り「なぜそうなるのか?」がスッキリ理解できるような解説内容にもなっており、C言語入門書としてもかなり分かりやすい参考書だと思います。
もちろんネット等でも色んな観点からの解説を読むことが出来ますので、分からない点は別の人・別の参考書の解説を読んで解決していきましょう!もちろん私のサイトも参考にしていただけると嬉しいです!
入門用のオススメ参考書は下記ページでも紹介していますので、こちらも是非参考にしていただければと思います。
https://daeudaeu.com/c_reference_book/