Ist std :: chrono :: Jahre Speicher wirklich mindestens 17 Bit?

14

Von cppreference

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

Mit libc++scheint es , die unterstreicht Speicherung std::chrono::yearsheißt , shortdie signiert ist 16 Bit .

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

Gibt es einen Tippfehler bei cppreference oder irgendetwas anderem?

Beispiel:

#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);
}

[ Godbolt Link ]

Sanddorn
quelle
2
yearBereich: eel.is/c++draft/time.cal.year#members-19 years Bereich: eel.is/c++draft/time.syn . yearist der "Name" des Ziviljahres und erfordert 16 Bit. yearsist eine Chronodauer, nicht dasselbe wie eine year. Man kann zwei subtrahieren yearund das Ergebnis hat Typ years. yearsist erforderlich, um das Ergebnis von halten zu können year::max() - year::min().
Howard Hinnant
1
std::chrono::years( 30797 ) + 365dkompiliert nicht.
Howard Hinnant
1
Das Ergebnis von years{30797} + days{365}ist 204528013 mit Einheiten von 216s.
Howard Hinnant
1
Das sind nur zwei hinzugefügte Zeiträume. Es zu verbieten würde bedeuten, zu verbieten hours{2} + seconds{5}.
Howard Hinnant
4
Meine Vermutung ist , dass Sie kalendarische Komponenten mit Dauer - Typen sind verwirrend , weil sie sich so ähnliche Namen haben. Hier ist eine allgemeine Regel: durationNamen sind Plural: years, months, days. Kalendarische Komponentennamen sind Singular: year, month, day. year{30797} + day{365}ist ein Fehler bei der Kompilierung. year{2020}ist dieses Jahr. years{2020}ist eine Dauer von 2020 Jahren.
Howard Hinnant

Antworten:

8

Der Referenzartikel ist korrekt . Wenn libc ++ einen kleineren Typ verwendet, scheint dies ein Fehler in libc ++ zu sein.

Andrey Semashev
quelle
Aber das Hinzufügen eines anderen word, der wahrscheinlich kaum verwendet wird, würde nicht dazu führen, dass year_month_dayVektoren unnötig groß werden? Könnte das at least 17 bitsnicht als normaler Text gezählt werden?
Sanddorn
3
@ Sanddorn year_month_dayenthält year, nicht years. Die Darstellung von muss yearnicht 16-Bit sein, obwohl der Typ shortals Exposition verwendet wird. OTOH, der 17-Bit-Teil in der yearsDefinition ist normativ, da er nicht nur als Exposition markiert ist. Und ehrlich gesagt ist es bedeutungslos zu sagen, dass es mindestens 17 Bit hat und es dann nicht benötigt.
Andrey Semashev
1
Ah yearin year_month_dayscheint in der intTat zu sein. => operator int Ich denke, dies unterstützt die at least 17 bits yearsImplementierung.
Sanddorn
Würde es Ihnen etwas ausmachen, Ihre Antwort zu bearbeiten? Es stellt sich heraus, dass std :: chrono :: Jahre tatsächlich int ist und std :: chrono :: Jahr maximal bei 32767 willkürlich ist.
Sanddorn
@sandthorn Die Antwort ist richtig, ich verstehe nicht, warum ich sie bearbeiten müsste.
Andrey Semashev
4

Ich zerlege das Beispiel unter https://godbolt.org/z/SNivyp Stück für Stück:

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

Vereinfachen und Annehmen using namespace std::chronoliegt im Geltungsbereich:

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

Der Unterausdruck years{0}ist aduration mit einer periodgleich ratio<31'556'952>und einem Wert gleich 0. Beachten Sie, dass years{1}, ausgedrückt als Gleitkomma days, genau 365,2425 beträgt. Dies ist die durchschnittliche Länge des Ziviljahres.

Der Unterausdruck days{365}ist ein durationmit einer periodgleich ratio<86'400>und einem Wert gleich 365.

Der Unterausdruck years{0} + days{365}ist a durationmit aperiod gleich ratio<216>und einem Wert gleich 146'000. Dies wird gebildet, indem zuerst das common_type_tvon ratio<31'556'952>und ratio<86'400>das GCD (31'556'952, 86'400) oder 216 gefunden wird. Die Bibliothek konvertiert zuerst beide Operanden in diese gemeinsame Einheit und führt dann die Addition in der gemeinsamen Einheit durch.

Um years{0}in Einheiten mit einer Periode von 216s umzurechnen, muss man 0 mit 146'097 multiplizieren. Dies ist zufällig ein sehr wichtiger Punkt. Diese Konvertierung kann leicht zu einem Überlauf führen, wenn sie nur mit 32 Bit ausgeführt wird.

<Abseite>

Wenn Sie sich zu diesem Zeitpunkt verwirrt fühlen, liegt dies daran, dass der Code wahrscheinlich eine kalendarische Berechnung beabsichtigt , aber tatsächlich eine chronologische Berechnung durchführt. Kalenderberechnungen sind Berechnungen mit Kalendern.

Kalender weisen alle Arten von Unregelmäßigkeiten auf, z. B. Monate und Jahre, die in Bezug auf Tage unterschiedlich lang sind. Eine kalendarische Berechnung berücksichtigt diese Unregelmäßigkeiten.

Eine chronologische Berechnung arbeitet mit festen Einheiten und dreht nur die Zahlen ohne Rücksicht auf Kalender aus. Eine chronologische Berechnung spielt keine Rolle, wenn Sie den Gregorianischen Kalender, den Julianischen Kalender, den Hindu-Kalender, den chinesischen Kalender usw. verwenden.

</ side>

Als nächstes nehmen wir unsere 146000[216]sDauer und konvertieren sie in eine Dauer mit einem periodvon ratio<86'400>(das einen Typ-Alias ​​namens hatdays ). Die Funktion floor<days>()führt diese Konvertierung durch und das Ergebnis ist 365[86400]soder einfacher 365d.

Der nächste Schritt nimmt das durationund wandelt es in ein time_point. Die Art der time_pointisttime_point<system_clock, days> hat einen Typalias namens sys_days. Dies ist einfach eine Zählung daysseit der system_clockEpoche, die 1970-01-01 00:00:00 UTC ist, ohne Schaltsekunden.

Endlich, das sys_days wird das year_month_daymit dem Wert in a umgewandelt1971-01-01 .

Eine einfachere Möglichkeit, diese Berechnung durchzuführen, ist:

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

Betrachten Sie diese ähnliche Berechnung:

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

Daraus ergibt sich das Datum 16668-12-31. Das ist wahrscheinlich einen Tag früher als erwartet ((14699 + 1970) -01-01). Der Unterausdruck years{14699} + days{0}lautet jetzt : 2'147'479'803[216]s. Beachten Sie, dass sich der Laufzeitwert INT_MAX( 2'147'483'647) nähert und dass repbeide zugrunde liegenyears und daysist int.

