0013. 運命数から日付を求める(C言語)

戻る

【解説】
 運命数については、「0011. 運命数(C言語)」を参照。これによって「日付→運命数」を知ることができたが、その逆、「運命数→日付」を知るプログラムへの興味が高まり、作成した。主な作成の指針は、次の通り。
・日付をたどっていき、調べたい運命数と一致する日付を調べるために、「年→月→日」の3重のループ処理とした。
・ループの範囲の指定を簡素化するために、一番外側の年の範囲を「1900年から当年」という可変のものとし、内側の月・日の範囲は、年末日までとなるように固定した(各月末の日は、関数を用いて暦に応じて変化するものとした)。
・結果の表示量は膨大なものになると予測されたため、結果の出力先を、画面のみ・ファイルのみ・画面とファイルの両方の3つから選べるようにした。
・表示する結果の一覧の冒頭に、「【運命数が×となる日】」の表示を加えて、何の結果かが一目で分かるようにした。
・出力結果に行数を付け、さらに縦に並べて同じ種類の項目が同じ位置に表示されるように工夫した。

untodate.cのソースコード】


/* 運命数から日付を求める       */
/* untodate.c(unmeisuu to date) */

#include <stdio.h>
#include <stdlib.h>
#include <string.h> /* strlen()  */
#include <ctype.h>  /* isdigit() */
#include <time.h>

/* 文字列が正しい形式の数値かどうかを判定する関数 */
int isValidNumber(char line[]);

/* year年month月に含まれる日数を返す関数 */
/* isLeapYear()関数を使用する            */
int getDaysOfTheMonth(int year,int month);

/* うるう年の判定をする関数 */
int isLeapYear(int year);

/* 運命数を求める関数       */
/* isZorome()関数を使用する */
int unmeisuu(int date);

/* ぞろ目かどうかを判定する関数 */
int isZorome(int n);

/* 現在日付時刻からファイル名を生成する関数 */
void genFileName(char *filenamep);

/* 10進整数の桁数を求める関数 */
int getDigit(int n);

