Я разбиваю пример на https://godbolt.org/z/SNivyp по частям:
auto a = std::chrono::year_month_day(
sys_days(
std::chrono::floor<days>(
std::chrono::years(0)
+ std::chrono::days( 365 )
)
)
);
Упрощение и допущение, что using namespace std::chrono
находится в области действия:
year_month_day a = sys_days{floor<days>(years{0} + days{365})};
Подвыражение years{0}
представляет собой duration
с period
, равным ratio<31'556'952>
и значением, равным 0
. Обратите внимание, что years{1}
, выраженное как days
с плавающей точкой, в точности равно 365.2425. Это средняя длина гражданского года.
Подвыражение days{365}
представляет собой duration
с period
, равным ratio<86'400>
и значением, равным 365
.
Подвыражение years{0} + days{365}
представляет собой duration
с period
, равным ratio<216>
и значением, равным 146'000
. Это формируется путем нахождения сначала common_type_t
из ratio<31'556'952>
и ratio<86'400>
, который представляет собой GCD (31'556'952, 86'400) или 216. Библиотека сначала преобразует оба операнда в эту общую единицу, а затем выполняет сложение в общей единице.
Чтобы преобразовать years{0}
в единицы с периодом 216 с, нужно умножить 0 на 146'097. Это очень важный момент. Это преобразование может легко вызвать переполнение, когда выполняется только с 32 битами.
Далее мы возьмем наш 146000[216]s
duration и преобразовать его в длительность с period
из ratio<86'400>
(который имеет псевдоним типа с именем days
). Функция floor<days>()
выполняет это преобразование, и в результате получается 365[86400]s
, или, проще говоря, просто 365d
.
На следующем шаге берется duration
и преобразуется в time_point
. Типом time_point
является time_point<system_clock, days>
с псевдонимом типа sys_days
. Это просто число days
, начиная с эпохи system_clock
, то есть 1970-01-01 00:00:00 UT C, исключая високосные секунды.
Наконец, sys_days
преобразуется в year_month_day
со значением 1971-01-01
.
Более простой способ сделать это вычисление:
year_month_day a = sys_days{} + days{365};
Рассмотрим подобное вычисление:
year_month_day j = sys_days{floor<days>(years{14699} + days{0})};
Это приводит к дате 16668-12-31
. Что, вероятно, на день раньше, чем вы ожидали ((14699 + 1970) -01-01). Подвыражение years{14699} + days{0}
теперь: 2'147'479'803[216]s
. Обратите внимание, что значение времени выполнения приближается к INT_MAX
(2'147'483'647
), и что базовые rep
для years
и days
равны int
.
Действительно, если вы конвертируете years{14700}
до единиц [216]s
вы получаете переполнение: -2'147'341'396[216]s
.
Чтобы исправить это, переключитесь на календарный расчет:
year_month_day j = (1970y + years{14700})/1/1;
Все результаты в https://godbolt.org/z/SNivyp, которые добавляют years
и days
и используют значение для years
, превышающее 14699, испытывают переполнение int
.
Если кто-то действительно хочет это сделать хронологические вычисления с years
и days
таким образом, тогда было бы разумно использовать 64-битную арифметику c. Это может быть достигнуто путем преобразования years
в единицы с rep
, используя более 32 бит в начале вычисления. Например:
years{14700} + 0s + days{0}
Если добавить 0s
к years
, (seconds
должно содержать не менее 35 бит), то для common_type
rep
в первый раз вводится значение 64 бит сложение (years{14700} + 0s
) и продолжается в 64 битах при добавлении days{0}
:
463'887'194'400s == 14700 * 365.2425 * 86400
Еще один способ избежать промежуточного переполнения (в этом диапазоне) - усечь years
до days
точность до добавление еще days
:
year_month_day j = sys_days{floor<days>(years{14700})} + days{0};
j
имеет значение 16669-12-31
. Это позволяет избежать проблемы, поскольку теперь модуль [216]s
никогда не создается. И мы никогда даже не приблизимся к пределу для years
, days
или year
.
Хотя, если вы ожидали 16700-01-01
, то у вас все еще есть проблема, и способ ее исправить вместо этого сделать календарный расчет:
year_month_day j = (1970y + years{14700})/1/1;