第28問の答え

答え

数値を0で書き始めると8進数になってしまう!
PlanetInfo planets[] = {
  { 004879, "Mercury" }, /* ← 先頭が0だと8進数! */
  { 012104, "Venus" },
  { 012742, "Earth" },
  { 006779, "Mars" },
  { 139820, "Jupiter" }, /* ← こちらは普通に10進数 */
  { 116460, "Saturn" },
  { 050724, "Uranus" },
  { 049244, "Neptune" },
};
10進数を表したいときは、先頭を0にしない書き方が正解!
PlanetInfo planets[] = {
  {   4879, "Mercury" },
  {  12104, "Venus" },
  {  12742, "Earth" },
  {   6779, "Mars" },
  { 139820, "Jupiter" },
  { 116460, "Saturn" },
  {  50724, "Uranus" },
  {  49244, "Neptune" },
};

解説

これは、数値の表記法についての出題でした。C言語では通常の10進数のほかに、8進数や16進数による表現が可能です。

16進数は第25問でも出てきたけど、よく使う表現よね。
あ、例の0xで始まるやつですね。(そっか、よく使うのか……)
でね、ちょっと分かりにくいのだけど、0で始まる数値は8進数なのよ。
並べて書くと、こんな感じ。
  int v10 = 100; /* 10進数 */
  int v8 = 0100; /* 8進数(10進数で64)*/
  int v16 = 0x100; /* 16進数(10進数で256) */
ええっ!ということは……
ここに並んでる整数は、10進数と8進数が混ざっちゃってるのか。
PlanetInfo planets[] = {
  { 004879, "Mercury" },
  { 012104, "Venus" },
  { 012742, "Earth" },
  { 006779, "Mars" },
  { 139820, "Jupiter" },
  { 116460, "Saturn" },
  { 050724, "Uranus" },
  { 049244, "Neptune" },
};
どれも普通の10進数に見えるのにね。
これって、数字の桁数をきれいに揃えてプログラムを書きたかっただけですよね?
そういうこと。だから、けっこうやってしまいがちな間違いといえるわね。
じゃあ、きれいに書けないですねー。
あら、そんなことはないわよ。
例えば、こんな風にすれば……
PlanetInfo planets[] = {
  {   4879, "Mercury" },
  {  12104, "Venus" },
  {  12742, "Earth" },
  {   6779, "Mars" },
  { 139820, "Jupiter" },
  { 116460, "Saturn" },
  {  50724, "Uranus" },
  {  49244, "Neptune" },
};
そっか。0で埋めるんじゃなくて、スペースを空ければよかったんですね!
そう。これでコンパイルできるようになったはずよ。

さて。今回の問題は、「コンパイルエラーになる」という点でした。でも、8進数を使うといつもエラーになるわけではありません。

16進数では、先頭の0xのあとは0から9までの数字とaからfまでの英字、合わせて16種類の文字を使いますね。8進数では、先頭の0のあと、0から7までの8種類の文字を使います。これに対して、問題のプログラムでは偶然89も使われていたためエラーになったのです。

普通の数字のように見えて、しかもエラーにならないときもあるって、困るじゃないですか。
そうなのよ。これはC言語の、ちょっと気を付けないといけないところね。

ちなみに、後発のプログラミング言語には表記法が改善されているものもあります。例えば、Swiftでは次のように書きます。

  let v10 = 100; // 10進数
  let v2 = 0b100; // 2進数(10進数で4)
  let v8 = 0o100; // 8進数(10進数で64)
  let v16 = 0x100; // 16進数(10進数で256)
ヘぇ〜。この書き方なら間違えにくいですね。
逆に、この書き方じゃないからC言語では間違えやすいっていうのも分かるわね。
たしかに!
ここがポイント!
数値の先頭を0にする書き方には要注意。それは8進数です!
ところで、8進数って本当に必要なんですか?
あー、それは疑問に思うわよね。私は使ったことがないわ。
なんとっ!
16進数が、1桁あたり4ビットなのは分かるかしら?
えっと、charとか8ビットの値が16進数で2桁だから、そうなりますね。
そうね。で、最近のコンピューターは32ビットとか64ビットが主流でしょう?
はい。
32ビットや64ビットは、4ビットで割り切れるから、16進数と相性がいいのよ。
ふむふむ。だから16進数はよく使うんですね。
でね。8進数は、1桁が3ビットなの。
まさかの奇数!
あはは。で、32ビットや64ビットは、3ビットじゃ割れないってわけ。
なるほど。だから8進数はあまり使わないんですね。
ところが!世の中には36ビットや48ビットのコンピューターもあるのよ。なんと3ビットで割れるのよ。
うわぁ!
つまり、8進数が便利な世界でプログラミングをしてる人もいるってことね。

修正後のプログラム

main.c
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

typedef struct {
  uint32_t diameter; /* [km] */
  const char *pName;
} PlanetInfo;

PlanetInfo planets[] = {
  {   4879, "Mercury" },
  {  12104, "Venus" },
  {  12742, "Earth" },
  {   6779, "Mars" },
  { 139820, "Jupiter" },
  { 116460, "Saturn" },
  {  50724, "Uranus" },
  {  49244, "Neptune" },
};

int main(void) {
  int n = sizeof(planets) / sizeof(planets[0]);
  for (int i=0; i<n; i++) {
    printf("Diameter of %s: %dkm\n", planets[i].pName, planets[i].diameter);
  }

  return EXIT_SUCCESS;
}
実行結果
Diameter of Mercury: 4879km
Diameter of Venus: 12104km
Diameter of Earth: 12742km
Diameter of Mars: 6779km
Diameter of Jupiter: 139820km
Diameter of Saturn: 116460km
Diameter of Uranus: 50724km
Diameter of Neptune: 49244km