Std :: chrono :: years storage è davvero almeno 17 bit?


14

Da cppreference

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

Usando libc++, sembra che la memoria di sottolineatura di std::chrono::yearsis shortsia firmata a 16 bit .

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

C'è un refuso su cppreference o qualcos'altro?

Esempio:

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

[ Link Godbolt ]


2
yearintervallo: eel.is/c++draft/time.cal.year#members-19 years intervallo: eel.is/c++draft/time.syn . yearè il "nome" dell'anno civile e richiede 16 bit. yearsè una durata crono, non la stessa cosa di a year. Si possono sottrarre due yeare il risultato ha tipo years. yearsè necessario per poter contenere il risultato di year::max() - year::min().
Howard Hinnant,

1
std::chrono::years( 30797 ) + 365dnon compilare.
Howard Hinnant,

1
Il risultato years{30797} + days{365}è 204528013 con unità di 216 secondi.
Howard Hinnant,

1
Sono state aggiunte solo due durate. Proibirlo significherebbe proibire hours{2} + seconds{5}.
Howard Hinnant,

4
La mia ipotesi è che si sta confondendo i componenti calendariali con i tipi di durata, perché non hanno tali nomi simili. Ecco una regola generale: durationi nomi sono plurali: years, months, days. I nomi dei componenti calendariali sono singolari: year, month, day. year{30797} + day{365}è un errore in fase di compilazione. year{2020}è quest'anno. years{2020}è una durata di 2020 anni.
Howard Hinnant,

Risposte:


8

L'articolo di cppreference è corretto . Se libc ++ usa un tipo più piccolo, questo sembra essere un bug in libc ++.


Ma aggiungerne un altro wordche a malapena usato non sarebbe inutilmente ingrossare i year_month_dayvettori? Non potrebbe at least 17 bitsessere considerato un testo normale?
sandthorn

3
@sandthorn year_month_daycontiene year, non years. Non yearè necessario che la rappresentazione di sia a 16 bit, sebbene il tipo shortsia usato come esposizione. OTOH, la parte a 17 bit nella yearsdefinizione è normativa in quanto non è contrassegnata solo come esposizione. E francamente, dire che ha almeno 17 bit e quindi non richiederlo non ha senso.
Andrey Semashev,

1
Ah yearin year_month_daysembra essere intdavvero. => operatore int Penso che questo supporti l' at least 17 bits yearsimplementazione.
sandthorn

Ti dispiacerebbe modificare la tua risposta? Si scopre che std :: chrono :: anni è in realtà int e std :: chrono :: l'anno è massimo a 32767 arbitrariamente ..
sandthorn

@sandthorn La risposta è corretta, non vedo perché dovrei modificarla.
Andrey Semashev,

4

Sto analizzando l'esempio di https://godbolt.org/z/SNivyp pezzo per pezzo:

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

Semplificare e assumere using namespace std::chrono è nell'ambito:

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

La sottoespressione years{0}è a durationcon un perioduguale a ratio<31'556'952>e un valore uguale a 0. Si noti che years{1}, espresso in virgola mobile days, è esattamente 365.2425. Questa è la durata media dell'anno civile.

La sottoespressione days{365}è a durationcon un perioduguale a ratio<86'400>e un valore uguale a 365.

La sottoespressione years{0} + days{365}è a durationcon un perioduguale a ratio<216>e un valore uguale a 146'000. Questo si forma trovando dapprima il common_type_tdi ratio<31'556'952>e ratio<86'400>che è il GCD (31'556'952, 86'400) o 216. La libreria prima converte entrambi gli operandi in questa unità comune, quindi esegue l'aggiunta nell'unità comune.

Per convertire years{0}in unità con un periodo di 216 secondi è necessario moltiplicare 0 per 146'097. Questo sembra essere un punto molto importante. Questa conversione può facilmente causare overflow quando eseguita con solo 32 bit.

<Aside>

Se a questo punto ti senti confuso, è perché il codice probabilmente intende un calcolo calendario , ma in realtà sta eseguendo un calcolo cronologico . I calcoli calendari sono calcoli con calendari.

I calendari presentano ogni sorta di irregolarità, ad esempio mesi e anni di diversa lunghezza fisica in termini di giorni. Un calcolo calendario tiene conto di queste irregolarità.

Un calcolo cronologico funziona con unità fisse e consente di estrarre i numeri senza tener conto dei calendari. A un calcolo cronologico non importa se usi il calendario gregoriano, il calendario giuliano, il calendario indù, il calendario cinese, ecc.

</ Aside>

Quindi prendiamo la nostra 146000[216]sdurata e la convertiamo in una durata con un perioddi ratio<86'400>(che ha un alias di tipo denominato days). La funzione floor<days>()esegue questa conversione e il risultato è 365[86400]s, o più semplicemente, giusto365d .

Il prossimo passo prende il duratione lo converte in a time_point. Il tipo di time_pointè time_point<system_clock, days>che ha un alias di tipo denominato sys_days. Questo è semplicemente un conteggio daysdal momento che ilsystem_clock l'epoca, che è 1970-01-01 00:00:00 UTC, escluso secondo salto.

Alla fine il sys_daysviene convertito in a year_month_daycon il valore1971-01-01 .

Un modo più semplice per eseguire questo calcolo è:

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

Considera questo calcolo simile:

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

Questo risulta nella data 16668-12-31. Che è probabilmente un giorno prima di quanto ti aspettavi ((14699 + 1970) -01-01). La sottoespressione years{14699} + days{0}è ora: 2'147'479'803[216]s. Si noti che il valore di runtime si sta avvicinandoINT_MAX ( 2'147'483'647) e che il sottostante repdi entrambi yearse lo daysèint .

