std :: chrono :: ปีการจัดเก็บอย่างน้อย 17 บิตจริงหรือ


14

จากcppreference

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);
}

[ ลิงค์ Godbolt ]


2
yearช่วง: eel.is/c++draft/time.cal.year#members-19 yearsช่วง: eel.is/c++draft/time.syn yearคือ "ชื่อ" ของปีโยธาและต้องใช้ 16 บิต yearsเป็นระยะเวลา Chrono yearไม่ใช่สิ่งเดียวกับ หนึ่งสามารถลบสองและผลที่มีประเภท year จะต้องสามารถที่จะถือผลมาจากการ yearsyearsyear::max() - year::min()
Howard Hinnant

1
std::chrono::years( 30797 ) + 365dไม่ได้รวบรวม
Howard Hinnant

1
ผลลัพธ์ของyears{30797} + days{365}คือ 204528013 กับหน่วยของ 216s
Howard Hinnant

1
นั่นเป็นเพียงสองช่วงเวลาที่เพิ่มเข้ามา hours{2} + seconds{5}ที่จะห้ามมันจะหมายถึงห้าม
Howard Hinnant

4
ฉันเดาว่าคุณกำลังสับสนส่วนประกอบ calendrical ประเภทระยะเวลาเพราะพวกเขาไม่ได้มีชื่อที่คล้ายกันเช่น นี่คือกฎทั่วไป: durationชื่อพหูพจน์: years, ,months daysชื่อองค์ประกอบ Calendrical 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จะไม่นับเป็นข้อความ norminal?
sandthorn

3
@sandthorn year_month_dayมีไม่year yearsการเป็นตัวแทนของyearไม่จำเป็นต้องเป็น 16 บิตแม้ว่าประเภทshortจะใช้เป็นการแสดงออก OTOH ส่วน 17 บิตในyearsคำจำกัดความเป็นบรรทัดฐานเนื่องจากไม่ได้ทำเครื่องหมายเป็นการแสดงออกเท่านั้น และตรงไปตรงมาบอกว่ามันเป็นอย่างน้อย 17 บิตแล้วไม่ต้องการมันไม่มีความหมาย
Andrey Semashev

1
Ah yearในyear_month_dayน่าจะเป็นintจริง => โอเปอเรเตอร์ intฉันคิดว่านี่รองรับat least 17 bits yearsการใช้งาน
sandthorn

คุณต้องการแก้ไขคำตอบของคุณหรือไม่ มันกลับกลายเป็นมาตรฐาน :: chrono :: ปีเป็นจริง int และstd :: chrono :: ปีสูงสุดที่ 32767 โดยพลการ ..
sandthorn

@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}เป็นdurationด้วยperiodเท่ากับและค่าเท่ากับratio<31'556'952> 0โปรดทราบว่าyears{1}ซึ่งแสดงเป็น floating-point 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}เป็นหน่วยที่มีช่วงเวลา 216s จะต้องคูณ 0 ด้วย 146'097 นี่เป็นจุดที่สำคัญมาก การแปลงนี้สามารถทำให้เกิดการโอเวอร์โฟลว์ได้อย่างง่ายดายเมื่อใช้เพียง 32 บิต

<กัน>

หาก ณ จุดนี้คุณรู้สึกสับสนอาจเป็นเพราะรหัสนั้นมีแนวโน้มที่จะทำการคำนวณตามปฏิทินแต่จริงๆแล้วเป็นการคำนวณตามลำดับเวลา การคำนวณปฏิทินคือการคำนวณด้วยปฏิทิน

ปฏิทินมีสิ่งผิดปกติทุกประเภทเช่นเดือนและปีที่มีความยาวต่างกันในแง่ของวัน การคำนวณปฏิทินจะพิจารณาความผิดปกติเหล่านี้

การคำนวณตามลำดับเวลาทำงานกับหน่วยคงที่และเพียงแค่คำนวณตัวเลขโดยไม่คำนึงถึงปฏิทิน การคำนวณตามลำดับเวลาไม่สนใจว่าคุณใช้ปฏิทินเกรกอเรียน, ปฏิทินจูเลียน, ปฏิทินฮินดู, ปฏิทินจีน ฯลฯ

</ กัน>

