std :: chrono :: yearsストレージは本当に少なくとも17ビットですか?


14

cppreferenceから

std::chrono::years (since C++20) duration</*signed integer type of at least 17 bits*/, std::ratio<31556952>>

を使用libc++すると、16ビットに署名されているstd::chrono::yearsis の下線付きストレージが表示さshortます

std::chrono::years( 30797 )        // yields  32767/01/01
std::chrono::years( 30797 ) + 365d // yields -32768/01/01 apparently UB

cppreferenceまたは何か他のタイプミスはありますか?

例:

#include <fmt/format.h>
#include <chrono>

template <>
struct fmt::formatter<std::chrono::year_month_day> {
  char presentation = 'F';

  constexpr auto parse(format_parse_context& ctx) {
    auto it = ctx.begin(), end = ctx.end();
    if (it != end && *it == 'F') presentation = *it++;

#   ifdef __exception
    if (it != end && *it != '}') {
      throw format_error("invalid format");
    }
#   endif

    return it;
  }

  template <typename FormatContext>
  auto format(const std::chrono::year_month_day& ymd, FormatContext& ctx) {
    int year(ymd.year() );
    unsigned month(ymd.month() );
    unsigned day(ymd.day() );
    return format_to(
        ctx.out(),
        "{:#6}/{:#02}/{:#02}",
        year, month, day);
  }
};

using days = std::chrono::duration<int32_t, std::ratio<86400> >;
using sys_day = std::chrono::time_point<std::chrono::system_clock, std::chrono::duration<int32_t, std::ratio<86400> >>;

template<typename D>
using sys_time = std::chrono::time_point<std::chrono::system_clock, D>;
using sys_day2 = sys_time<days>;

int main()
{
  auto a = std::chrono::year_month_day( 
    sys_day( 
      std::chrono::floor<days>(
        std::chrono::hours( (1<<23) - 1 ) 
      )
    )
  );

  auto b = std::chrono::year_month_day( 
    sys_day( 
      std::chrono::floor<days>(
        std::chrono::minutes( (1l<<29) - 1 ) 
      )
    )
  );

  auto c = std::chrono::year_month_day( 
    sys_day( 
      std::chrono::floor<days>(
        std::chrono::seconds( (1l<<35) - 1 ) 
      )
    )
  );

  auto e = std::chrono::year_month_day( 
    sys_day( 
      std::chrono::floor<days>(
        std::chrono::days( (1<<25) - 1 ) 
      )
    )
  );

  auto f = std::chrono::year_month_day( 
    sys_day( 
      std::chrono::floor<days>(
        std::chrono::weeks( (1<<22) - 1 ) 
      )
    )
  );

  auto g = std::chrono::year_month_day( 
    sys_day( 
      std::chrono::floor<days>(
        std::chrono::months( (1<<20) - 1 ) 
      )
    )
  );

  auto h = std::chrono::year_month_day( 
    sys_day( 
      std::chrono::floor<days>(
        std::chrono::years( 30797 ) // 0x7FFF - 1970
      )
    )
  );

  auto i = std::chrono::year_month_day( 
    sys_day( 
      std::chrono::floor<days>(
        std::chrono::years( 30797 ) // 0x7FFF - 1970
      ) + std::chrono::days(365)
    )
  );

  fmt::print("Calendar limit by duration's underlining storage:\n"
             "23 bit hour       : {:F}\n"
             "29 bit minute     : {:F}\n"
             "35 bit second     : {:F}\n"
             "25 bit days       : {:F}\n"
             "22 bit week       : {:F}\n"
             "20 bit month      : {:F}\n"
             "16? bit year      : {:F}\n"
             "16? bit year+365d : {:F}\n"
             , a, b, c, e, f, g, h, i);
}

ゴッドボルトリンク


2
year範囲:eel.is/c++draft/time.cal.year#members-19 years範囲: eel.is/c++draft/time.synyear民生年の「名前」であり、16ビットが必要です。 yearsはクロノ期間であり、とは異なりyearます。2 yearを減算すると、結果はtypeになりyearsます。 yearsの結果を保持できるようにする必要がありますyear::max() - year::min()
ハワードヒンナン