Infatti, se ti converti years{14700}in unità di [216]ste, ottieni un overflow: -2'147'341'396[216]s .

Per risolvere questo problema, passa a un calcolo calendario:

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

Tutti i risultati su https://godbolt.org/z/SNivyp che stanno aggiungendoyears ed dayse utilizzando un valore yearsche è maggiore di 14699 stanno sperimentandoint overflow.

Se si vuole davvero fare calcoli cronologici con yearse in daysquesto modo, sarebbe saggio usare l'aritmetica a 64 bit. Ciò può essere ottenuto convertendo yearsin unità con arep utilizzo maggiore di 32 bit all'inizio del calcolo. Per esempio:

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

Aggiungendo 0sa years, ( secondsdeve avere almeno 35 bit), common_type repviene forzato a 64 bit per la prima aggiunta ( years{14700} + 0s) e continua con 64 bit quando si aggiungedays{0} :

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

Un altro modo per evitare un overflow intermedio (a questo intervallo) è troncare yearscon daysprecisione prima di aggiungere altro days:

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

jha il valore 16669-12-31. Questo evita il problema perché ora l' [216]sunità non viene mai creata in primo luogo. E non ci avviciniamo mai nemmeno al limite per years, dayso year.

Tuttavia, se ti aspettavi 16700-01-01, allora hai ancora un problema e il modo per correggerlo è invece fare un calcolo calendario:

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

1
Ottima spiegazione Sono preoccupato per il calcolo cronologico. Se vedessi years{14700} + 0s + days{0}in una base di codice, non avrei idea di cosa 0sci faccia lì e di quanto sia importante. Esiste un modo alternativo, forse più esplicito? Sarebbe qualcosa di duration_cast<seconds>(years{14700}) + days{0}meglio?
Bolov,

duration_castsarebbe peggio perché è una cattiva forma da utilizzare duration_castper le conversioni non troncanti. Troncare le conversioni può essere una fonte di errori logici ed è meglio usare il "martello grande" solo quando ne hai bisogno, in modo da poter individuare facilmente le conversioni troncanti nel tuo codice.
Howard Hinnant,

1
Si potrebbe creare una durata personalizzata: use llyears = duration<long long, years::period>;e quindi usarla invece. Ma probabilmente la cosa migliore è pensare a ciò che stai cercando di realizzare e chiederti se lo stai facendo nel modo giusto. Ad esempio, hai davvero bisogno della precisione del giorno su una scala temporale di 10 mila anni? Il calendario civile è preciso solo per circa 1 giorno in 4 mila anni. Forse un millennio a virgola mobile sarebbe un'unità migliore?
Howard Hinnant,

Chiarimento: la modellazione del crono del calendario civile è esatta nell'intervallo da -32767/1/1 a 32767/12/31. L'accuratezza del calendario civile rispetto alla modellizzazione del sistema solare è di circa 1 giorno in 4 mila anni.
Howard Hinnant,

1
Dipenderebbe davvero dal caso d'uso e al momento ho problemi a pensare a un caso d'uso motivante da aggiungere yearse days. Questo sta letteralmente aggiungendo alcuni multipli di 365.2425 giorni ad un numero integrale di giorni. Normalmente se si desidera eseguire un calcolo cronologico nell'ordine di mesi o anni, si tratta di modellare un po 'di fisica o biologia. Forse questo post sui diversi modi per aggiungere monthsal system_clock::time_pointaiuterebbe a chiarire la differenza tra i due tipi di calcoli: stackoverflow.com/a/43018120/576911
Howard Hinnant
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.