ショッピングモール  Automotive / Motorcycles ( Repair & Performance )  Automotive ( Parts )


掲示板利用宣言

 次のフォームをすべてチェックしてからご利用ください。

 私は

 題名と投稿者名は具体的に書きます。
 課題の丸投げはしません。
 ソースの添付は「HTML変換ツール」で字下げします。
 返信の引用は最小限にします。
 環境(OSとコンパイラ)や症状は具体的に詳しく書きます。
 返信の付いた投稿は削除しません。
 マルチポスト(多重投稿)はしません。

掲示板1

管理者用メニュー    ツリーに戻る    携帯用URL    ホームページ    記事検索    ログ    タグ一覧

No.5345

ある日時からN秒前or後の日時の算出
投稿者---chu-(2006/01/26 08:54:13)


テスト環境: LSIC Win98

ある日時からN秒前or後の日時を算出する関数を作成しています。
条件として、time.hは実行環境用のコンパイラが対応していないので使えません。
[過去ログNo.15649]のコードを参考に[test.c]のように組みました。
http://f4.aaa.livedoor.jp/~pointc/log1188.html

期待通り動いていると思うのですが、[実行結果]にある[大きいNのテスト]が体感でわかるほど遅いのが気になります。
速くする考え方をアドバイス頂けないでしょうか。

[test.c]
#include <stdio.h>