1
std::chrono::years( 30797 ) + 365dコンパイルしません。
ハワードヒンナン

1
の結果years{30797} + days{365}は、単位が216秒の204528013です。
ハワードヒンナン

1
追加される期間は2つだけです。禁止することは禁止することを意味しhours{2} + seconds{5}ます。
ハワードヒンナン

4
私の推測では、彼らはので、あなたが持続タイプと暦のコンポーネントを混乱しているということである、そのような類似した名前を持っています。ここでは一般的なルールです: duration名前が複数あります: yearsmonthsdays。暦コンポーネント名は単数形です: yearmonthdayyear{30797} + day{365}コンパイル時のエラーです。 year{2020}今年です。 years{2020}2020年の期間です。
ハワードヒンナン

回答:


8

cppreferenceの記事は正しいです。libc ++がより小さい型を使用する場合、これはlibc ++のバグのようです。


しかしword、おそらくほとんど使用されていない別のものを追加しても、year_month_dayベクトルが不必要に大きくならないでしょうか?それat least 17 bitsは通常のテキストとしてカウントされませんか?
サンソーン

3
@sandthornにyear_month_dayyear、ではなくが含まれますyearsyearタイプshortは説明として使用されますが、の表現は16ビットである必要はありません。OTOH、years定義の17ビット部分は、説明としてのみマークされていないため、規範的です。率直に言って、それが少なくとも17ビットであり、それを必要としないと言っても意味がありません。
Andrey Semashev

1
ああ確かyearyear_month_dayそうintです。=> operator intこれはat least 17 bits years実装をサポートすると思います。
サンソーン

回答を編集していただけませんか?それは判明のstd ::クロノ::年間で実際にint型とのstd ::クロノ::年間でarbitarily 32767で最大...
sandthorn

@sandthorn正解です。なぜ編集する必要があるのか​​わかりません。
Andrey Semashev

4

私はhttps://godbolt.org/z/SNivypで例を少しずつ分解しています

  auto a = std::chrono::year_month_day( 
    sys_days( 
      std::chrono::floor<days>(
        std::chrono::years(0) 
        + std::chrono::days( 365 )
      )
    )
  );

単純化と仮定using namespace std::chronoは範囲内です:

year_month_day a = sys_days{floor<days>(years{0} + days{365})};

部分式がyears{0}あるdurationperiod等しいratio<31'556'952>値に等しいです0years{1}浮動小数点として表されるdaysは正確に365.2425であることに注意してください。これは民生年の平均の長さです。

部分式がdays{365}あるdurationperiod等しいratio<86'400>値に等しいです365

部分式がyears{0} + days{365}あるdurationperiod等しいratio<216>値に等しいです146'000。これは、最初の発見によって形成されているcommon_type_tratio<31'556'952>ratio<86'400>GCD(31'556'952、86'400)、または216本の共通部にライブラリ最初の変換の両方のオペランドであり、そして、共通単位で加算を行います。

years{0}周期が216秒の単位に変換するには、0に146'097を掛ける必要があります。これは非常に重要なポイントです。この変換は、32ビットのみで実行すると、簡単にオーバーフローを引き起こす可能性があります。

<さておき>

この時点で混乱している場合は、コードがカレンダー計算を意図している可能性が高いが、実際には時系列計算を行っているためです。カレンダー計算は、カレンダーを使用した計算です。

カレンダーには、月単位と年単位で日数の物理的な長さが異なるなど、あらゆる種類の不規則性があります。カレンダー計算では、これらの不規則性が考慮されます。

時系列計算は固定単位で機能し、カレンダーに関係なく数値をクランクアウトします。グレゴリオ暦、ユリウス暦、ヒンドゥー暦、中国暦などを使用しても、年代順の計算は問題になりません。

</ aside>

次は、私たちは取る146000[216]s時間をしてと期間に変換periodするratio<86'400>(名前付きの型エイリアスを持っていますdays)。関数floor<days>()はこの変換を行い、結果は365[86400]s、またはもっと簡単に言えばです365d

次のステップでは、durationを取得してに変換しtime_pointます。タイプがtime_pointあるtime_point<system_clock, days>という名前の型の別名を持っていますsys_days。これは、うるう秒を除いた1970-01-01 00:00:00 UTCのエポックdays以降の単純なカウントですsystem_clock

最後に、はの値でsys_daysaに変換されyear_month_dayます1971-01-01

この計算を行う簡単な方法は次のとおりです。

year_month_day a = sys_days{} + days{365};

次のような計算について考えてみます。

year_month_day j = sys_days{floor<days>(years{14699} + days{0})};

これは日付になります16668-12-31。これはおそらく、予想していたよりも1日早い((14699 + 1970)-01-01)。部分式years{14699} + days{0}は次のとおり 2'147'479'803[216]sです。実行時の値が近づいていることに注意してくださいINT_MAX2'147'483'647)、および下にあることrepの両方yearsdaysなりますint