int main(void)
{
    char line[256], outputto, ch, zz;
    char filenamea[256], *filenamep;
    int un, year, month, day, date, i, rows, totalrows;
    struct tm *t;
    time_t tt;
    FILE *foutp;
    filenamep = filenamea;
    
    time(&tt);
    t = localtime(&tt);
    while (1) {
        printf("【運命数から日付を求めて表示します】\n");
        printf("日付は、1900年1月1日以降今年末までに限ります。\n\n");
        while (1) {
            printf("調べたい運命数を入力してください。\n");
            printf("ただし、次の数に限ります。\n");
            printf("1~9の整数、11、22、33、44。\n\n");
            
            printf("運命数 = ? ");
            fgets(line, sizeof line, stdin);
            sscanf(line, "%d%c", &un, &zz);
            line[strlen(line) - 1] = '\0';
            if (!isValidNumber(line)) {
                printf("無効な入力です。\n");
                printf("半角の正の整数値を入力してください。\n\n");
                continue;
            } else if (un != 1 && un != 2 && un != 3 && un != 4 && un != 5 &&
                       un != 6 && un != 7 && un != 8 && un != 9 &&
                       un != 11 && un != 22 && un != 33 && un != 44 ||
                       zz != '\n') {
                printf("無効な入力です。\n");
                printf("1~9の整数、11、22、33、44のうちのいずれかを");
                printf("入力してください。\n\n");
                continue;
            } else
                break;
        }
        while (1) {
            printf("結果の出力先を1、2、3から選んでください。\n");
            printf("1. 画面だけに出力する。\n");
            printf("2. ファイルだけに出力する。\n");
            printf("3. 画面とファイルの両方に出力する。\n");
            fgets(line, sizeof line, stdin);
            sscanf(line, "%c%c", &outputto, &zz);
            if (outputto != '1' && outputto != '2' && outputto != '3' || zz != '\n') {
                printf("無効な入力です。\n");
                printf("1、2、3のいずれかを入力してください。\n\n");
                continue;
            } else
                break;
        }
        
        /* 「【運命数が△△となる日】」の行を表示するための処理 */
        switch (outputto) {
        case '1':
            printf("【運命数が%dとなる日】\n", un);
            break;
        case '2':
            genFileName(filenamep);
            foutp = fopen(filenamep, "w");
            if (foutp == NULL) {
                printf("ファイルを開けません。終了します。\n");
                return EXIT_FAILURE;
            }
            fprintf(foutp, "【運命数が%dとなる日】\n", un);
            break;
        case '3':
            printf("【運命数が%dとなる日】\n", un);
            genFileName(filenamep);
            foutp = fopen(filenamep, "w");
            if (foutp == NULL) {
                printf("ファイルを開けません。終了します。\n");
                return EXIT_FAILURE;
            }
            fprintf(foutp, "【運命数が%dとなる日】\n", un);
            break;
        default:
            break;
        }
        
        /* totalrowsの値を取得するためのループ処理 */
        rows = 0;
        for (year = 1900; year <= 1900+(t->tm_year); year++)
            for (month = 1; month <= 12; month++)
                for (day = 1; day <= getDaysOfTheMonth(year, month); day++) {
                    date = 10000 * year + 100 * month + day;
                    if (un == unmeisuu(date))
                        rows++;
                }
        totalrows = rows;
        
        /* 日付の一覧を表示するためのループ処理 */
        rows = 0;
        for (year = 1900; year <= 1900+(t->tm_year); year++)
            for (month = 1; month <= 12; month++)
                for (day = 1; day <= getDaysOfTheMonth(year, month); day++) {
                    date = 10000 * year + 100 * month + day;
                    if (un == unmeisuu(date)) {
                        switch (outputto) {
                        case '1':
                            rows++;
                            for (i = 0; i < getDigit(totalrows)-getDigit(rows); i++)
                                printf(" ");
                            printf("%d: %4d年%2d月%2d日\n", rows, year, month, day);
                            break;
                        case '2':
                            rows++;
                            for (i = 0; i < getDigit(totalrows)-getDigit(rows); i++)
                                fprintf(foutp, " ");
                            fprintf(foutp, "%d: %4d年%2d月%2d日\n", rows, year, month, day);
                            break;
                        case '3':
                            rows++;
                            for (i = 0; i < getDigit(totalrows)-getDigit(rows); i++)
                                printf(" ");
                            printf("%d: %4d年%2d月%2d日\n", rows, year, month, day);
                            for (i = 0; i < getDigit(totalrows)-getDigit(rows); i++)
                                fprintf(foutp, " ");
                            fprintf(foutp, "%d: %4d年%2d月%2d日\n", rows, year, month, day);
                            break;
                        default:
                            break;
                        }
                    }
                }
        
        /* 選択肢2、3の場合のファイルを閉じる処理 */
        switch (outputto) {
        case '1':
            break;
        case '2':
        case '3':
            fclose(foutp);
            break;
        default:
            break;
        }
        
        /* 操作を続けるかどうかの選択 */
        while (1) {
            printf("\n続けますか?(y/n) ");
            fgets(line, sizeof line, stdin);
            sscanf(line, "%c%c", &ch, &zz);
            if ((ch == 'y') && (zz == '\n')) {
                printf("\n+++++\n\n");
                break;
            } else if ((ch == 'n') && (zz == '\n'))
                return EXIT_SUCCESS;
            else {
                printf("無効な入力です。\n");
                printf("yまたはnを入力してください。\n");
                continue;
            }
        }
    }
}

/* 文字列が正しい形式の数値かどうかを判定する関数 */
int isValidNumber(char line[])
{
    int i = 0;
    
    /* 最初は符号(省略可) */
    if (line[i] == '+')
        i++;
    /* 次に数字(省略不可) */
    if (!isdigit(line[i]))
        return 0;
    i++;
    /* 次に数字列(省略可) */
    while (isdigit(line[i]))
        i++;
    
    return line[i] == '\0';
}

/* year年month月に含まれる日数を返す関数 */
/* isLeapYear()関数を使用する            */
int getDaysOfTheMonth(int year,int month)
{
    int days;
    if (isLeapYear(year) && (month == 2))         /* うるう年の2月           */
        days = 29;
    else if ((!isLeapYear(year)) && (month == 2)) /* うるう年ではない2月     */
        days = 28;
    else if ((month == 4) || (month == 6) ||      /* 4月、6月、9月、11月     */
     (month == 9) || (month == 11))
        days = 30;
    else if ((month == 1) || (month == 3) ||      /* 1、3、5、7、8、10、12月 */
     (month == 5) || (month == 7) ||
     (month == 8) || (month == 10) ||
     (month == 12))
        days = 31;
    else {                                        /* 上記以外                */
        printf("月の日数の計算ができません。処理を終了します。\n");
        exit(EXIT_FAILURE);
    }
    return days;
}