void calc_datetime(int *year, int *month, int *day, int *hour, int *min, int *sec, long n)
{
    const int table[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
    int day_max;
    long sec_long;

    sec_long = (long)*sec + n;
    if ( sec_long >= 0 ) {
        while ( sec_long >= 60 ) {
            sec_long -= 60;
            (*min)++;
            if ( *min >= 60 ) {
                *min = 0;
                (*hour)++;
                if ( *hour >= 24 ) {
                    *hour = 0;
                    (*day)++;
                    day_max = table[*month];
                    if ( (*month == 2) && (*year % 4 == 0 && (*year % 100 != 0 || *year % 400 == 0)) ) {
                        day_max++;
                    }
                    if ( *day > day_max ) {
                        *day = 1;
                        (*month)++;
                        if ( *month > 12 ) {
                            *month = 1;
                            (*year)++;
                        }
                    }
                }
            }
        }
    }
    else {
        while ( sec_long < 0 ) {
            sec_long += 60;
            (*min)--;
            if ( *min < 0 ) {
                *min = 59;
                (*hour)--;
                if ( *hour < 0 ) {
                    *hour = 23;
                    (*day)--;
                    if ( *day <= 0 ) {
                        (*month)--;
                        if ( *month <= 0 ) {
                            *month = 12;
                            (*year)--;
                        }
                        day_max = table[*month];
                        if ( (*month == 2) && (*year % 4 == 0 && (*year % 100 != 0 || *year % 400 == 0)) ) {
                            day_max++;
                        }
                        *day = day_max;
                    }
                }
            }
        }
    }
    *sec = (int)sec_long;
}

int main(void)
{
    int year, month, day, hour, min, sec;
    long n;

    while ( 1 ) {
        printf("年/月/日 時:分:秒>");
        scanf("%d/%d/%d %d:%d:%d", &year, &month, &day, &hour, &min, &sec);

        printf("%04d/%02d/%02d %02d:%02d:%02d ±Nsec>", year, month, day, hour, min, sec);
        scanf("%ld", &n);

        calc_datetime(&year, &month, &day, &hour, &min, &sec, n);

        printf("%04d/%02d/%02d %02d:%02d:%02d\n", year, month, day, hour, min, sec);
    }

    return 0;
}

[実行結果]
>test
年/月/日 時:分:秒>2005/12/31 1:2:3          // 年跨ぎのテスト(加算) OK
2005/12/31 01:02:03 ±Nsec>+86400
2006/01/01 01:02:03
年/月/日 時:分:秒>2006/1/1 1:2:3            // 年跨ぎのテスト(減算) OK
2006/01/01 01:02:03 ±Nsec>-86400
2005/12/31 01:02:03
年/月/日 時:分:秒>2003/2/28 1:2:3           // 通常年のテスト(加算) OK
2003/02/28 01:02:03 ±Nsec>+86400
2003/03/01 01:02:03
年/月/日 時:分:秒>2003/3/1 1:2:3            // 通常年のテスト(減算) OK
2003/03/01 01:02:03 ±Nsec>-86400
2003/02/28 01:02:03
年/月/日 時:分:秒>2004/2/28 1:2:3           // 閏年のテスト(加算) OK
2004/02/28 01:02:03 ±Nsec>+86400
2004/02/29 01:02:03
年/月/日 時:分:秒>2004/2/28 1:2:3           // 閏年のテスト(加算) OK
2004/02/28 01:02:03 ±Nsec>+172800
2004/03/01 01:02:03
年/月/日 時:分:秒>2004/3/1 1:2:3            // 閏年のテスト(減算) OK
2004/03/01 01:02:03 ±Nsec>-86400
2004/02/29 01:02:03
年/月/日 時:分:秒>2000/1/1 0:0:0            // 大きいNのテスト(加算) 遅い
2000/01/01 00:00:00 ±Nsec>+1234567890
2039/02/13 23:31:30
年/月/日 時:分:秒>2000/1/1 0:0:0            // 大きいNのテスト(減算) 遅い
2000/01/01 00:00:00 ±Nsec>-1234567890
1960/11/17 00:28:30
年/月/日 時:分:秒>^C

>



この投稿にコメントする

削除パスワード

発言に関する情報 題名 投稿番号 投稿者名 投稿日時
<子記事> Re:ある日時からN秒前or後の日時の算出 5346 iijima 2006/01/26 09:48:06
<子記事> Re:ある日時からN秒前or後の日時の算出 5347 かずま 2006/01/26 16:56:39


No.5346

Re:ある日時からN秒前or後の日時の算出
投稿者---iijima(2006/01/26 09:48:06)


除算、剰余算を使い、先にNを日・時・分・秒に換算してから処理してはいかがでしょうか。

M(分)= N / 60(N÷60,小数点以下切り捨て)
S(秒)= N % 60(N÷60の余り)

H(時)= M / 60
M(分)= M % 60

D(日)= H / 24
H(時)= H % 24



この投稿にコメントする

削除パスワード

No.5348

Re:ある日時からN秒前or後の日時の算出
投稿者---chu-(2006/01/26 17:20:23)


返信ありがとうございます。

[大きいNのテスト]が体感でわかるほど速くなりました。
しかし、これを減算に対応させるにはどう考えればよいのでしょうか。

[test.c]
#include <stdio.h>

void calc_datetime(int *year, int *month, int *day, int *hour, int *min, int *sec, long n)
{
    const int table[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
    int day_max;
    long hour_long;
    long min_long;
    long sec_long;

    sec_long = (long)*sec + n;
    min_long = (long)*min + sec_long / 60;
    hour_long = (long)*hour + min_long / 60;

    *sec = (int)(sec_long % 60);
    *min = (int)(min_long % 60);
    *hour = (int)(hour_long % 24);

    *day += (int)(hour_long / 24);

    while ( 1 ) {
        day_max = table[*month];
        if ( (*month == 2) && (*year % 4 == 0 && (*year % 100 != 0 || *year % 400 == 0)) ) {
            day_max++;
        }
        if ( *day <= day_max ) {
            break;
        }
        *day -= day_max;
        (*month)++;
        if ( *month > 12 ) {
            *month = 1;
            (*year)++;
        }
    }
}

int main(void)
{
    int year, month, day, hour, min, sec;
    long n;

    while ( 1 ) {
        printf("年/月/日 時:分:秒>");
        scanf("%d/%d/%d %d:%d:%d", &year, &month, &day, &hour, &min, &sec);

        printf("%04d/%02d/%02d %02d:%02d:%02d ±Nsec>", year, month, day, hour, min, sec);
        scanf("%ld", &n);

        calc_datetime(&year, &month, &day, &hour, &min, &sec, n);

        printf("%04d/%02d/%02d %02d:%02d:%02d\n", year, month, day, hour, min, sec);
    }

    return 0;
}



この投稿にコメントする

削除パスワード

No.5347

Re:ある日時からN秒前or後の日時の算出
投稿者---かずま(2006/01/26 16:56:39)


> 期待通り動いていると思うのですが、[実行結果]にある[大きいNのテスト]
> が体感でわかるほど遅いのが気になります。
> 速くする考え方をアドバイス頂けないでしょうか。

ループを使わないものと書いてみました。バグがあるかもしれません。
#include <stdio.h>

void calc_datetime(int *year, int *month, int *day,
                   int *hour, int *min,   int *sec, long n)
{
    static int t[] = { 306,337,0,31,61,92,122,153,184,214,245,275 };
    long x, z;  int y, m, d, f = 0;
    n += (*hour * 60L + *min) * 60L + *sec;
    z = (n < 0) ? -(-n / (60L * 60 * 24) + 1) : n / (60L * 60 * 24);
    n -= z * (60L * 60 * 24);
    *sec = n % 60, *min = n / 60 % 60, *hour = n / (60 * 60);
    y = (*month < 3) ? *year - 1 : *year;
    x = 365L*y + y/4 - y/100 + y/400 + t[*month-1] + *day + z - 1;
    y = x / (365L * 400 + 97) * 400, x %= 365L * 400 + 97;
    d = x / (365L * 100 + 24),       x %= 365L * 100 + 24;
    y += x / (365 * 4 + 1) * 4 + d * 100,  x %= 365 * 4 + 1;
    if (d == 4) f = 1;
    y += d = x / 365;  x %= 365;
    if (d == 4) f = 1;
    m = (x * 5 + 2) / 153 + 3;
    if (m > 12) m -= 12, y++;
    d = x - t[m-1] + 1;
    if (f) m--, d = 29;
    *year = y, *month = m, *day = d;
}

int main(void)
{
    int year, month, day, hour, min, sec;  long n;

    for (;;) {
        printf("年/月/日 時:分:秒>");
        if (scanf("%d/%d/%d %d:%d:%d",
                &year, &month, &day, &hour, &min, &sec) != 6) break;
        printf("%04d/%02d/%02d %02d:%02d:%02d ±Nsec>",
                year, month, day, hour, min, sec);
        if (scanf("%ld", &n) != 1) break;
        calc_datetime(&year, &month, &day, &hour, &min, &sec, n);
        printf("%04d/%02d/%02d %02d:%02d:%02d\n",
                year, month, day, hour, min, sec);
    }
    return 0;
}



この投稿にコメントする

削除パスワード

No.5350

Re:ある日時からN秒前or後の日時の算出
投稿者---かずま(2006/01/26 17:36:56)


> ループを使わないものと書いてみました。バグがあるかもしれません。
バグありました。

    n -= z * (60L * 60 * 24);

の直後に、

    if (n == 60L * 60 * 24) n = 0, z++;

を追加してください。



この投稿にコメントする

削除パスワード

No.5351

Re:ある日時からN秒前or後の日時の算出
投稿者---chu-(2006/01/27 02:11:41)


返信ありがとうございます。

[大きいNのテスト(加算)]を例にとってtimeGetTime()で測定しました。

No.5345:1000000回:128880000ms(推測)(実測:100回:12888ms)
No.5348:1000000回:10716ms
No.5347:1000000回:691ms

No.5347(+No.5350)を読み解き、理解した上で自分風に書き直して使用しようと思います。
躓いたときに解説を頂けると幸いです。



この投稿にコメントする

削除パスワード

No.5352

Re:ある日時からN秒前or後の日時の算出
投稿者---たいちう(2006/01/27 12:12:00)


> No.5347(+No.5350)を読み解き、
> 理解した上で自分風に書き直して使用しようと思います。

水を挿すようで悪いのですが時間の無駄ではないかと。

No.5345とNo.5348の違いをまず理解してください。
実行時間の違いはループの回数にあります。
秒単位の回数だったのが日単位の回数になることで
飛躍的に(理論的?には86,400倍に)性能が向上します。

さらに工夫してループそのものをなくしてしまったのが、
かずまさんのプログラムです。この掲示板を訪れる実力者は多いですが、
その中でもかずまさんの(少なくともこの手の)実力はトップレベルだと、
私は感じています。

私もプロですが、同じものを作ることはできないでしょう。
よほど時間をかけても効率面でかずまさんのものよりも劣ると思います。
(特に1月と2月の扱いは私には思いつけないかと)

このように、No.5347は特殊なプログラムですので、理解できなくても
構わないものです(恥ずかしくないんだぞ、俺)。時間や才能によほど
恵まれているのでないなら避けて通ったほうが無難でしょう。

それでも挑戦するんだ、というなら、是非頑張ってください。
途中までしか理解できなかったとしても、得られるものは大きいと思います。


この投稿にコメントする

削除パスワード

No.5353

Re:ある日時からN秒前or後の日時の算出
投稿者---かずま(2006/01/28 16:55:49)


> 水を挿すようで悪いのですが時間の無駄ではないかと。

コンパイル可能な完全ソースと実行結果までつけて具体的にアドバイスを求め、
さらに得られた回答には実行時間の計測まで行って返事するという最近まれに
見る理想的な質問者に対して、そんな言い方は失礼ではないでしょうか?


> No.5345とNo.5348の違いをまず理解してください。
> 実行時間の違いはループの回数にあります。
> 秒単位の回数だったのが日単位の回数になることで
> 飛躍的に(理論的?には86,400倍に)性能が向上します。

No.5345 はループ 1回につき 60秒減少するから分単位で、
No.5348 はループ 1回につき ひと月減少するから月単位ではありませんか?


> このように、No.5347は特殊なプログラムですので、理解できなくても
> 構わないものです(恥ずかしくないんだぞ、俺)。時間や才能によほど
> 恵まれているのでないなら避けて通ったほうが無難でしょう。

No.5347 は特殊なプログラムではありません。四則演算(剰余は除算に含める)
と if または 条件演算子(?:) しか使っていません。
ちゃんと説明すれば誰でも理解できるものと思われます。

Y年 M月 D日 h時 m分 s秒に n秒を足すという問題ですね。

まず、時分秒を秒に変換してから n を足して、また時分秒に戻します。

    S = ((h * 60) + m) * 60 + s + n
    d = S / (60 * 60 * 24)
    S = S % (60 * 60 * 24)
    h = S / (60 * 60)
    m = S / 60 % 60
    s = S % 60

S が大きい場合 d日の繰り上がりが生じます。

また、S が負の場合は、(60 * 60 * 24) で割って d日前を求めるときに、
1日余分に前にもどし、余りの時分秒を正にする必要があります。

    d = - ((-S) / (60 * 60 * 24) + 1)
    S = S - d * (60 * 60 * 24)

ただし、-S が (60 * 60 * 24) で割り切れるときは、1日前に戻しすぎて、
時分秒が 24:00:00 になるので、d++, S = 0 にします。(これが訂正したバグ)


次に、年月日を通算日に変換してから d を足して、また年月日に戻します。

通算日 x は 0年 3月 1日を起点とすると、次の式で求められます。

    int t[] = { 306,337,0,31,61,92,122,153,184,214,245,275 }
    y = (M < 3) ? (Y - 1) : Y
    x = 365*y + y/4 - y/100 + y/400 + t[M-1] + D - 1

ここで t は、3月1日を起点とする年内での通算日の表です。
閏年の扱いを簡単にするために、1月と2月は前年の12月に続くものとします。

x に d を足して、あとはその x を年月日に戻すだけです。

0年3月1から 4年2月29日までの日数は (365 * 4 + 1)。
4年3月1から 8年2月29日までの日数は (365 * 4 + 1)。
8年3月1から12年2月29日までの日数は (365 * 4 + 1)。

これを 25個集めると (365 * 100 + 25) になりますが、100年は閏年ではないので、
0年3月1から 100年2月28日までの日数は (365 * 100 + 24)。

これを 4個集めると (365* 400 + 96) になりますが、400年は閏年なので、
0年3月1から 400年2月29日までの日数は (365 * 400 + 97)。

このことから通算日 x は、次のように表せます。

    x = (365 * 400 + 97) * Q + (365 * 100 + 24) * c
       + (365 * 4 + 1) * q + 365 * k + (年内の通算日)

まず、Q から求めます。

    Q = x / (365 * 400 + 97)
    x = x % (365 * 400 + 97)

Q が 4 なら 1600〜2000年、Q が 5 なら 2000〜2400年です。
x は 400年間の中の通算日になりました。

    c = x / (365 * 100 + 24)
    x = x % (365 * 100 + 24)

c が 0 なら 0〜100年、c が 3 なら 300〜400年です。
x は 100年間の中の通算日になりました。

    q = x / (365 * 4 + 1)
    x = x % (365 * 4 + 1)

q が 0 なら 0〜4年、q が 24 なら 96〜100年です。
x は 4年間の中の通算日になりました。

    k = x / 365
    x = x % 365

k が 4年間の中の何年目かを表します。
x は 年内の通算日になりました。

西暦 y年は、y = 400 * Q + 100 * c + 4 * q + k

年内の通算日 x から m月は、グラフを描いてすべての点 (x, m) が下になる
直線の方程式により、m = (x * 5 + 2) / 153 + 3

さらに m > 12 なら、1月と 2月の補正で、m -= 12, y++

月内の d 日は、d = x - t[m-1] + 1

これで、y年 m月 d日が求まりました。

ところが、この計算では閏日の 2月29日がすべて 3月1日になってしまいます。
そこで、c == 4 または k == 4 のときは m = 2, d = 29 とします。

これで、通算日 x から y年 m月 d日への変換が完了しました。



この投稿にコメントする

削除パスワード

No.5354

Re:ある日時からN秒前or後の日時の算出
投稿者---たいちう(2006/01/28 20:04:54)


> No.5345 はループ 1回につき 60秒減少するから分単位で、
> No.5348 はループ 1回につき ひと月減少するから月単位ではありませんか?

これは私の落ち度です。ご指摘有難うございます。

iijimaさんのアドバイスも日単位でしたし、私も同じ事を書いたのですが、
書き終わるとiijimaさんの書きこみ後でしたので、投稿しませんでした。
そのような先入観もあり、No.5348も日単位のプログラムと思ってました。
申し訳ありません。


> 見る理想的な質問者に対して、そんな言い方は失礼ではないでしょうか?

私はそうは思いません。
上記のようにNo.5348をよく読んでいなかったため質問者のレベルを過小評価
してしまっていた面もあります。ですが私の意図は、「初心者は難しい
プログラムに固執するよりも色々なプログラムに慣れ親しんだほうがよい。
難しいプログラムに取り組むことから得られることは大きいけど」ということで、
このことに関しては誤解されやすい文章だったとは思っていません。


> No.5347 は特殊なプログラムではありません。四則演算(剰余は除算に含める)
> と if または 条件演算子(?:) しか使っていません。
> ちゃんと説明すれば誰でも理解できるものと思われます。

説明なしで理解できる人がどれだけいるでしょうか?
その意味で特殊と思っています。
私や私の周りのレベルが低いだけかもしれませんので、
特殊だということを説得することはできません。


最後に、誤解されてはいないと思いますが、私にはかずまさんに
けんかを売る気も質問者を馬鹿にする気もありません。
私の書きこみが不適切だったとまでは思っていませんが、間違いがあったのは
事実ですので、もう少し慎重に書きこみをしたいと思います。


この投稿にコメントする

削除パスワード

No.5355

Re:ある日時からN秒前or後の日時の算出
投稿者---かずま(2006/01/29 13:01:39)


> 説明なしで理解できる人がどれだけいるでしょうか?
> その意味で特殊と思っています。
ソースを読まずに理解できる人はいません。
ソースが複雑な場合、読んでも理解できない人はたくさんいます。
ところが、No.5347 は、C プログラムとしては非常に単純なものです。

calc_datetime 内で、
・ループがない。
・関数呼び出しがない。標準ライブラリも再帰も関係ない。
・構造体を使わない。
・文字列操作がない。
・ファイル入出力がない。
・浮動小数点がない。
・ポインタの演算がない。

実際にこのソースを読み始めた人で理解できない人は少ないのではないか
と思います。

たいちゅうさんは、ソースを読んでみましたか?

字の詰まったプログラムに嫌気が差したのか、あるいは、いつも奇妙なプロ
グラムを書くやつが書いたものだから読んでも分からないだろうという先入
観をもって読まなかったのでありませんか?

実際に読んでみてください。

最初の実行文:
    n += (*hour * 60L + *min) * 60L + *sec;

これが理解できない人もいるかも知れませんが、ごく少数でしょう。
n が何を表しているか分かりますよね。
60L の L って何だろうと気になる人がいるかもしれませんが、
これを無視するか調べるかはその人の自由です。

次の文:
    z = (n < 0) ? -(-n / (60L * 60 * 24) + 1) : n / (60L * 60 * 24);

n の正負で処理が分かれます。2つのことを同時に考えられる人は少ないで
しょうから、簡単な正の場合だけを考えます。
    z = n / (60L * 60 * 24);

(60 * 60 * 24) が 1日の秒数だと分からない人がどれだけいると思いますか?
z の意味も理解できますよね。

ちょっと言い忘れていましたが、未知のプログラムを読む場合、実際に値を
あてはめて、値の変化をトレースしていくのが良い方法です。chu- さんの
[実行結果] にある 2005/12/31 01:02:03 ±Nsec>+86400 を利用するとよい
でしょう。

    n -= z * (60L * 60 * 24);
    *sec = n % 60, *min = n / 60 % 60, *hour = n / (60 * 60);

ここまで理解するのに何の問題もないと思います。もちろん全くの初心者は
想定していません。+= や ?: 程度は知っている人が対象です。

    y = (*month < 3) ? *year - 1 : *year;

1月 2月と、3月以降では扱いが違うのか。
多分うるう年のせいだろうと予想できますよね。

    x = 365L*y + y/4 - y/100 + y/400 + t[*month-1] + *day + z - 1;

y が 2005 だったら、365L*y は 2005年間の日数だなと考えますよね。
+ y/4 - y/100 + y/400 でうるう年の補正をしていることが分かりますよね。

t[*month-1] は、t の宣言を見て、*month が 12 だったら 235。
なんだこのわけの分からない数値は! と思うかもしれませんが、
3月が 0、4月が 31、5月が 62 というところを見ると、3月からの通算日で
あることが予想できるでしょう。

これで、x が通算日であることが理解できました。

    y = x / (365L * 400 + 97) * 400, x %= 365L * 400 + 97;
    d = x / (365L * 100 + 24),       x %= 365L * 100 + 24;
    y += x / (365 * 4 + 1) * 4 + d * 100,  x %= 365 * 4 + 1;

400年間の日数で割って、100年間の日数で割って、4年間の日数で割って、
y は 4年単位の年数、x は 4年内の通算日だと分かります。

    if (d == 4) f = 1;

これは何だと思うかもしれませんが、2005/12/31 という実際の値でトレース
しているときは d は 4 にならないので、後回しにしましょう。

    y += d = x / 365;  x %= 365;

ついに y が本当の年、x が年内の通算日になったことが分かります。

    if (d == 4) f = 1;

これも後回し。

    m = (x * 5 + 2) / 153 + 3;

何だこの 153 というマジックナンバーは! でも、x の値はわかっている
ので、これを当てはめると m が月を表していることに気づくはずです。

    if (m > 12) m -= 12, y++;

1月 2月の補正であることは予想できるでしょう。

    d = x - t[m-1] + 1;

日が求まりました。

    if (f) m--, d = 29;

f だと 29日か。2月のことだな。f が 1 になる場合について考えてみよう。
最初に保留した n が負の場合について考えてみよう。

これでソース読みは終わりです。

> 説明なしで理解できる人がどれだけいるでしょうか?
86400 と書かずに (60 * 60 * 24) と書いてあるでしょう。
1825 と書かずに (365 * 4 + 1) と書いてあるでしょう。
ソースというのは、ある意味で「説明」でもあるわけです。

デバッガを使うもよし。printf を散りばめるのもよし。紙と鉛筆を使って
値をトレースするのだって、すごく有効なソース読みの手段です。

そんなソース読みを最初から放棄するのは残念でなりません。



この投稿にコメントする

削除パスワード

No.5356

Re:ある日時からN秒前or後の日時の算出
投稿者---かずま(2006/01/29 14:37:46)


> たいちゅうさんは、ソースを読んでみましたか?

たいちうさんですね。名前を間違って申しわけありません。
最初の質問者が chu- さんだったから間違ったわけではありません。


この投稿にコメントする

削除パスワード

No.5360

Re:ある日時からN秒前or後の日時の算出
投稿者---かずま(2006/01/30 13:26:35)


いくつか間違いがあるので訂正しておきます。


> 3月が 0、4月が 31、5月が 62 というところを見ると、3月からの通算日で
> あることが予想できるでしょう。

5月は 62 ではなく 61 ですね。
3月1日を起点として、4月1日は31日後、5月1日は61日後ということです。

> 86400 と書かずに (60 * 60 * 24) と書いてあるでしょう。
> 1825 と書かずに (365 * 4 + 1) と書いてあるでしょう。
> ソースというのは、ある意味で「説明」でもあるわけです。

(365 * 4 + 1) は 1825 ではなく 1461 でした。


時間の計算でループを使わないプログラムを書けと言われたら、書けない人は
たくさんいるでしょう。でも、書かれたプログラムを読むだけなら、読める人
はかなりいると思います。

時分秒の繰り上がりは単純ですが、年月日の繰り上がりは複雑です。その複雑
なものをコーディングしたからと言って、プログラムが特殊なわけではありません。

年月日の計算は、3月1日を起点にして考えればちょっと簡単になるということ
を知っていれば、プログラムを書くのだって多くの人にできると思います。


この投稿にコメントする

削除パスワード

No.5362

Re:ある日時からN秒前or後の日時の算出
投稿者---chu-(2006/01/31 17:29:00)


詳細な解説ありがとうございます。

うるう年の判定方法の正しさを納得するのに時間がかかりましたが、ほぼ理解できたつもりです。
ほぼ、というのは[m = (x * 5 + 2) / 153 + 3;]の係数の算出法がわからなかったためです。
どうせマジックナンバーなら153を2の累乗値に変更しようと思い、エクセルで係数をいろいろ入力して
正しく変換する値を探しましたが結局見つかりませんでした。
数学の問題なのでしょうし、365日分の通算日→月変換の正しさは確認できたので同じ係数を使用します。

[test.c]のように書き直しました。
一番ややこしい年の処理を読みやすくするため、解説のQ,c,q,kに相当する変数を追加しました。
コンパイル(SH-C)されたアセンブラソースを見ると、No.5347に比べてスタックを2つ余分に使っていましたがOKとします。
それと、[n -= z * (60L * 60 * 24);]を参考にして剰余演算を無くしました。
結果、[測定]のように多少速くなりました。

[calc_datetime]の正しさを、[過去ログNo.2346]を参考にして組んだ[calc_datetime2]との比較によりテストしました。
http://f1.aaa.livedoor.jp/~pointc/log359.html
[実行結果]を見る限り、少なくとも1970/01/01〜2038/01/19では正しく動作していそうです。

[測定] ※No.5351とは別環境
No.5347:1000000回:1628ms
今回   :1000000回:1196ms

[test.c] ※テスト環境:BCB5.0 Win98
#include <stdio.h>
#include <time.h>

void calc_datetime(int *year, int *month, int *day, int *hour, int *min, int *sec, long n)
{
    static const int day_offset[] = { 306, 337, 0, 31, 61, 92, 122, 153, 184, 214, 245, 275 };
    long sec_total;
    long day_total;
    int day_n;
    int y, m;
    int y400, y100, y4, y1;

    sec_total = (*hour * 60L + *min) * 60 + *sec + n;
    if ( sec_total >= 0 ) {
        day_n = sec_total / (24L * 60 * 60);
        sec_total -= day_n * (24L * 60 * 60);
    }
    else {
        day_n = -(-sec_total / (24L * 60 * 60) + 1);
        sec_total -= day_n * (24L * 60 * 60);
        if ( sec_total == 24L * 60 * 60 ) {
            day_n++;
            sec_total = 0;
        }
    }
    *hour = sec_total / (60L * 60);
    sec_total -= *hour * (60L * 60);
    *min = sec_total / 60L;
    sec_total -= *min * 60L;
    *sec = sec_total;

    y = *year;
    if ( *month < 3 ) {
        y--;
    }
    day_total = y*365L + y/4 - y/100 + y/400 + day_offset[*month-1] + (*day - 1) + day_n;
    y400 = day_total / (365L * 400 + 97);
    day_total -= y400 * (365L * 400 + 97);
    y100 = day_total / (365L * 100 + 24);
    day_total -= y100 * (365L * 100 + 24);
    y4 = day_total / (365L * 4 + 1);
    day_total -= y4 * (365L * 4 + 1);
    y1 = day_total / 365L;
    day_total -= y1 * 365L;
    y = y400 * 400 + y100 * 100 + y4 * 4 + y1;
    if ( y100 < 4 && y1 < 4 ) {
        m = (day_total * 5 + 2) / 153 + 3;
        if ( m > 12 ) {
            y++;
            m -= 12;
        }
        *year = y;
        *month = m;
        *day = day_total - day_offset[m-1] + 1;
    }
    else {
        *year = y;
        *month = 2;
        *day = 29;
    }
}

void calc_datetime2(int *year, int *month, int *day, int *hour, int *min, int *sec, long n)
{
    struct tm t = { 0 };

    t.tm_year = *year - 1900;
    t.tm_mon  = *month - 1;
    t.tm_mday = *day;
    t.tm_hour = *hour;
    t.tm_min  = *min;
    t.tm_sec  = *sec + n;
    mktime(&t);
    *year  = t.tm_year + 1900;
    *month = t.tm_mon + 1;
    *day   = t.tm_mday;
    *hour  = t.tm_hour;
    *min   = t.tm_min;
    *sec   = t.tm_sec;
}

int main(void)
{
    const int table[] = { 1, 1, 86398 };
    int y1, m1, d1, h1, n1, s1;
    int y2, m2, d2, h2, n2, s2;
    long n;
    int i;

    i = 0;
    for ( n = +table[++i % 3]; n >= 0; n += table[++i % 3] ) {
        y1 = y2 = 2006;
        m1 = m2 = 1;
        d1 = d2 = 1;
        h1 = h2 = 0;
        n1 = n2 = 0;
        s1 = s2 = 0;
        calc_datetime(&y1, &m1, &d1, &h1, &n1, &s1, n);
        calc_datetime2(&y2, &m2, &d2, &h2, &n2, &s2, n);
        if ( y1 != y2 || m1 != m2 || d1 != d2 || h1 != h2 || n1 != n2 || s1 != s2 ) {
            break;
        }
    }
    printf("A:%02d/%02d/%02d %02d:%02d:%02d %ld\n", y1, m1, d1, h1, n1, s1, n);
    printf("B:%02d/%02d/%02d %02d:%02d:%02d %ld\n", y2, m2, d2, h2, n2, s2, n);

    i = 0;
    for ( n = -table[++i % 3]; n <= 0; n -= table[++i % 3] ) {
        y1 = y2 = 2006;
        m1 = m2 = 1;
        d1 = d2 = 1;
        h1 = h2 = 0;
        n1 = n2 = 0;
        s1 = s2 = 0;
        calc_datetime(&y1, &m1, &d1, &h1, &n1, &s1, n);
        calc_datetime2(&y2, &m2, &d2, &h2, &n2, &s2, n);
        if ( y1 != y2 || m1 != m2 || d1 != d2 || h1 != h2 || n1 != n2 || s1 != s2 ) {
            break;
        }
    }
    printf("A:%02d/%02d/%02d %02d:%02d:%02d %ld\n", y1, m1, d1, h1, n1, s1, n);
    printf("B:%02d/%02d/%02d %02d:%02d:%02d %ld\n", y2, m2, d2, h2, n2, s2, n);

    getchar();

    return 0;
}

[実行結果]
A:2038/01/19 23:59:59 1011484799
B:2006/01/01 00:00:1011484799 1011484799
A:1970/01/01 00:00:01 -1136073599
B:2006/01/01 00:00:-1136073599 -1136073599



この投稿にコメントする

削除パスワード

No.5366

Re:ある日時からN秒前or後の日時の算出
投稿者---かずま(2006/02/02 02:31:02)


> ほぼ、というのは[m = (x * 5 + 2) / 153 + 3;]の係数の算出法がわからなかったためです。
> どうせマジックナンバーなら153を2の累乗値に変更しようと思い、エクセルで係数をいろいろ入力して
> 正しく変換する値を探しましたが結局見つかりませんでした。
153 = 31 + 30 + 31 + 30 + 31 です。
通算日を月に変換しようとすれば 153 / 5 = 30.6 で割ればよいということです。

通算日 x は、3月を起点にしていますから、これを y = 0月として、
    03/01  x =   0 --> y = 0
    03/02  x =   1 --> y = 0
    03/03  x =   2 --> y = 0
    03/31  x =  30 --> y = 0
    04/01  x =  31 --> y = 1
    05/01  x =  61 --> y = 2
    06/01  x =  92 --> y = 3
    07/01  x = 122 --> y = 4
    08/01  x = 153 --> y = 5
    09/01  x = 184 --> y = 6
    10/01  x = 214 --> y = 7
    11/01  x = 245 --> y = 8
    12/01  x = 275 --> y = 9
    01/01  x = 306 --> y = 10
    02/01  x = 337 --> y = 11
    02/28  x = 364 --> y = 11

x軸、y軸を書いて、(x,y) の点をプロットしてグラフは書けますよね。
365個の点を全部打つ必要はありません。
(0,0), (31,1), (61,2), ... (337,11) だけを書いて、折れ線で結んでみましょう。
そうすると、(122,4) と (275,9) を結んだ直線の下にすべての点がくることが
分かるでしょう。
この直線の方程式は、y - 4 = ((9 - 4) / (275 - 122)) * (x - 122)
すなわち、y = (5 * x + 2) / 153
m = y + 3 だから、m = (x * 5 + 2) / 153 + 3 となります。
m = (x * 5 + 461) / 153 と書くと、足し算がひとつ減ってほんの少し早くなるかも
しれませんが、高速化したいんだったら、

static char h[365] = {
    3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
    4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
    5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
    6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
    7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
    8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
    9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
    10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
    11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,
    12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2
};

m = h[x];
if (m < 3) y++;

と書けばよいでしょう。

> [実行結果]を見る限り、少なくとも1970/01/01〜2038/01/19では正しく動作していそうです。
1900/03/01〜2000/02/28 の範囲でよければ、この期間は 4年に 1度の閏年ですから、
次のように簡略化でき、高速になります。

    day_total = y*365L + y/4 + day_offset[*month-1] + (*day - 1) + day_n;
    y4 = day_total / (365L * 4 + 1);
    day_total -= y4 * (365L * 4 + 1);
    y1 = day_total / 365L;
    day_total -= y1 * 365L;
    y = y4 * 4 + y1;
    if ( y1 < 4 ) {



この投稿にコメントする

削除パスワード

No.5368

Re:ある日時からN秒前or後の日時の算出
投稿者---かずま(2006/02/02 08:36:16)


> static char h[365] = {
>     3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,

マクロを使ったほうが見やすいかな?

#define F(m)  m,m,m,m,m,m,m,m,m,m,m,m,m,m,m,m,m,m,m,m,m,m,m,m,m,m,m,m /* 2月 */
#define S(m)  F(m),m,m  /* 小の月 */
#define L(m)  S(m),m    /* 大の月 */

static char h[365] = {
    L(3), S(4), L(5), S(6), L(7), L(8), S(9), L(10), S(11), L(12), L(1), F(2)
};



この投稿にコメントする

削除パスワード

No.5369

Re:ある日時からN秒前or後の日時の算出
投稿者---かずま(2006/02/02 12:00:03)


> 1900/03/01〜2000/02/28 の範囲でよければ、この期間は 4年に 1度の閏年ですから、
> 次のように簡略化でき、高速になります。

すみません。1900/03/01〜2100/02/28 の範囲の間違いです。


この投稿にコメントする

削除パスワード

No.5374

Re:ある日時からN秒前or後の日時の算出
投稿者---chu-(2006/02/03 19:03:32)


返信ありがとうございます。

> そうすると、(122,4) と (275,9) を結んだ直線の下にすべての点がくることが
> 分かるでしょう。

実際にエクセルで表を描き、直線ツールを定規代わりにあててみましたが、
(0,0)(61,2)(122,4)(214,7)(275,9)(364,11)のどれかかな? と、絞り込むことはできましたが、
残念ながら差が微妙すぎて2点までは絞り込めませんでした。
しかし、値の意味がわかったので[test.c]を組んで2点を求めることができました。

calc_datetime()も、少しでも読みやすくなればとの思いから、mの算出式を以下のように変更しました。

    m = (day_total * 5 + 2) / 153 + 3;
      ↓
    m = (day_total * (9 - 4) + (4 * (275 - 122) - 122 * (9 - 4))) / (275 - 122) + 3;

> static char h[365] = {

結果、以下のようになりました。

No.5362:1000000回:1196ms
今回   :1000000回:1102ms

試しにcharをintにしてみましたが同じ測定結果でした。

[test.c]
#include <stdio.h>

#define ARRAYSIZE(array) (sizeof(array)/sizeof(array[0]))

const int to_month[365] = {
    3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
    4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
    5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
    6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
    7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
    8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
    9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
    10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
    11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,
    12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2
};

const struct {
    int x, y;
} point[] = {
    { 0, 0 },
    { 31, 1 },
    { 61, 2 },
    { 92, 3 },
    { 122, 4 },
    { 153, 5 },
    { 184, 6 },
    { 214, 7 },
    { 245, 8 },
    { 275, 9 },
    { 306, 10 },
    { 337, 11 },
    { 364, 11 },
};

int main(void)
{
    int day_total;
    int m;
    int x1, y1;
    int x2, y2;
    int i, j;
    int ng;

    for ( i = 0; i < ARRAYSIZE(point) - 1; i++ ) {
        for ( j = i + 1; j < ARRAYSIZE(point); j++ ) {
            x1 = point[i].x;
            y1 = point[i].y;
            x2 = point[j].x;
            y2 = point[j].y;
            ng = 0;
            for ( day_total = 0; day_total < 365; day_total++ ) {
                m = (day_total * (y2 - y1) + (y1 * (x2 - x1) - x1 * (y2 - y1))) / (x2 - x1) + 3;
                if ( m > 12 ) {
                    m -= 12;
                }
                if ( m != to_month[day_total] ) {
                    ng = 1;
                    break;
                }
            }
            if ( !ng ) {
                printf("(%d,%d)-(%d,%d)\n", x1, y1, x2, y2);
            }
        }
    }

    return 0;
}

[実行結果]
>test
(122,4)-(275,9)

>



この投稿にコメントする

削除パスワード

管理者用メニュー    ツリーに戻る    携帯用URL    ホームページ    記事検索    ログ    タグ一覧




掲示板提供:Real Integrity