Из cppreference
std::chrono::years (since C++20) duration</*signed integer type of at least 17 bits*/, std::ratio<31556952>>
Используя libc++
это кажется , подчеркивающее хранение std::chrono::years
IS , short
который подписанная 16 бит .
std::chrono::years( 30797 ) // yields 32767/01/01
std::chrono::years( 30797 ) + 365d // yields -32768/01/01 apparently UB
Есть ли опечатка на cppreference или что-то еще?
Пример:
#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);
}
[ Godbolt link ]
year
диапазон: eel.is/c++draft/time.cal.year#members-19years
диапазон: eel.is/c++draft/time.syn .year
является именем гражданского года и требует 16 бит.years
это длительность хроно, а не то же самое, чтоyear
. Можно вычесть два,year
и результат имеет типyears
.years
требуется, чтобы иметь возможность удерживать результатyear::max() - year::min()
.std::chrono::years( 30797 ) + 365d
не компилируетсяyears{30797} + days{365}
204528013 с единицами 216.hours{2} + seconds{5}
.duration
имена во множественном числеyears
,months
,days
. Календарные имена компонентов единичны:year
,month
,day
.year{30797} + day{365}
это ошибка времени компиляцииyear{2020}
в этом годуyears{2020}
это продолжительность 2020 года.Ответы:
Статья cppreference верна . Если libc ++ использует меньший тип, то это похоже на ошибку в libc ++.
источник
word
который, вероятно, едва использовался, не было бы ненужнымyear_month_day
вектором? Разве этоat least 17 bits
не считается нормальным текстом?year_month_day
содержитyear
, а неyears
. Представлениеyear
не обязательно должно быть 16-битным, хотя типshort
используется в качестве экспозиции. OTOH, 17-битная часть вyears
определении является нормативной, поскольку она не помечена как только экспозиция. И, честно говоря, говорить, что это как минимум 17 бит, а затем не требует его, бессмысленно.year
в ,year_month_day
кажется, вint
самом деле. => оператор int Я думаю, что это поддерживаетat least 17 bits
years
реализацию.Я разбираю пример на https://godbolt.org/z/SNivyp по частям:
Упрощение и допущение
using namespace std::chrono
находится в области:Подвыражение
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>
который является НОД (31'556'952, 86'400), или 216. Библиотека сначала преобразует оба операнда к этой общей единице, а затем делают добавление в общем блоке.Чтобы преобразовать
years{0}
в юниты с периодом 216 с, нужно умножить 0 на 146'097. Это очень важный момент. Это преобразование может легко вызвать переполнение, когда сделано только с 32 битами.<В сторону>
Если в этот момент вы чувствуете растерянность, это потому, что код, скорее всего, предназначен для календарных вычислений, но на самом деле выполняет хронологические вычисления. Календарные вычисления - это вычисления с календарями.
Календари имеют всевозможные нарушения, например, месяцы и годы разной физической длины в днях. Календарное вычисление учитывает эти нарушения.
Хронологический расчет работает с фиксированными единицами и просто проверяет числа, не обращая внимания на календари. Хронологические вычисления не заботятся, используете ли вы григорианский календарь, юлианский календарь, индусский календарь, китайский календарь и т. Д.
</ В сторону>
Далее мы берем нашу
146000[216]s
продолжительность и превратить его в период с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 UTC, исключая високосные секунды.Наконец,
sys_days
преобразовывается в значениеyear_month_day
со значением1971-01-01
.Более простой способ сделать это вычисление:
Рассмотрим это похожее вычисление:
Это приводит к дате
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
.Чтобы это исправить, переключитесь на календарный расчет:
Все результаты на https://godbolt.org/z/SNivyp , которые добавляются
years
иdays
используют значение дляyears
которого больше 14699, испытываютint
переполнение.Если кто-то действительно хочет делать хронологические вычисления
years
иdays
таким образом, то было бы разумно использовать 64-битную арифметику. Это может быть достигнуто путем преобразованияyears
в единицы измерения сrep
использованием более 32 бит в начале вычислений. Например:При добавлении
0s
кyears
(seconds
должно быть не менее 35 бит),common_type
rep
для первого добавления (years{14700} + 0s
) устанавливается значение 64 бита и при добавлении продолжается 64 битаdays{0}
: :Еще один способ избежать промежуточного переполнения (в этом диапазоне) состоит в том, чтобы усечь
years
доdays
точности, прежде чем добавлять большеdays
:j
имеет значение16669-12-31
. Это позволяет избежать проблемы, потому что теперь[216]s
устройство никогда не создается в первую очередь. И мы даже не приблизиться к пределу приyears
,days
илиyear
.Хотя, если вы ожидали
16700-01-01
, тогда у вас все еще есть проблема, и способ ее исправить - сделать вместо этого календарные вычисления:источник
years{14700} + 0s + days{0}
в базе кода, я бы не знал, что0s
там происходит и насколько это важно. Есть ли альтернативный, может быть, более явный способ? Будет ли что-то вродеduration_cast<seconds>(years{14700}) + days{0}
лучше?duration_cast
было бы хуже, потому что это плохая форма для использованияduration_cast
для неусечающих преобразований. Усеченные преобразования могут быть источником логических ошибок, и лучше использовать «большой молот» только тогда, когда вам это нужно, чтобы вы могли легко обнаружить усеченные преобразования в своем коде.use llyears = duration<long long, years::period>;
а затем использовать ее вместо. Но, вероятно, лучше всего подумать о том, чего вы пытаетесь достичь, и спросить, правильно ли вы это делаете. Например, вам действительно нужна точность дня на шкале времени, которая составляет 10 тысяч лет? Гражданский календарь точен только до 1 дня за 4 тысячи лет. Возможно, тысячелетия с плавающей запятой были бы лучшей единицей?years
иdays
. Это буквально прибавляет кратное 365,2425 дня к целому числу дней. Обычно, если вы хотите сделать хронологический расчет порядка месяцев или лет, он должен моделировать физику или биологию. Возможно , этот пост по - разному , чтобы добавитьmonths
кsystem_clock::time_point
помогло бы прояснить разницу между этими двумя типами вычислений: stackoverflow.com/a/43018120/576911