/* うるう年の判定をする関数 */
int isLeapYear(int year)
{
    return ((year % 400 == 0) || ((year % 100) && (year % 4 == 0)));
}

/* 運命数を求める関数       */
/* isZorome()関数を使用する */
int unmeisuu(int date)
{
    int sum = 0;
    
    while (date > 0) {
        sum += date % 10;
        date /= 10;
    }
    if (isZorome(sum))
        return sum;
    if (sum > 9)
        sum = unmeisuu(sum);
    return sum;
}

/* ぞろ目かどうかを判定する関数 */
int isZorome(int n)
{
    int m;
    
    /* 1桁の場合、ぞろ目ではないので偽を返す */
    if (!(n/10))
        return 0;
    
    m = n % 10;          /* 1の位を記憶                         */
    while (1) {          /* 繰り返し                            */
        n /= 10;         /* 10で割って1未満を切り捨て           */
        if (!n)          /* nが0になったら                      */
            break;       /* 繰り返しをやめる                    */
        if (m != n % 10) /* 1の位とある位を比較して違っていたら */
            return 0;    /* 偽を返す                            */
    }
    
    return 1;            /* nが0になるまですべての位が同じなら真を返す */
}

/* 現在日付時刻からファイル名を生成する関数 */
void genFileName(char *filenamep)
{
    struct tm *t;
    time_t tt;
    char snumrand[10];
    
    time(&tt);
    t = localtime(&tt);
    
    srand((unsigned int)time(NULL));
    rand(); rand(); rand(); rand(); rand();
    sprintf(filenamep, "untodate%04d%02d%02d_%02d%02d%02d_",
     1900+(t->tm_year), 1+(t->tm_mon), t->tm_mday,
     t->tm_hour, t->tm_min, t->tm_sec);
    sprintf(snumrand, "%04d", rand() % 10000);
    filenamep = strcat(filenamep, snumrand);
    filenamep = strcat(filenamep, ".txt");
}

/* 10進整数の桁数を求める関数 */
int getDigit(int n)
{
    int digit = 0;
    while(n){
        n /= 10;
        digit++;
    }
    return digit;
}


untodateの実行結果】赤字はキーボードからの入力を表す。

D:\test>untodate
【運命数から日付を求めて表示します】
日付は、1900年1月1日以降今年末までに限ります。

調べたい運命数を入力してください。
ただし、次の数に限ります。
1~9の整数、11、22、33、44。

運命数 = ? 1
結果の出力先を1、2、3から選んでください。
1. 画面だけに出力する。
2. ファイルだけに出力する。
3. 画面とファイルの両方に出力する。
2

続けますか?(y/n) y

+++++
(中略)
+++++

【運命数から日付を求めて表示します】
日付は、1900年1月1日以降今年末までに限ります。

調べたい運命数を入力してください。
ただし、次の数に限ります。
1~9の整数、11、22、33、44。

運命数 = ? 9
結果の出力先を1、2、3から選んでください。
1. 画面だけに出力する。
2. ファイルだけに出力する。
3. 画面とファイルの両方に出力する。
2

続けますか?(y/n) y

+++++

【運命数から日付を求めて表示します】
日付は、1900年1月1日以降今年末までに限ります。

調べたい運命数を入力してください。
ただし、次の数に限ります。
1~9の整数、11、22、33、44。

運命数 = ? 11
結果の出力先を1、2、3から選んでください。
1. 画面だけに出力する。
2. ファイルだけに出力する。
3. 画面とファイルの両方に出力する。
2

続けますか?(y/n) y

+++++

【運命数から日付を求めて表示します】
日付は、1900年1月1日以降今年末までに限ります。

調べたい運命数を入力してください。
ただし、次の数に限ります。
1~9の整数、11、22、33、44。

運命数 = ? 22
結果の出力先を1、2、3から選んでください。
1. 画面だけに出力する。
2. ファイルだけに出力する。
3. 画面とファイルの両方に出力する。
2

続けますか?(y/n) y

+++++

【運命数から日付を求めて表示します】
日付は、1900年1月1日以降今年末までに限ります。

調べたい運命数を入力してください。
ただし、次の数に限ります。
1~9の整数、11、22、33、44。

運命数 = ? 33
結果の出力先を1、2、3から選んでください。
1. 画面だけに出力する。
2. ファイルだけに出力する。
3. 画面とファイルの両方に出力する。
2

