에서 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 링크 ]
답변
cppreference 기사가 정확합니다 . 만약 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}
A는 duration
A의 period
동일 ratio<31'556'952>
및 동일 값 0
. 참고 years{1}
부동 소수점 표현을 days
정확히 365.2425이다. 이것은 민간 연도 의 평균 길이입니다.
서브 표현식 days{365}
A는 duration
A의 period
동일 ratio<86'400>
및 동일 값 365
.
서브 표현식 years{0} + days{365}
A는 duration
A의 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 비트 만 있으면 오버플로를 쉽게 일으킬 수 있습니다.
<옆으로>
이 시점에서 혼란을 느낄 경우, 코드가 가능성이 의도 때문이다 역법 계산을하지만, 실제로하고있는 연대 기적 계산을. 달력 계산은 달력을 사용한 계산입니다.
달력은 일과 관련하여 물리적 길이가 다른 달과 년과 같은 모든 종류의 불규칙성을 가지고 있습니다. 캘린더 계산은 이러한 불규칙성을 고려합니다.
시간순 계산은 고정 단위로 작동하며 달력에 관계없이 숫자를 계산합니다. 연대기 계산은 그레고리력, 율리우스 력, 힌두 력, 중국 력 등을 사용하더라도 상관 없습니다.
</ 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
이후의 숫자이며 system_clock
, 윤초를 제외한 1970-01-01 00:00:00 UTC입니다.
마지막으로 값을 가진 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
하면 overflow : -2'147'341'396[216]s
.
이 문제를 해결하려면 금전 계산으로 전환하십시오.
year_month_day j = (1970y + years{14700})/1/1;
의 모든 결과 https://godbolt.org/z/SNivyp 추가 years
및 days
및 값을 사용하여 years
그 발생 14,699보다 큰 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;