In der Tat, wenn Sie konvertieren years{14700} in Einheiten von , erhalten [216]sSie einen Überlauf : -2'147'341'396[216]s.

Um dies zu beheben, wechseln Sie zu einer Kalenderberechnung:

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

Alle Ergebnisse unter https://godbolt.org/z/SNivyp , die yearsund hinzufügendays einen Wert verwenden years, der größer als 14699 ist, werden angezeigtint Überlauf auf.

Wenn man wirklich chronologische Berechnungen mit yearsund auf daysdiese Weise durchführen möchte, ist es ratsam, 64-Bit-Arithmetik zu verwenden. Dies kann erreicht werden, indem yearszu repBeginn der Berechnung in Einheiten mit einer Verwendung von mehr als 32 Bit konvertiert wird . Zum Beispiel:

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

Durch Hinzufügen 0szu years( secondsmuss mindestens 35 Bit haben) wird das common_type repfür die erste Addition ( years{14700} + 0s) auf 64 Bit gezwungen und beim Hinzufügen in 64 Bit fortgesetzt days{0}:

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

Eine weitere Möglichkeit, einen Zwischenüberlauf (in diesem Bereich) zu vermeiden, besteht darin years, die daysGenauigkeit zu verringern , bevor weitere hinzugefügt werdendays :

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

jhat den Wert 16669-12-31. Dies vermeidet das Problem, da das [216]sGerät jetzt überhaupt nicht mehr erstellt wird. Und wir bekommen nicht einmal nahe an der Grenze für years, daysoder year.

Wenn Sie erwartet haben 16700-01-01, haben Sie immer noch ein Problem, und die Möglichkeit, es zu korrigieren, besteht darin, stattdessen eine kalendarische Berechnung durchzuführen:

year_month_day j = (1970y + years{14700})/1/1;
Howard Hinnant
quelle
1
Tolle Erklärung. Ich mache mir Sorgen um die chronologische Berechnung. Wenn ich years{14700} + 0s + days{0}in einer Codebasis sehe, hätte ich keine Ahnung, was 0sdort tut und wie wichtig es ist. Gibt es einen alternativen, vielleicht expliziteren Weg? Wäre etwas duration_cast<seconds>(years{14700}) + days{0}besser?
Bolov
duration_castwäre schlimmer, weil es eine schlechte Form ist, sie duration_castfür nicht abschneidende Konvertierungen zu verwenden. Das Abschneiden von Konvertierungen kann zu logischen Fehlern führen. Verwenden Sie den "großen Hammer" am besten nur, wenn Sie ihn benötigen, damit Sie die abgeschnittenen Konvertierungen in Ihrem Code leicht erkennen können.
Howard Hinnant
1
Man könnte eine benutzerdefinierte Dauer erstellen: use llyears = duration<long long, years::period>;und diese stattdessen verwenden. Aber wahrscheinlich ist es das Beste, darüber nachzudenken, was Sie erreichen wollen, und sich zu fragen, ob Sie es richtig machen. Benötigen Sie zum Beispiel wirklich Tagesgenauigkeit auf einer Zeitskala von 10 Tausend Jahren? Der Zivilkalender ist nur auf ungefähr einen Tag in viertausend Jahren genau. Vielleicht wäre ein Gleitkomma-Jahrtausend eine bessere Einheit?
Howard Hinnant
Klarstellung: Chronos Modellierung des Zivilkalenders liegt genau im Bereich von -32767/1/1 bis 32767/12/31. Die Genauigkeit des Zivilkalenders in Bezug auf die Modellierung des Sonnensystems beträgt nur etwa einen Tag in viertausend Jahren.
Howard Hinnant
1
Es würde wirklich vom Anwendungsfall abhängen und ich habe derzeit Probleme, an einen motivierenden Anwendungsfall zu denken, um yearsund hinzuzufügen days. Dies bedeutet buchstäblich ein Vielfaches von 365,2425 Tagen zu einer ganzzahligen Anzahl von Tagen. Wenn Sie eine chronologische Berechnung in der Größenordnung von Monaten oder Jahren durchführen möchten, müssen Sie normalerweise etwas Physik oder Biologie modellieren. Vielleicht ist dieser Beitrag auf den verschiedenen Möglichkeiten hinzuzufügen , monthsum system_clock::time_pointden Unterschied zwischen den beiden Arten von Berechnungen würde helfen , zu klären: stackoverflow.com/a/43018120/576911
Howard Hinnant