この古い質問に出くわし、新しい情報を追加できるかもしれないと思いました。Thomas Porninによってこれを書いている既存の単一の回答は良い回答であり、私はそれを支持しました。しかし、私はそれをより良くするための挑戦として受け止めました。同じ答えを 2 倍の速さで出すことができたらどうでしょうか? 多分もっと速い?
この取り組みをテストするために、Thomas の回答を関数でラップしました。
#include <tuple>
std::tuple<int, int, int>
ymd_from_ydoy1(int year, int day_of_year)
{
static const int month_len[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
int leap = (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0);
int day_of_month = day_of_year;
int month;
for (month = 0; month < 12; month ++) {
int mlen = month_len[month];
if (leap && month == 1)
mlen ++;
if (day_of_month <= mlen)
break;
day_of_month -= mlen;
}
return {year, month, day_of_month};
}
そして、それを改善するための私の試みは、以下に基づいています:
クロノ互換の低レベル日付アルゴリズム
上記の記事では、この状況に直接対処していません。ただし、日付操作に関連するアルゴリズムの詳細な説明が含まれており、「年間通算日」の概念も含まれていますが、その概念はこの質問で指定されているものとは異なります。
この質問では、「年間通算日」は 1 から始まるカウントで、1 月 1 日が年の始まりです (1 月 1 日 == 1 日目)。 クロノ互換の低レベル日付アルゴリズムには、アルゴリズムに同様の「年間通算日」の概念がありますcivil_from_days
が、それは 3 月 1 日を過ぎた日数 (3 月 1 日 == 0 日) です。
私の考えでは、必要な結果を見つけるために 12 か月にわたって反復する必要のない、小片を選んcivil_from_days
で新しいものを作成できたということです。ymd_from_ydoy
これが私が思いついたものです:
std::tuple<int, int, int>
ymd_from_ydoy2(int year, int day_of_year)
{
int leap = (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0);
if (day_of_year < 60 + leap)
return {year, day_of_year/32, day_of_year - (day_of_year/32)*31};
day_of_year -= 60 + leap;
int mp = (5*day_of_year + 2)/153;
int day_of_month = day_of_year - (153*mp+2)/5 + 1;
return {year, mp + 2, day_of_month};
}
支店はまだありますが、その数は少なくなっています。この代替案の正確性とパフォーマンスの両方をテストするために、次のように記述しました。
#include <iostream>
#include <chrono>
#include <cassert>
template <class Int>
constexpr
bool
is_leap(Int y) noexcept
{
return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0);
}
constexpr
unsigned
last_day_of_month_common_year(unsigned m) noexcept
{
constexpr unsigned char a[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
return a[m-1];
}
template <class Int>
constexpr
unsigned
last_day_of_month(Int y, unsigned m) noexcept
{
return m != 2 || !is_leap(y) ? last_day_of_month_common_year(m) : 29u;
}
int
main()
{
using namespace std;
using namespace std::chrono;
typedef duration<long long, pico> picoseconds;
picoseconds ps1{0};
picoseconds ps2{0};
int count = 0;
const int ymax = 1000000;
for (int y = -ymax; y <= ymax; ++y)
{
bool leap = is_leap(y);
for (int doy = 1; doy <= 365 + leap; ++doy, ++count)
{
auto d1 = ymd_from_ydoy1(y, doy);
auto d2 = ymd_from_ydoy2(y, doy);
assert(d1 == d2);
}
}
auto t0 = high_resolution_clock::now();
for (int y = -ymax; y <= ymax; ++y)
{
bool leap = is_leap(y);
for (int doy = 1; doy <= 365 + leap; ++doy)
{
auto d1 = ymd_from_ydoy1(y, doy);
auto d2 = ymd_from_ydoy1(y, doy);
assert(d1 == d2);
}
}
auto t1 = high_resolution_clock::now();
for (int y = -ymax; y <= ymax; ++y)
{
bool leap = is_leap(y);
for (int doy = 1; doy <= 365 + leap; ++doy)
{
auto d1 = ymd_from_ydoy2(y, doy);
auto d2 = ymd_from_ydoy2(y, doy);
assert(d1 == d2);
}
}
auto t2 = high_resolution_clock::now();
ps1 = picoseconds(t1-t0)/(count*2);
ps2 = picoseconds(t2-t1)/(count*2);
cout << ps1.count() << "ps\n";
cout << ps2.count() << "ps\n";
}
このテストには 3 つのループがあります。
- 2 つのアルゴリズムが +/- 100 万年の範囲で同じ結果を生成することをテストします。
- 最初のアルゴリズムの時間を計ります。
- 2 番目のアルゴリズムの時間を計ります。
どちらのアルゴリズムも非常に高速であることが判明しました...私がテストしているiMac Core i5では数ナノ秒です。したがって、小数ナノ秒の一次推定値を取得するためにピコ秒が導入されました。
typedef duration<long long, pico> picoseconds;
次の 2 点を指摘したいと思います。
- 測定単位としてピコ秒を使用し始めていることは、どれほど素晴らしいことでしょうか?
std::chrono
ピコ秒と簡単に相互運用できるのは、どれほどクールなことでしょうか?
私にとって、このテストは(およそ)出力します:
8660ps
2631ps
は よりも約3.3ymd_from_ydoy2
倍高速であることを示しymd_from_ydoy1
ます。
お役に立てれば。この回答から得られる重要なこと:
- chrono-compatible Low-Level Date Algorithmsには、日付操作のための便利で効率的なアルゴリズムがあります。この例のように、アルゴリズムをバラバラにして再構築する必要がある場合でも、それらは役に立ちます。アルゴリズムの説明は、それらを分離して、このような例で再適用できるようにするためにあります。
<chrono>
非常に高速な機能を測定する際に非常に柔軟に対応できます。非常に速いよりも 3 倍速くても、それでも十分な勝利です。