De cppreference
std::chrono::years (since C++20) duration</*signed integer type of at least 17 bits*/, std::ratio<31556952>>
Usando libc++, parece que el almacenamiento subrayado de std::chrono::yearsis shortestá firmado 16 bits .
std::chrono::years( 30797 ) // yields 32767/01/01
std::chrono::years( 30797 ) + 365d // yields -32768/01/01 apparently UB
¿Hay un error tipográfico en cppreference o algo más?
Ejemplo:
#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);
}

yearrango: eel.is/c++draft/time.cal.year#members-19yearsrango: eel.is/c++draft/time.syn .yeares el "nombre" del año civil y requiere 16 bits.yearses una duración crono, no es lo mismo que ayear. Uno puede restar dosyeary el resultado tiene tipoyears.yearsse requiere para poder mantener el resultado deyear::max() - year::min().std::chrono::years( 30797 ) + 365dno compilayears{30797} + days{365}es 204528013 con unidades de 216s.hours{2} + seconds{5}.durationlos nombres son plurales:years,months,days. Nombres de los componentes del calendario son singulares:year,month,day.year{30797} + day{365}es un error en tiempo de compilación.year{2020}es este añoyears{2020}tiene una duración de 2020 años.Respuestas:
El artículo de cppreference es correcto . Si libc ++ usa un tipo más pequeño, entonces esto parece ser un error en libc ++.
fuente
wordque probablemente apenas se use no sería unyear_month_dayvector de volumen innecesario? ¿Podría esoat least 17 bitsno contarse como texto normal?year_month_daycontieneyear, noyears. La representación deyearno se requiere que sea de 16 bits, aunque el tiposhortse utiliza como exposición. OTOH, la parte de 17 bits en layearsdefinición es normativa, ya que no está marcada solo como exposición. Y francamente, decir que tiene al menos 17 bits y luego no requerirlo no tiene sentido.yearenyear_month_dayparece ser deinthecho. => operator int Creo que esto es compatible con laat least 17 bitsyearsimplementación.Estoy desglosando el ejemplo en https://godbolt.org/z/SNivyp pieza por pieza:
Simplificar y asumir
using namespace std::chronoestá dentro del alcance:La subexpresión
years{0}es undurationcon un valorperiodigual aratio<31'556'952>y un valor igual a0. Tenga en cuenta queyears{1}, expresado como punto flotantedays, es exactamente 365.2425. Esta es la duración promedio del año civil.La subexpresión
days{365}es undurationcon un valorperiodigual aratio<86'400>y un valor igual a365.La subexpresión
years{0} + days{365}es undurationcon un valorperiodigual aratio<216>y un valor igual a146'000. Esto se forma al encontrar primero elcommon_type_tderatio<31'556'952>yratio<86'400>cuál es el MCD (31'556'952, 86'400), o 216. La biblioteca primero convierte ambos operandos a esta unidad común, y luego hace la suma en la unidad común.Para convertir
years{0}a unidades con un período de 216s, se debe multiplicar 0 por 146'097. Esto pasa a ser un punto muy importante. Esta conversión puede causar desbordamiento fácilmente cuando se realiza con solo 32 bits.<aside>
Si en este punto te sientes confundido, es porque el código probablemente intenta un cálculo calendárico , pero en realidad está haciendo un cálculo cronológico . Los cálculos calendáricos son cálculos con calendarios.
Los calendarios tienen todo tipo de irregularidades, como meses y años de diferente duración física en términos de días. Un cálculo calendárico tiene en cuenta estas irregularidades.
Un cálculo cronológico funciona con unidades fijas, y solo produce los números sin tener en cuenta los calendarios. A un cálculo cronológico no le importa si usa el calendario gregoriano, el calendario juliano, el calendario hindú, el calendario chino, etc.
</aside>
Luego tomamos nuestra
146000[216]sduración y la convertimos a una duración con unperiodofratio<86'400>(que tiene un alias de tipo llamadodays). La funciónfloor<days>()realiza esta conversión y el resultado es365[86400]s, o más simplemente, justo365d.El siguiente paso toma el
durationy lo convierte en atime_point. El tipo detime_pointes eltime_point<system_clock, days>que tiene un alias de tipo llamadosys_days. Esto es simplemente un recuentodaysdesde lasystem_clocképoca, que es 1970-01-01 00:00:00 UTC, excluyendo los segundos bisiestos.Finalmente
sys_daysse convierte a ayear_month_daycon el valor1971-01-01.Una forma más simple de hacer este cálculo es:
Considere este cálculo similar:
Esto da como resultado la fecha
16668-12-31. Probablemente un día antes de lo que esperaba ((14699 + 1970) -01-01). La subexpresiónyears{14699} + days{0}es ahora:2'147'479'803[216]s. Tenga en cuenta que el valor de tiempo de ejecución se está acercandoINT_MAX(2'147'483'647) y que el subyacenterepde ambosyearsydaysesint.De hecho si convierte
years{14700}a unidades de[216]sllegar desbordamiento:-2'147'341'396[216]s.Para solucionar esto, cambie a un cálculo calendárico:
Todos los resultados en https://godbolt.org/z/SNivyp que están agregando
yearsydaysusando un valoryearsmayor que 14699 están experimentando unintdesbordamiento.Si uno realmente quiere hacer cálculos cronológicos con
yearsy dedaysesta manera, entonces sería aconsejable usar aritmética de 64 bits. Esto se puede lograr mediante la conversiónyearsa unidades con unrepuso superior a 32 bits al principio del cálculo. Por ejemplo:Al agregar
0sayears, (secondsdebe tener al menos 35 bits), luegocommon_typerepse fuerza a 64 bits para la primera adición (years{14700} + 0s) y continúa en 64 bits al agregardays{0}:Otra forma de evitar el desbordamiento intermedio (en este rango) es truncar
yearscondaysprecisión antes de agregar másdays:jtiene el valor16669-12-31. Esto evita el problema porque ahora la[216]sunidad nunca se crea en primer lugar. Y nunca nos acercamos al límite deyears,daysoyear.Aunque si esperaba
16700-01-01, todavía tiene un problema, y la forma de corregirlo es hacer un cálculo calendárico en su lugar:fuente
years{14700} + 0s + days{0}en una base de código, no tendría idea de lo que0sestá haciendo allí y lo importante que es. ¿Existe una forma alternativa, tal vez más explícita? ¿Te gustaría algoduration_cast<seconds>(years{14700}) + days{0}mejor?duration_castsería peor porque es una mala forma de usarduration_castpara conversiones no truncadas. Truncar las conversiones puede ser una fuente de errores lógicos, y es mejor usar solo el "gran martillo" cuando lo necesite, para que pueda detectar fácilmente las conversiones truncadas en su código.use llyears = duration<long long, years::period>;y luego usarla en su lugar. Pero probablemente lo mejor es pensar en lo que está tratando de lograr y preguntarse si lo está haciendo de la manera correcta. Por ejemplo, ¿realmente necesita precisión de día en una escala de tiempo de 10 mil años? El calendario civil solo tiene una precisión de aproximadamente 1 día en 4 mil años. ¿Quizás un milenio en coma flotante sería una mejor unidad?yearsydays. Esto literalmente está agregando un múltiplo de 365.2425 días a un número integral de días. Normalmente, si desea hacer un cálculo cronológico en el orden de meses o años, es modelar algo de física o biología. Tal vez este post sobre las diferentes maneras de añadirmonthsasystem_clock::time_pointayudaría a aclarar la diferencia entre los dos tipos de cálculos: stackoverflow.com/a/43018120/576911