std::chrono::years (since C++20) duration</*signed integer type of at least 17 bits*/, std::ratio<31556952>>
Używając libc++
, wydaje się, że podkreśleniem std::chrono::years
jest short
to, że podpisano 16 bitów .
std::chrono::years( 30797 ) // yields 32767/01/01
std::chrono::years( 30797 ) + 365d // yields -32768/01/01 apparently UB
Czy istnieje literówka na preferencjach lub coś innego?
Przykład:
#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 ]
year
zakres: eel.is/c++draft/time.cal.year#members-19years
zakres: eel.is/c++draft/time.syn .year
to „nazwa” roku cywilnego i wymaga 16 bitów.years
jest czasem trwania chrono, a nie tym samym coyear
. Można odjąć dwa,year
a wynik ma typyears
.years
musi być w stanie utrzymać wynikyear::max() - year::min()
.std::chrono::years( 30797 ) + 365d
nie kompiluje się.years{30797} + days{365}
jest 204528013 z jednostkami 216s.hours{2} + seconds{5}
.duration
imiona są w liczbie mnogiej:years
,months
,days
. Calendrical nazwy elementów są w liczbie pojedynczej:year
,month
,day
.year{30797} + day{365}
jest błędem czasu kompilacji.year{2020}
jest w tym roku.years{2020}
trwa 2020 lat.Odpowiedzi:
Artykuł o preferencjach jest poprawny . Jeśli libc ++ używa mniejszego typu, wydaje się to być błędem w libc ++.
źródło
word
który prawdopodobnie rzadko używany nie byłbyyear_month_day
niepotrzebnym łączeniem wektorów? Czyat least 17 bits
nie można tego uznać za tekst normalny?year_month_day
zawierayear
, nieyears
. Reprezentacjayear
nie musi być 16-bitowa, chociaż typshort
jest używany jako ekspozycja. OTOH, 17-bitowa częśćyears
definicji jest normatywna, ponieważ nie jest oznaczona tylko jako ekspozycja. I szczerze mówiąc, twierdzenie, że ma co najmniej 17 bitów, a następnie nie wymaganie, jest bez znaczenia.year
wyear_month_day
wydaje się byćint
rzeczywiście. => operator int Myślę, że obsługuje toat least 17 bits
years
implementację.Rozbijam przykład na https://godbolt.org/z/SNivyp kawałek po kawałku:
Uproszczenie i założenie
using namespace std::chrono
obejmuje:Sub ekspresja
years{0}
jestduration
zperiod
równaratio<31'556'952>
i wartość równą0
. Zauważ, żeyears{1}
wyrażony jako zmiennoprzecinkowydays
, wynosi dokładnie 365.2425. Jest to średnia długość roku cywilnego.Sub ekspresja
days{365}
jestduration
zperiod
równaratio<86'400>
i wartość równą365
.Sub ekspresja
years{0} + days{365}
jestduration
zperiod
równaratio<216>
i wartość równą146'000
. To jest utworzone przez pierwszy ustaleniemcommon_type_t
zratio<31'556'952>
iratio<86'400>
który jest GCD (31'556'952, 86'400) lub 216. Biblioteka najpierw konwertuje z argumentów do tej wspólnej jednostki, a następnie wykonuje się dodatkowe do wspólnego urządzenia.Aby przeliczyć
years{0}
na jednostki z okresem 216s, należy pomnożyć 0 przez 146'097. Jest to bardzo ważny punkt. Ta konwersja może łatwo spowodować przepełnienie, gdy zostanie wykonana tylko z 32 bitami.<aside>
Jeśli w tym momencie czujesz się zdezorientowany, dzieje się tak dlatego, że kod prawdopodobnie planuje obliczenia kalendarza , ale w rzeczywistości wykonuje obliczenia chronologiczne . Obliczenia kalendarzowe to obliczenia z kalendarzami.
Kalendarze mają różnego rodzaju nieregularności, takie jak miesiące i lata o różnej długości fizycznej pod względem dni. Obliczenia kalendarzowe uwzględniają te nieprawidłowości.
Obliczenia chronologiczne działają z ustalonymi jednostkami i po prostu korygują liczby bez względu na kalendarze. Obliczenia chronologiczne nie dbają o to, czy używasz kalendarza gregoriańskiego, kalendarza juliańskiego, kalendarza hinduskiego, kalendarza chińskiego itp.
</aside>
Następny bierzemy nasz
146000[216]s
czas i przekształcić ją w czasie trwania zperiod
odratio<86'400>
(który ma typu alias o nazwiedays
). Funkcjafloor<days>()
wykonuje tę konwersję, a wynik jest365[86400]s
, lub prościej, sprawiedliwy365d
.Następnym krokiem jest
duration
i konwertuje go natime_point
. Typtime_point
jesttime_point<system_clock, days>
który ma typu alias o nazwiesys_days
. Jest to po prostu odliczaniedays
odsystem_clock
epoki 1970-01-01 00:00:00 UTC, z wyłączeniem sekund przestępnych.Na koniec
sys_days
konwertowane jestyear_month_day
na wartość o wartości1971-01-01
.Prostszym sposobem wykonania tego obliczenia jest:
Rozważ to podobne obliczenie:
To powoduje datę
16668-12-31
. Który jest prawdopodobnie dzień wcześniej, niż się spodziewałeś ((14699 + 1970) -01-01). Podwyrażenieyears{14699} + days{0}
jest teraz:2'147'479'803[216]s
. Zauważ, że wartość czasu wykonania jest bliskaINT_MAX
(2'147'483'647
), i że podstawąrep
zarównoyears
idays
jestint
.Rzeczywiście, jeśli przekonwertować
years{14700}
do jednostek[216]s
dostać overflow:-2'147'341'396[216]s
.Aby to naprawić, przejdź do obliczeń kalendarza:
Wszystkie wyniki na https://godbolt.org/z/SNivyp , które dodają
years
idays
używają do tego wartościyears
większej niż 14699, ulegająint
przepełnieniu.Jeśli ktoś naprawdę chce zrobić z obliczeń chronologicznych
years
idays
ten sposób, to byłoby mądre, aby użyć 64 bitowej arytmetyki. Można to osiągnąć przez konwersjęyears
na jednostki przyrep
użyciu więcej niż 32 bitów na początku obliczeń. Na przykład:Dodając
0s
doyears
, (seconds
musi mieć co najmniej 35 bitów), następniecommon_type
rep
jest zmuszany do 64 bitów dla pierwszego dodawania (years{14700} + 0s
) i kontynuuje w 64 bitach podczas dodawaniadays{0}
:Jeszcze innym sposobem uniknięcia pośredniego przepełnienia (w tym zakresie) jest obcięcie
years
dodays
precyzji przed dodaniem kolejnychdays
:j
ma wartość16669-12-31
. Pozwala to uniknąć problemu, ponieważ teraz[216]s
jednostka nigdy nie jest tworzona. I nigdy nawet zbliżyć się do granicyyears
,days
lubyear
.Chociaż jeśli się spodziewałeś
16700-01-01
, nadal masz problem, a sposobem na jego rozwiązanie jest wykonanie obliczeń kalendarza:źródło
years{14700} + 0s + days{0}
w bazie kodu, nie miałbym pojęcia, co0s
tam robi i jak to jest ważne. Czy istnieje alternatywny, może bardziej wyraźny sposób? Czy coś takiegoduration_cast<seconds>(years{14700}) + days{0}
byłoby lepsze?duration_cast
byłoby gorzej, ponieważ jest to zła forma do zastosowaniaduration_cast
w przypadku konwersji nieobcinających. Obcinanie konwersji może być źródłem błędów logicznych i najlepiej używać „dużego młota” tylko wtedy, gdy jest to potrzebne, aby można było łatwo wykryć obcięte konwersje w kodzie.use llyears = duration<long long, years::period>;
a następnie użyć go zamiast tego. Ale prawdopodobnie najlepszą rzeczą jest zastanowienie się nad tym, co próbujesz osiągnąć, i zastanowienie się, czy idziesz do tego we właściwy sposób. Na przykład, czy naprawdę potrzebujesz precyzji dnia w skali czasowej wynoszącej 10 tysięcy lat? Kalendarz cywilny jest dokładny z dokładnością do około 1 dnia na 4 tysiące lat. Może tysiąclecie zmiennoprzecinkowe byłoby lepszą jednostką?years
idays
. To dosłownie dodaje pewną wielokrotność 365.2425 dni do pewnej integralnej liczby dni. Zwykle, jeśli chcesz wykonać obliczenia chronologiczne na podstawie miesięcy lub lat, jest to modelowanie fizyki lub biologii. Być może ten post na temat różnych sposobów dodawaniamonths
dosystem_clock::time_point
pomógłby wyjaśnić różnicę między dwoma rodzajami obliczeń: stackoverflow.com/a/43018120/576911