Da cppreference
std::chrono::years (since C++20) duration</*signed integer type of at least 17 bits*/, std::ratio<31556952>>
Usando libc++
, parece que o armazenamento de sublinhado de std::chrono::years
é short
que é assinado 16 bits .
std::chrono::years( 30797 ) // yields 32767/01/01
std::chrono::years( 30797 ) + 365d // yields -32768/01/01 apparently UB
Existe um erro de digitação na cppreference ou qualquer outra coisa?
Exemplo:
#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 ]
year
intervalo: eel.is/c++draft/time.cal.year#members-19years
range: eel.is/c++draft/time.syn .year
é o "nome" do ano civil e requer 16 bits.years
é uma duração de crono, não é a mesma coisa que ayear
. Pode-se subtrair doisyear
e o resultado tem tipoyears
.years
é necessário para poder manter o resultado deyear::max() - year::min()
.std::chrono::years( 30797 ) + 365d
não compila.years{30797} + days{365}
é 204528013 com unidades de 216s.hours{2} + seconds{5}
.duration
os nomes são plurais:years
,months
,days
. Os nomes dos componentes do calendário são singulares:year
,month
,day
.year{30797} + day{365}
é um erro em tempo de compilação.year{2020}
é este ano.years{2020}
é uma duração de 2020 anos.Respostas:
O artigo cppreference está correto . Se a libc ++ usa um tipo menor, isso parece ser um bug na libc ++.
fonte
word
que provavelmente mal fosse usado não seria um volume desnecessário deyear_month_day
vetores? Issoat least 17 bits
não pode ser contado como texto norminal?year_month_day
contémyear
, nãoyears
. A representação deyear
não precisa ser de 16 bits, embora o tiposhort
seja usado como exposição. OTOH, a parte de 17 bits nayears
definição é normativa, pois não é marcada apenas como exposição. E, francamente, dizer que ele tem pelo menos 17 bits e depois não exigir isso não faz sentido.year
nosyear_month_day
parece serint
, de fato. => operator int Acho que isso suporta aat least 17 bits
years
implementação.Estou detalhando o exemplo em https://godbolt.org/z/SNivyp, peça por peça:
Simplificar e assumir
using namespace std::chrono
está no escopo:A sub-expressão
years{0}
é aduration
com umperiod
igual aratio<31'556'952>
e um valor igual a0
. Observe queyears{1}
, expresso como ponto flutuantedays
, é exatamente 365,2425. Essa é a média duração do ano civil.A sub-expressão
days{365}
é aduration
com umperiod
igual aratio<86'400>
e um valor igual a365
.A sub-expressão
years{0} + days{365}
é aduration
com umperiod
igual aratio<216>
e um valor igual a146'000
. Isso é formado pela primeira descobertacommon_type_t
deratio<31'556'952>
eratio<86'400>
que é o GCD (31'556'952, 86'400), ou 216. A biblioteca primeiros convertidos ambos os operandos para esta unidade comum, e em seguida faz a adição na unidade comum.Converter
years{0}
em unidades com um período de 216s, é necessário multiplicar 0 por 146'97. Este é um ponto muito importante. Essa conversão pode facilmente causar estouro quando feita com apenas 32 bits.<lado>
Se nesse momento você se sentir confuso, é porque o código provavelmente pretende uma computação de calendário , mas na verdade está fazendo um computação cronológica . Cálculos de calendários são cálculos com calendários.
Os calendários têm todos os tipos de irregularidades, como meses e anos com diferentes comprimentos físicos em termos de dias. Uma computação de calendário leva em consideração essas irregularidades.
Um cálculo cronológico funciona com unidades fixas e apenas aumenta os números sem levar em consideração os calendários. Um cálculo cronológico não se importa se você usa o calendário gregoriano, o calendário juliano, o calendário hindu, o calendário chinês etc.
</aside>
Em seguida, pegamos nossa
146000[216]s
duração e a convertemos em uma duração com umperiod
deratio<86'400>
(que tem um alias de tipo chamadodays
). A funçãofloor<days>()
faz essa conversão e o resultado é365[86400]s
, ou mais simplesmente, apenas365d
.O próximo passo pega
duration
oe converte em umtime_point
. O tipo detime_point
é otime_point<system_clock, days>
que possui um alias de tipo chamadosys_days
. Isso é simplesmente uma contagemdays
desde asystem_clock
época, que é 01-01-2009 00:00:00 UTC, excluindo segundos bissextos.Finalmente, o
sys_days
é convertido para ayear_month_day
com o valor1971-01-01
.Uma maneira mais simples de fazer esse cálculo é:
Considere este cálculo semelhante:
Isso resulta na data
16668-12-31
. O que provavelmente é um dia antes do que você esperava ((14699 + 1970) -01-01). A sub-expressãoyears{14699} + days{0}
é agora:2'147'479'803[216]s
. Observe que o valor do tempo de execução está próximoINT_MAX
(2'147'483'647
) e que o subjacenterep
de ambosyears
edays
éint
.Na verdade se você converter
years{14700}
para unidades de[216]s
você começa overflow:-2'147'341'396[216]s
.Para corrigir isso, mude para um cálculo de calendário:
Todos os resultados em https://godbolt.org/z/SNivyp que estão adicionando
years
edays
e usando um valor para oyears
que é maior do que 14.699 estão experimentandoint
estouro.Se alguém realmente deseja fazer cálculos cronológicos
years
edays
dessa maneira, seria aconselhável usar a aritmética de 64 bits. Isso pode ser conseguido convertendoyears
para unidades com umrep
uso maior que 32 bits no início da computação. Por exemplo:Ao adicionar
0s
ayears
((seconds
deve ter pelo menos 35 bits), o valorcommon_type
rep
é forçado a 64 bits para a primeira adição (years{14700} + 0s
) e continua em 64 bits ao adicionardays{0}
:Outra maneira de evitar o estouro intermediário (nesse intervalo) é truncar
years
comdays
precisão antes de adicionar maisdays
:j
tem o valor16669-12-31
. Isso evita o problema, porque agora a[216]s
unidade nunca é criada em primeiro lugar. E nunca chegamos perto do limite parayears
,days
ouyear
.Embora se você estivesse esperando
16700-01-01
, ainda tenha um problema, e a maneira de corrigi-lo é fazer um cálculo de calendário:fonte
years{14700} + 0s + days{0}
vir em uma base de código, não teria idéia do que0s
está fazendo lá e de quão importante é. Existe uma maneira alternativa, talvez mais explícita? Algo comoduration_cast<seconds>(years{14700}) + days{0}
seria melhor?duration_cast
seria pior porque é uma má forma de usarduration_cast
para conversões não truncadas. Truncar conversões pode ser uma fonte de erros lógicos, e é melhor usar apenas o "big hammer" quando você precisar, para poder identificar facilmente as conversões truncantes em seu código.use llyears = duration<long long, years::period>;
e depois usá-la. Mas provavelmente o melhor é pensar no que você está tentando realizar e questionar se está fazendo o caminho certo. Por exemplo, você realmente precisa da precisão do dia em uma escala de tempo de 10 mil anos? O calendário civil tem precisão de apenas 1 dia em 4 mil anos. Talvez um milênio de ponto flutuante seria uma unidade melhor?years
edays
. Isso literalmente adiciona alguns múltiplos de 365.2425 dias a um número inteiro de dias. Normalmente, se você deseja fazer um cálculo cronológico da ordem de meses ou anos, é para modelar alguma física ou biologia. Talvez este post sobre as diferentes maneiras de adicionarmonths
asystem_clock::time_point
ajudaria a esclarecer a diferença entre os dois tipos de cálculos: stackoverflow.com/a/43018120/576911