std::chrono::years (since C++20) duration</*signed integer type of at least 17 bits*/, std::ratio<31556952>>
使用libc++
,似乎std::chrono::years
的下划线存储是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);
}
cppreference文章为correct。如果libc ++使用较小的类型,则这似乎是libc ++中的错误。
[我正在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。这是公民年度的[[average长度。
days{365}
是duration
,其中period
等于ratio<86'400>
,并且值等于365
。子表达式years{0} + days{365}
是duration
,其中period
等于ratio<216>
,并且值等于146'000
。这是通过首先找到LCM(31'556'952,86'400)或216的common_type_t
和ratio<31'556'952>
的ratio<86'400>
来形成的。库首先将两个操作数都转换为该公共单位,然后以通用单位进行加法。
要将years{0}
转换为周期为216s的单位,必须将0乘以146'097。这恰好是非常重要的一点。仅使用32位完成此转换就很容易导致溢出。
如果在这一点上您感到困惑,那是因为该代码可能打算进行
calendrical
计算,但实际上是在进行chronological计算。日历计算是带有日历的计算。 日历具有各种不规则性,例如月份和年份在天数方面具有不同的物理长度。日历计算将这些不规则性考虑在内。按时间顺序进行的计算使用固定单位,并且仅计算出数字而不考虑日历。如果您使用公历,儒略历,印度历,中国历等,按时间顺序计算并不重要。
aside>
接下来,我们将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
纪元(1970年1月1日00:00:00 UTC)开始,这只是system_clock
的计数,不包括leap秒。
最后将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位算术将是明智的。这可以通过在计算的早期使用大于32位将years
转换为具有rep
的单位来实现。例如:
years{14700} + 0s + days{0}
[通过将0s
加到years
,(seconds
必须至少具有35位),然后对于第一次加法[common_type
),rep
years{14700} + 0s
被强制为64位,并继续以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;