ต่อไปเราจะใช้เวลาของเรา146000[216]sระยะเวลาและแปลงเป็นระยะเวลาที่มีperiodของratio<86'400>(ซึ่งมีประเภทนามแฝงชื่อdays) ฟังก์ชั่นfloor<days>()ไม่แปลงนี้และผลที่ได้คือหรือมากกว่าเพียงแค่365[86400]s365d

ขั้นตอนต่อไปจะใช้เวลาและแปลงเป็น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และเป็นdaysint

แน่นอนถ้าคุณแปลง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 สิ่งนี้สามารถทำได้โดยการแปลง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};

j16669-12-31มีค่า วิธีนี้จะช่วยหลีกเลี่ยงปัญหาได้เพราะตอนนี้[216]sไม่มีการสร้างยูนิตในตอนแรก และเราไม่เคยแม้แต่จะได้ใกล้ชิดกับขีด จำกัด สำหรับyears, หรือdaysyear

แม้ว่าคุณจะคาดหวัง16700-01-01แต่คุณก็ยังมีปัญหาและวิธีแก้ไขก็คือการคำนวณแบบ Calendrical แทน:

year_month_day j = (1970y + years{14700})/1/1;

1
คำอธิบายที่ดี ฉันกังวลเกี่ยวกับการคำนวณตามลำดับเวลา ถ้าฉันเห็นyears{14700} + 0s + days{0}ใน codebase ฉันก็ไม่รู้0sจะทำยังไงและมันสำคัญแค่ไหน มีวิธีอื่นอาจชัดเจนกว่านี้ไหม? บางสิ่งduration_cast<seconds>(years{14700}) + days{0}จะดีขึ้นไหม
bolov

duration_castจะเลวร้ายยิ่งขึ้นเพราะเป็นรูปแบบที่ไม่ดีที่จะใช้duration_castสำหรับการแปลงที่ไม่ถูกตัดทอน การตัดทอนการแปลงอาจเป็นสาเหตุของข้อผิดพลาดทางตรรกะและควรใช้ "ค้อนใหญ่" เมื่อคุณต้องการเท่านั้นเพื่อที่คุณจะได้เห็นการแปลงที่ถูกตัดทอนในรหัสของคุณ
Howard Hinnant

1
หนึ่งสามารถสร้างช่วงเวลาที่กำหนดเอง: use llyears = duration<long long, years::period>;แล้วใช้มันแทน แต่อาจเป็นสิ่งที่ดีที่สุดคือการคิดเกี่ยวกับสิ่งที่คุณพยายามทำให้สำเร็จและถามว่าคุณกำลังทำในสิ่งที่ถูกต้องหรือไม่ ตัวอย่างเช่นคุณต้องการความแม่นยำของวันในระดับเวลาที่ 10,000 ปีหรือไม่ ปฎิทินพลเรือนนั้นมีความแม่นยำเพียงประมาณ 1 วันใน 4 พันปีเท่านั้น บางทีพันปีจุดลอยตัวอาจเป็นหน่วยที่ดีกว่านี้ได้ไหม
Howard Hinnant

การชี้แจง: การสร้างแบบจำลองปฏิทินพลเรือนของโครโนนั้นแน่นอนในช่วง -32767/1/1 ถึง 32767/12/31 ความถูกต้องของปฏิทินพลเรือนเกี่ยวกับการสร้างแบบจำลองระบบสุริยจักรวาลมีเพียง 1 วันใน 4 พันปีเท่านั้น
Howard Hinnant

1
จริงๆมันจะขึ้นอยู่กับกรณีการใช้งานและฉันกำลังมีความคิดปัญหาในกรณีที่ใช้การสร้างแรงจูงใจในการเพิ่มและyears daysนี่คือการเพิ่มทวีคูณของ 365.2425 วันเป็นจำนวนอินทิกรัลบางวัน โดยปกติถ้าคุณต้องการทำการคำนวณตามลำดับเดือนหรือปีมันเป็นแบบจำลองฟิสิกส์หรือชีววิทยา บางทีอาจจะโพสต์นี้เกี่ยวกับวิธีการที่แตกต่างกันเพื่อเพิ่มmonthsเพื่อsystem_clock::time_pointจะช่วยชี้แจงความแตกต่างระหว่างทั้งสองประเภทของการคำนวณ: stackoverflow.com/a/43018120/576911
ฮาวเวิร์ด Hinnant
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.