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::years
is short
está 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);
}
year
rango: eel.is/c++draft/time.cal.year#members-19years
rango: eel.is/c++draft/time.syn .year
es el "nombre" del año civil y requiere 16 bits.years
es una duración crono, no es lo mismo que ayear
. Uno puede restar dosyear
y el resultado tiene tipoyears
.years
se requiere para poder mantener el resultado deyear::max() - year::min()
.std::chrono::years( 30797 ) + 365d
no compilayears{30797} + days{365}
es 204528013 con unidades de 216s.hours{2} + seconds{5}
.duration
los 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
word
que probablemente apenas se use no sería unyear_month_day
vector de volumen innecesario? ¿Podría esoat least 17 bits
no contarse como texto normal?year_month_day
contieneyear
, noyears
. La representación deyear
no se requiere que sea de 16 bits, aunque el tiposhort
se utiliza como exposición. OTOH, la parte de 17 bits en layears
definició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.year
enyear_month_day
parece ser deint
hecho. => operator int Creo que esto es compatible con laat least 17 bits
years
implementación.Estoy desglosando el ejemplo en https://godbolt.org/z/SNivyp pieza por pieza:
Simplificar y asumir
using namespace std::chrono
está dentro del alcance:La subexpresión
years{0}
es unduration
con un valorperiod
igual 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 unduration
con un valorperiod
igual aratio<86'400>
y un valor igual a365
.La subexpresión
years{0} + days{365}
es unduration
con un valorperiod
igual aratio<216>
y un valor igual a146'000
. Esto se forma al encontrar primero elcommon_type_t
deratio<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]s
duración y la convertimos a una duración con unperiod
ofratio<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
duration
y lo convierte en atime_point
. El tipo detime_point
es eltime_point<system_clock, days>
que tiene un alias de tipo llamadosys_days
. Esto es simplemente un recuentodays
desde lasystem_clock
época, que es 1970-01-01 00:00:00 UTC, excluyendo los segundos bisiestos.Finalmente
sys_days
se convierte a ayear_month_day
con 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 subyacenterep
de ambosyears
ydays
esint
.De hecho si convierte
years{14700}
a unidades de[216]s
llegar 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
years
ydays
usando un valoryears
mayor que 14699 están experimentando unint
desbordamiento.Si uno realmente quiere hacer cálculos cronológicos con
years
y dedays
esta manera, entonces sería aconsejable usar aritmética de 64 bits. Esto se puede lograr mediante la conversiónyears
a unidades con unrep
uso superior a 32 bits al principio del cálculo. Por ejemplo:Al agregar
0s
ayears
, (seconds
debe tener al menos 35 bits), luegocommon_type
rep
se 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
years
condays
precisión antes de agregar másdays
:j
tiene el valor16669-12-31
. Esto evita el problema porque ahora la[216]s
unidad nunca se crea en primer lugar. Y nunca nos acercamos al límite deyears
,days
oyear
.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 que0s
está 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_cast
sería peor porque es una mala forma de usarduration_cast
para 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?years
ydays
. 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ñadirmonths
asystem_clock::time_point
ayudaría a aclarar la diferencia entre los dos tipos de cálculos: stackoverflow.com/a/43018120/576911