De cppreference
std::chrono::years (since C++20) duration</*signed integer type of at least 17 bits*/, std::ratio<31556952>>
L' utilisation libc++
, il semble que le stockage sous - jacent de std::chrono::years
est -ce short
qui est signé 16 bits .
std::chrono::years( 30797 ) // yields 32767/01/01
std::chrono::years( 30797 ) + 365d // yields -32768/01/01 apparently UB
Y a-t-il une faute de frappe sur cppreference ou autre chose?
Exemple:
#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);
}
[ Lien Godbolt ]
year
plage: eel.is/c++draft/time.cal.year#members-19years
plage: eel.is/c++draft/time.syn .year
est le "nom" de l'année civile et nécessite 16 bits.years
est une durée chrono, pas la même chose qu'unyear
. On peut soustraire deuxyear
et le résultat a du typeyears
.years
est nécessaire pour pouvoir conserver le résultat deyear::max() - year::min()
.std::chrono::years( 30797 ) + 365d
ne compile pas.years{30797} + days{365}
est de 204528013 avec des unités de 216s.hours{2} + seconds{5}
.duration
years
months
days
year
month
day
year{30797} + day{365}
year{2020}
years{2020}
Réponses:
L'article cppreference est correct . Si libc ++ utilise un type plus petit, cela semble être un bogue dans libc ++.
la source
word
qui est probablement à peine utilisé ne serait-il pasyear_month_day
inutile de gonfler les vecteurs? Celaat least 17 bits
ne pourrait- il pas être considéré comme un texte norminal?year_month_day
contientyear
, nonyears
. La représentation deyear
n'est pas nécessairement 16 bits, bien que le typeshort
soit utilisé comme exposition. OTOH, la partie 17 bits de layears
définition est normative car elle n'est pas marquée comme exposition uniquement. Et franchement, dire qu'il s'agit d'au moins 17 bits et ne pas l'exiger n'a aucun sens.year
enyear_month_day
semble être enint
effet. => operator int Je pense que cela prendat least 17 bits
years
en charge la mise en œuvre.Je décompose l'exemple sur https://godbolt.org/z/SNivyp morceau par morceau:
Simplifier et supposer
using namespace std::chrono
est dans la portée:La sous-expression
years{0}
est unduration
avec unperiod
égal àratio<31'556'952>
et une valeur égale à0
. Notez queyears{1}
, exprimé en virgule flottantedays
, est exactement 365,2425. Il s'agit de la durée moyenne de l'année civile.La sous-expression
days{365}
est unduration
avec unperiod
égal àratio<86'400>
et une valeur égale à365
.La sous-expression
years{0} + days{365}
est unduration
avec unperiod
égal àratio<216>
et une valeur égale à146'000
. Ceci est formé en trouvant d'abord lecommon_type_t
deratio<31'556'952>
etratio<86'400>
qui est le GCD (31'556'952, 86'400), ou 216. La bibliothèque convertit d'abord les deux opérandes en cette unité commune, puis fait l'addition dans l'unité commune.Pour convertir
years{0}
en unités avec une période de 216s, il faut multiplier 0 par 146'097. Cela se trouve être un point très important. Cette conversion peut facilement provoquer un débordement lorsqu'elle n'est effectuée qu'avec 32 bits.<côté>
Si, à ce stade, vous vous sentez confus, c'est parce que le code a probablement l'intention d'un calcul calendaire , mais fait en fait un calcul chronologique . Les calculs calendaires sont des calculs avec des calendriers.
Les calendriers présentent toutes sortes d'irrégularités, telles que des mois et des années ayant des longueurs physiques différentes en termes de jours. Un calcul calendaire prend en compte ces irrégularités.
Un calcul chronologique fonctionne avec des unités fixes et ne fait que lancer les chiffres sans tenir compte des calendriers. Un calcul chronologique ne se soucie pas si vous utilisez le calendrier grégorien, le calendrier julien, le calendrier hindou, le calendrier chinois, etc.
</aside>
Ensuite, nous prenons notre
146000[216]s
durée et la convertissons en une durée avec unperiod
deratio<86'400>
(qui a un alias de type nommédays
). La fonctionfloor<days>()
effectue cette conversion et le résultat est365[86400]s
, ou plus simplement, juste365d
.L'étape suivante prend le
duration
et le convertit en atime_point
. Le type dutime_point
esttime_point<system_clock, days>
qui a un alias de type nommésys_days
. Il s'agit simplement d'un décomptedays
depuis l'system_clock
époque, qui est 1970-01-01 00:00:00 UTC, à l'exclusion des secondes intercalaires.Enfin, le
sys_days
est converti en unyear_month_day
avec la valeur1971-01-01
.Un moyen plus simple de faire ce calcul est:
Considérez ce calcul similaire:
Il en résulte la date
16668-12-31
. Ce qui est probablement un jour plus tôt que prévu ((14699 + 1970) -01-01). Le sous - expressionyears{14699} + days{0}
est maintenant:2'147'479'803[216]s
. Notez que la valeur d'exécution est proche deINT_MAX
(2'147'483'647
) et que le sous-jacentrep
des deuxyears
etdays
estint
.En effet , si vous convertissez
years{14700}
des unités de[216]s
vous overflow:-2'147'341'396[216]s
.Pour résoudre ce problème, passez à un calcul calendaire:
Tous les résultats sur https://godbolt.org/z/SNivyp qui ajoutent
years
etdays
utilisent une valeuryears
supérieure à 14699 connaissent unint
débordement.Si l'on veut vraiment faire des calculs chronologiques avec
years
et dedays
cette façon, il serait sage d'utiliser l'arithmétique 64 bits. Cela peut être accompli en convertissantyears
en unités avec unerep
utilisation supérieure à 32 bits au début du calcul. Par exemple:En ajoutant
0s
àyears
, (seconds
doit avoir au moins 35 bits), puiscommon_type
rep
est forcé à 64 bits pour le premier ajout (years{14700} + 0s
) et continue en 64 bits lors de l'ajoutdays{0}
:Encore une autre façon d'éviter un débordement intermédiaire (dans cette plage) consiste à tronquer
years
avecdays
précision avant d'en ajouter plusdays
:j
a la valeur16669-12-31
. Cela évite le problème car maintenant l'[216]s
unité n'est jamais créée en premier lieu. Et nous ne nous approchons même pas de la limite pouryears
,days
ouyear
.Bien que si vous vous y attendiez
16700-01-01
, vous avez toujours un problème, et la façon de le corriger est de faire un calcul calendaire à la place:la source
years{14700} + 0s + days{0}
dans une base de code, je n'aurais aucune idée de ce0s
qui s'y fait et de son importance. Existe-t-il un autre moyen, peut-être plus explicite? Souhaitez-vous que quelque chose deduration_cast<seconds>(years{14700}) + days{0}
mieux?duration_cast
serait pire car il est de mauvaise forme à utiliserduration_cast
pour les conversions non tronquées. La troncature des conversions peut être une source d'erreurs logiques, et il est préférable de n'utiliser le "gros marteau" que lorsque vous en avez besoin, afin de pouvoir facilement repérer les conversions tronquées dans votre code.use llyears = duration<long long, years::period>;
puis l'utiliser à la place. Mais la meilleure chose à faire est probablement de réfléchir à ce que vous essayez d'accomplir et de vous demander si vous vous y prenez correctement. Par exemple, avez-vous vraiment besoin d'une précision journalière sur une échelle de temps de 10 000 ans? Le calendrier civil n'est exact qu'à environ 1 jour en 4 000 ans. Peut-être qu'un millénaire en virgule flottante serait une meilleure unité?years
etdays
. Cela ajoute littéralement un multiple de 365,2425 jours à un certain nombre entier de jours. Normalement si vous voulez faire un calcul chronologique de l'ordre des mois ou des années, c'est pour modéliser de la physique ou de la biologie. Peut-être que ce post sur les différentes façons d'ajoutermonths
àsystem_clock::time_point
contribuerait à clarifier la différence entre les deux types de calculs: stackoverflow.com/a/43018120/576911