続けますか?(y/n) y

+++++

【運命数から日付を求めて表示します】
日付は、1900年1月1日以降今年末までに限ります。

調べたい運命数を入力してください。
ただし、次の数に限ります。
1~9の整数、11、22、33、44。

運命数 = ? 44
結果の出力先を1、2、3から選んでください。
1. 画面だけに出力する。
2. ファイルだけに出力する。
3. 画面とファイルの両方に出力する。
2

続けますか?(y/n) n

D:\test>dir
(中略)
2022-10-18  20:39           148,480 untodate.exe
2022-10-18  20:42           109,825 untodate20221018_204204_4345.txt       ←運命数が1~9、11、22、33、44の13個のテキストファイルが出力された。
2022-10-18  20:42            29,261 untodate20221018_204207_3357.txt
2022-10-18  20:42           109,759 untodate20221018_204210_5137.txt
2022-10-18  20:42            72,337 untodate20221018_204214_0844.txt
2022-10-18  20:42           109,803 untodate20221018_204217_9856.txt
2022-10-18  20:42            67,585 untodate20221018_204220_1636.txt
2022-10-18  20:42           109,869 untodate20221018_204223_0648.txt
2022-10-18  20:42           108,505 untodate20221018_204227_6355.txt
2022-10-18  20:42           109,891 untodate20221018_204229_4209.txt
2022-10-18  20:42            80,544 untodate20221018_204232_3221.txt
2022-10-18  20:42            37,512 untodate20221018_204235_5001.txt
2022-10-18  20:42            42,308 untodate20221018_204237_2855.txt
2022-10-18  20:42             1,324 untodate20221018_204240_1867.txt


【出力されたファイルuntodate20221018_204204_4345.txt(運命数が1となる日付一覧)の中身(抜粋)】

【運命数が1となる日】
   1: 1900年 1月 8日
   2: 1900年 1月17日
   3: 1900年 1月26日
   4: 1900年 2月 7日
   5: 1900年 2月16日
(中略)
4987: 2022年11月29日
4988: 2022年12月 1日
4989: 2022年12月10日
4990: 2022年12月19日
4991: 2022年12月28日

 :
(中略)
 :
【出力されたファイルuntodate20221018_204240_1867.txt(運命数が44となる日付一覧)の中身(抜粋)】

【運命数が44となる日】
 1: 1959年 9月29日
 2: 1968年 9月29日
 3: 1969年 8月29日
 4: 1969年 9月19日
 5: 1969年 9月28日
(中略)
61: 1999年 8月17日
62: 1999年 8月26日
63: 1999年 9月 7日
64: 1999年 9月16日
65: 1999年 9月25日


【結果の考察1】
 以上の結果から、各運命数に対応する日付の数が分かった。上に表示されているだけで、運命数が1になる日の数は、(1900年1月1日~2022年12月31日の範囲で)4,991日、44になる日の数は65日と、運命数に対して異なっている。そこで、全部の運命数に対する日数と、全体に対する割合を一覧表にし、さらに円グラフを作成して、運命数の出現する度数分布の可視化を試みた。

【運命数ごとの日付の数の一覧表および円グラフ】
グラフ1

【結果の考察2】
 運命数が1~9の範囲では、2・4・6以外ではほぼ約11%で同程度であったが、運命数2で約3%、運命数4・6で約7%と少なくなった。これは、「運命数を計算する途中でぞろ目になったらその数を運命数とする」というルールによると考えられる。例えば、日付の各桁数の合計が2となるもののうち、計算の途中で11となるものはそれから除かれるので、相対的に運命数が2となる場合の数は減少する。同様に、合計が4となるものからは22となるものが、合計が6となるものからは33となるものが除かれることになる。そこで、運命数2と11、4と22、6と33の割合をそれぞれ合計すると、2.958 + 8.147 = 11.105、7.317 + 3.793 = 11.110、6.836 + 4.278 = 11.114と、他の場合と同等の割合となった。残った、運命数44の場合が全体の0.145%と、極めて低い値となったが、これも同様に考えて運命数8の場合との合計を計算してみると、10.976 + 0.145 = 11.121となり、予測通りの結果となった。今回は、対象期間を1900年1月1日~2022年12月31日としたが、その範囲においては運命数44というのが極めてまれな出現確率の数であることが示された。プログラミングの手段としたC言語の仕様によりこの期間としたが、別の期間で計算してみると当然結果が変わってくると思われる。

戻る