実際、years{14700}単位に変換すると[216]sオーバーフローが発生します -2'147'341'396[216]s

これを修正するには、カレンダー計算に切り替えます。

year_month_day j = (1970y + years{14700})/1/1;

https://godbolt.org/z/SNivypにある14699より大きい値を追加yearsdaysて使用している結果はすべてオーバーフローが発生しています。yearsint

1が実際に時系列で計算やりたい場合yearsdays、このように、64ビット演算を使用するのが賢明だろう。これは、計算の早い段階で32ビットを超える使用でyears単位に変換することによって実現できますrep。例えば:

years{14700} + 0s + days{0}

に追加する0sことyearsで(seconds少なくとも35ビットを持つ必要があります)、common_type rep最初の追加(years{14700} + 0s)では64ビットに強制され、追加時に64ビットで続行しますdays{0}

463'887'194'400s == 14700 * 365.2425 * 86400

(この範囲で)中間のオーバーフローを回避するさらに別の方法は、さらに追加するyearsdays精度切り捨てることdaysです。

year_month_day j = sys_days{floor<days>(years{14700})} + days{0};

j値があります16669-12-31。これにより、[216]sユニットが最初から作成されることがないため、問題が回避されます。またyearsdaysやの制限に近づくこともありませんyear

を期待していた16700-01-01としても、問題は解決せず、それを修正する方法は代わりにカレンダー計算を行うことです:

year_month_day j = (1970y + years{14700})/1/1;

1
素晴らしい説明。時系列計算が心配です。years{14700} + 0s + days{0}コードベースで見ると、0sそこで何が行われていて、それがどれほど重要であるかわかりません。別の、おそらくより明示的な方法はありますか?のようなものduration_cast<seconds>(years{14700}) + days{0}が良いでしょうか?
ボロフ

duration_castduration_cast非切り捨て変換に使用するのは不適切な形式であるため、さらに悪くなります。変換の切り捨ては論理エラーの原因となる可能性があるため、必要な場合にのみ「ビッグハンマー」を使用することをお勧めします。これにより、コード内で切り捨てられる変換を簡単に見つけることができます。
ハワードヒンナン

1
カスタムの期間を作成して、use llyears = duration<long long, years::period>;代わりにそれを使用することができます 。しかし、おそらく最良のことは、達成しようとしていることを考え、それを正しい方法で行っているかどうかを質問することです。たとえば、1万年の時間スケールでの日精度が本当に必要ですか?公暦は、4千年で約1日しか正確ではありません。多分、浮動小数点数千年がより良い単位でしょうか?
ハワードヒンナン

明確化:クロノの民間暦のモデリングは-32767/1/1から32767/12/31の範囲で正確です。太陽系のモデル化に関する内暦の正確さは、4千年で約1日です。
ハワードヒンナン

1
それは本当にユースケースに依存するでしょう、そして私は現在、追加する動機付けのユースケースを考えるのに苦労yearsしていdaysます。これは、文字通り365.2425日の倍数をいくつかの整数日に追加しています。通常、月単位または年単位で年代順の計算を行う場合は、物理学または生物学をモデル化します。おそらく、追加するmonthsためのさまざまな方法に関するこの投稿system_clock::time_pointは、2つのタイプの計算の違いを明確にするのに役立ちます:stackoverflow.com/a/43018120/576911
Howard Hinnant
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.