std :: chrono :: years 스토리지는 실제로 최소 17 비트입니까?


14

에서 cppreference

std::chrono::years (since C++20) duration</*signed integer type of at least 17 bits*/, std::ratio<31556952>>

사용하여 libc++, 그것의 밑줄 저장 보인다 std::chrono::yearsIS 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 링크 ]


2
year범위 : eel.is/c++draft/time.cal.year#members-19 years 범위 : eel.is/c++draft/time.syn . year내년의 "이름"이며 16 비트가 필요합니다. years는 크로노 지속 시간이며 year. 하나는 2를 빼고 year결과는 type years입니다. years의 결과를 보유 할 수 있어야합니다 year::max() - year::min().
Howard Hinnant

1
std::chrono::years( 30797 ) + 365d컴파일하지 않습니다.
Howard Hinnant

1
결과 years{30797} + days{365}는 204528013이며 단위는 216입니다.
Howard Hinnant

1
두 개의 지속 시간 만 추가됩니다. 금지한다는 것은 금지하는 것을 의미합니다 hours{2} + seconds{5}.
Howard Hinnant

4
내 생각 엔 그들이 때문에이 기간 유형 역법 구성 요소를 혼동하고 있다는 것이다 않는 등 유사한 이름이 있습니다. 여기에 일반적인 규칙이있다 : duration이름은 복수입니다 : years, months, days. 역법 구성 요소 이름은 단수 : year, month, day. year{30797} + day{365}컴파일 타임 오류입니다. year{2020}올해입니다. years{2020}2020 년 동안 지속됩니다.
Howard Hinnant

답변:


8

cppreference 기사가 정확합니다 . 만약 libc ++가 더 작은 타입을 사용한다면 이것은 libc ++의 버그 인 것 같습니다.


그러나 word거의 사용하지 않는 다른 것을 추가하면 year_month_day불필요하게 벡터를 크게 만들지 않습니까? at least 17 bits일반 텍스트로 계산되지 않을 수 있습니까?
샌드 톤

3
@sandthorn year_month_day은 포함 year하지 않습니다 years. year형식 short이 박람회로 사용 되더라도 의 표현은 16 비트 일 필요는 없습니다 . years정의 에서 17 비트 부분 인 OTOH 는 박람회로만 표시되지 않으므로 표준입니다. 솔직히 말해서, 적어도 17 비트는 필요하지 않다는 것은 의미가 없습니다.
Andrey Semashev

1
아 참 year으로 year_month_day보인다 int. => operator int 이것이 at least 17 bits years구현을 지원한다고 생각합니다 .
샌드 톤

답을 편집 하시겠습니까? 그것은 std :: chrono :: years 는 실제로 int이고 std :: chrono :: year
임의로

@sandthorn 대답은 정확합니다. 왜 편집해야하는지 모르겠습니다.
Andrey Semashev

4

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는 durationA의 period동일 ratio<31'556'952>및 동일 값 0. 참고 years{1}부동 소수점 표현을 days정확히 365.2425이다. 이것은 민간 연도 의 평균 길이입니다.

서브 표현식 days{365}A는 durationA의 period동일 ratio<86'400>및 동일 값 365.

서브 표현식 years{0} + days{365}A는 durationA의 period동일 ratio<216>및 동일 값 146'000. 이것은 처음 발견함으로써 형성된다 common_type_tratio<31'556'952>ratio<86'400>최대 공약수 (31'556'952, 86'400) 또는 (216)이 공통 유닛에 제 라이브러리 두 피연산자 변환되고, 그 후 공통 부에 첨가한다.

years{0}주기가 216 초인 단위 로 변환하려면 0에 146'097을 곱해야합니다. 이것은 매우 중요한 포인트입니다. 이 변환은 32 비트 만 있으면 오버플로를 쉽게 일으킬 수 있습니다.

<옆으로>

이 시점에서 혼란을 느낄 경우, 코드가 가능성이 의도 때문이다 역법 계산을하지만, 실제로하고있는 연대 기적 계산을. 달력 계산은 달력을 사용한 계산입니다.

달력은 일과 관련하여 물리적 길이가 다른 달과 년과 같은 모든 종류의 불규칙성을 가지고 있습니다. 캘린더 계산은 이러한 불규칙성을 고려합니다.

시간순 계산은 고정 단위로 작동하며 달력에 관계없이 숫자를 계산합니다. 연대기 계산은 그레고리력, 율리우스 력, 힌두 력, 중국 력 등을 사용하더라도 상관 없습니다.

</ aside>

우리는 우리의 걸릴 다음으로 146000[216]s기간을하고있는 기간으로 변환 periodratio<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_day1971-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 추가 yearsdays및 값을 사용하여 years그 발생 14,699보다 큰 int오버플.

하나는 정말와 연대 계산을 수행하고자하는 경우 yearsdays이 방법, 그것은 현명한 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

그러나 (이 범위에서) 중간 오버 플로우를 방지하는 또 다른 방법은 잘라야하는 것입니다 yearsdays정밀 전에 더 추가 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;

1
좋은 설명입니다. 나는 시간 계산에 대해 걱정하고 있습니다. years{14700} + 0s + days{0}코드베이스에서 볼 때 나는 0s거기에서 무엇 을하고 있으며 그것이 얼마나 중요한지 전혀 모를 것입니다. 대안적이고 더 명백한 방법이 있습니까? 겠습니까 뭔가처럼에게 duration_cast<seconds>(years{14700}) + days{0}더 나은?
bolov

duration_castduration_cast잘리지 않는 전환 에 사용 하기에 좋지 않은 형태이기 때문에 더 나쁩니다 . 잘림 변환은 논리 오류의 원인이 될 수 있으며 필요할 때 "큰 망치"만 사용하는 것이 가장 좋습니다. 따라서 코드에서 잘림 변환을 쉽게 확인할 수 있습니다.
Howard Hinnant

1
사용자 정의 기간 : use llyears = duration<long long, years::period>;을 만든 다음 대신 사용할 수 있습니다. 그러나 아마도 최선의 방법은 당신이 이루고자하는 것에 대해 생각하고 올바른 방향으로 가고 있는지 질문하는 것입니다. 예를 들어 10 만년의 시간 척도에 대해 정확한 하루 정밀도가 필요합니까? 시민 일정은 4 천년 동안 약 1 일까지만 정확합니다. 아마도 부동 소수점 천년이 더 나은 단위일까요?
Howard Hinnant

설명 : 시민 캘린더의 크로노 모델링은 -32767/1/1에서 32767/12/31 범위에 있습니다. 태양계 모델링에 관한 민사 달력의 정확도는 4 천년 동안 약 1 일입니다.
Howard Hinnant

1
그것은 실제로 유스 케이스에 달려 있으며 현재 동기 부여 유스 케이스를 추가 years하고 를 생각하는 데 어려움을 겪고 있습니다 days. 이것은 문자 그대로 365.2425 일의 배수를 정수일에 추가하는 것입니다. 일반적으로 몇 개월 또는 몇 년 단위로 연대순 계산을 수행하려면 물리 또는 생물학을 모델링하는 것입니다. 추가 monthssystem_clock::time_point수 있는 다른 방법에 대한이 게시물 은 두 가지 유형의 계산의 차이점을 명확하게 설명하는 데 도움이됩니다. stackoverflow.com/a/43018120/576911
Howard Hinnant
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.