Là std :: chrono :: năm lưu trữ thực sự ít nhất 17 bit?


14

Từ cppreference

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

Sử dụng libc++, có vẻ như việc lưu trữ nhấn mạnh của std::chrono::yearsshortcó chữ ký 16 bit .

std::chrono::years( 30797 )        // yields  32767/01/01
std::chrono::years( 30797 ) + 365d // yields -32768/01/01 apparently UB

Có một lỗi đánh máy trên cppreference hoặc bất cứ điều gì khác?

Thí dụ:

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

[ Liên kết Godbolt ]


2
yearphạm vi: eel.is/c++draft/time.cal.year#members-19 years phạm vi: eel.is/c++draft/time.syn . yearlà "tên" của năm dân sự và cần 16 bit. yearslà thời lượng chrono, không giống như a year. Người ta có thể trừ hai yearvà kết quả có loại years. yearslà cần thiết để có thể giữ kết quả của year::max() - year::min().
Howard Hinnant

1
std::chrono::years( 30797 ) + 365dkhông biên dịch.
Howard Hinnant

1
Kết quả years{30797} + days{365}là 204528013 với đơn vị là 216s.
Howard Hinnant

1
Đó chỉ là hai thời lượng được thêm vào. Cấm nó có nghĩa là cấm hours{2} + seconds{5}.
Howard Hinnant

4
Tôi đoán là bạn đang nhầm lẫn thành phần calendrical với các loại thời gian bởi vì họ làm có tên tương tự như vậy. Dưới đây là một nguyên tắc chung: durationtên là số nhiều: years, months, days. Tên thành phần Calendrical là số ít: year, month, day. year{30797} + day{365}là một lỗi thời gian biên dịch. year{2020}là năm nay years{2020}là một khoảng thời gian 2020 năm dài.
Howard Hinnant

Câu trả lời:


8

Bài viết cppreference là chính xác . Nếu libc ++ sử dụng loại nhỏ hơn thì đây có vẻ là một lỗi trong libc ++.


Nhưng thêm một wordcái khác mà hầu như không được sử dụng sẽ không phải là year_month_dayvectơ bulking không cần thiết? Điều đó at least 17 bitscó thể không được tính là văn bản thông thường?
Sandthorn

3
@sandthorn year_month_daycó chứa year, không years. Đại diện của yearkhông bắt buộc phải là 16 bit, mặc dù loại shortđược sử dụng làm giải trình. OTOH, phần 17 bit trong yearsđịnh nghĩa là quy phạm vì nó không được đánh dấu là chỉ giải thích. Và thẳng thắn, nói rằng nó ít nhất 17 bit và sau đó không yêu cầu nó là vô nghĩa.
Andrey Semashev

1
Ah yeartrong year_month_daydường như là intthực sự. => Toán tử int Tôi nghĩ điều này hỗ trợ at least 17 bits yearsthực hiện.
Sandthorn

Bạn có phiền chỉnh sửa câu trả lời của bạn? Hóa ra std :: chrono :: năm thực sự là int và std :: chrono :: năm là tối đa ở 32767 một cách tự do ..
sandthorn

@sandthorn Câu trả lời là chính xác, tôi không hiểu tại sao tôi cần chỉnh sửa nó.
Andrey Semashev

4

Tôi đang chia nhỏ ví dụ tại https://godbolt.org/z/SNivyp từng mảnh:

  auto a = std::chrono::year_month_day( 
    sys_days( 
      std::chrono::floor<days>(
        std::chrono::years(0) 
        + std::chrono::days( 365 )
      )
    )
  );

Đơn giản hóa và giả định using namespace std::chronolà trong phạm vi:

year_month_day a = sys_days{floor<days>(years{0} + days{365})};

Tiểu biểu years{0}là một durationvới một periodbằng ratio<31'556'952>và một giá trị tương đương để 0. Lưu ý rằng years{1}, được biểu thị dưới dạng dấu phẩy động days, chính xác là 365.2425. Đây là độ dài trung bình của năm dân sự.

Tiểu biểu days{365}là một durationvới một periodbằng ratio<86'400>và một giá trị tương đương để 365.

Tiểu biểu years{0} + days{365}là một durationvới một periodbằng ratio<216>và một giá trị tương đương để 146'000. Này được hình thành bởi việc tìm kiếm đầu tiên common_type_tcủa ratio<31'556'952>ratio<86'400>là GCD (31'556'952, 86'400), hoặc 216. Thư viện cải đầu tiên cả hai toán hạng cho đơn vị phổ biến này, và sau đó thực hiện việc bổ sung trong đơn vị thông thường.

Để chuyển đổi years{0}thành các đơn vị có thời gian là 216, người ta phải nhân 0 với 146'097. Điều này xảy ra là một điểm rất quan trọng. Chuyển đổi này có thể dễ dàng gây ra tràn khi chỉ thực hiện với 32 bit.

<sang một bên>

Nếu tại thời điểm này bạn cảm thấy bối rối, đó là bởi vì mã có thể có ý định tính toán theo lịch , nhưng thực sự đang thực hiện một tính toán theo thời gian . Tính toán lịch là tính toán với lịch.

Lịch có tất cả các loại bất thường, chẳng hạn như tháng và năm có độ dài vật lý khác nhau về ngày. Một tính toán lịch có tính đến những bất thường này.

Một tính toán theo thời gian làm việc với các đơn vị cố định, và chỉ cần chỉnh các số mà không liên quan đến lịch. Một tính toán theo thời gian không quan tâm nếu bạn sử dụng lịch Gregorian, lịch Julian, lịch Hindu, lịch Trung Quốc, v.v.

</ sang một bên>

Tiếp theo chúng ta mất của chúng tôi 146000[216]sthời gian và chuyển nó sang một thời gian với một periodsố ratio<86'400>(trong đó có một kiểu bí danh tên days). Hàm floor<days>()thực hiện chuyển đổi này và kết quả là 365[86400]s, hoặc đơn giản hơn, chỉ 365d.

Bước tiếp theo lấy durationvà chuyển đổi nó thành a time_point. Các loại time_pointtime_point<system_clock, days>trong đó có một kiểu bí danh tên sys_days. Đây chỉ đơn giản là số đếm dayskể từ system_clockkỷ nguyên, đó là 1970-01-01 00:00:00 UTC, không bao gồm giây nhuận.

Cuối cùng, sys_daysđược chuyển đổi thành a year_month_dayvới giá trị 1971-01-01.

Một cách đơn giản hơn để thực hiện tính toán này là:

year_month_day a = sys_days{} + days{365};

Hãy xem xét tính toán tương tự này:

year_month_day j = sys_days{floor<days>(years{14699} + days{0})};

Kết quả này trong ngày 16668-12-31. Đó có lẽ là một ngày sớm hơn bạn mong đợi ((14699 + 1970) -01-01). Hiện years{14699} + days{0}tại phụ là : 2'147'479'803[216]s. Lưu ý rằng giá trị thời gian chạy gần INT_MAX( 2'147'483'647) và cơ sở repcủa cả hai yearsdaysint.

Thật vậy, nếu bạn chuyển đổi years{14700}sang các đơn vị của [216]sbạn bị tràn : -2'147'341'396[216]s.

Để khắc phục điều này, hãy chuyển sang tính toán theo lịch:

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

Tất cả các kết quả tại https://godbolt.org/z/SNivyp được thêm yearsdaysvà sử dụng một giá trị cho yearsrằng lớn hơn 14.699 đang gặp inttràn.

Nếu một người thực sự muốn thực hiện các tính toán theo thời gian yearsdaystheo cách này, thì sẽ là khôn ngoan khi sử dụng số học 64 bit. Điều này có thể được thực hiện bằng cách chuyển đổi yearssang các đơn vị có repsử dụng sớm hơn 32 bit trong tính toán. Ví dụ:

years{14700} + 0s + days{0}

Bằng cách thêm 0svào years, ( secondsphải có ít nhất 35 bit), sau đó common_type repđược buộc thành 64 bit cho lần thêm đầu tiên ( years{14700} + 0s) và tiếp tục trong 64 bit khi thêm days{0}:

463'887'194'400s == 14700 * 365.2425 * 86400

Tuy nhiên, một cách khác để tránh tràn trung gian (ở cự ly này) là để cắt ngắn yearsđể dayschính xác trước khi bổ sung thêm days:

year_month_day j = sys_days{floor<days>(years{14700})} + days{0};

jcó giá trị 16669-12-31. Điều này tránh được vấn đề bởi vì bây giờ [216]sđơn vị không bao giờ được tạo ra ở nơi đầu tiên. Và chúng tôi thậm chí không bao giờ đến gần giới hạn cho years, dayshoặc year.

Mặc dù nếu bạn đang mong đợi 16700-01-01, thì bạn vẫn có một vấn đề, và cách để khắc phục nó là thực hiện một phép tính theo lịch thay thế:

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

1
Giải thích tuyệt vời. Tôi lo lắng về tính toán thời gian. Nếu tôi thấy years{14700} + 0s + days{0}trong một codebase, tôi sẽ không biết cái gì 0sđang làm ở đó và nó quan trọng như thế nào. Có một cách khác, có thể rõ ràng hơn? Một cái gì đó như duration_cast<seconds>(years{14700}) + days{0}sẽ tốt hơn?
bolov

duration_castsẽ tệ hơn vì đây là hình thức xấu để sử dụng duration_castcho các chuyển đổi không cắt ngắn. Chuyển đổi cắt ngắn có thể là nguồn gốc của lỗi logic và tốt nhất là chỉ sử dụng "búa lớn" khi bạn cần, để bạn có thể dễ dàng phát hiện ra các chuyển đổi cắt ngắn trong mã của mình.
Howard Hinnant

1
Người ta có thể tạo thời lượng tùy chỉnh: use llyears = duration<long long, years::period>;và sau đó sử dụng thời lượng đó. Nhưng có lẽ điều tốt nhất là suy nghĩ về những gì bạn đang cố gắng thực hiện và đặt câu hỏi liệu bạn có đang đi đúng hướng hay không. Ví dụ, bạn có thực sự cần độ chính xác ngày theo thang thời gian là 10 nghìn năm không? Lịch dân sự chỉ chính xác đến khoảng 1 ngày trong 4 nghìn năm. Có lẽ một thiên niên kỷ điểm nổi sẽ là một đơn vị tốt hơn?
Howard Hinnant

Làm rõ: mô hình lịch dân sự của chrono chính xác trong khoảng -32767/1/1 đến 32767/12/31. Độ chính xác của lịch dân sự đối với việc mô hình hóa hệ mặt trời chỉ khoảng 1 ngày trong 4 nghìn năm.
Howard Hinnant

1
Nó thực sự sẽ phụ thuộc vào trường hợp sử dụng và tôi hiện đang gặp khó khăn khi nghĩ về trường hợp sử dụng có động lực để thêm yearsdays. Đây thực sự là thêm một số bội số của 365.2425 ngày vào một số ngày không thể thiếu. Thông thường nếu bạn muốn thực hiện một tính toán theo thời gian theo thứ tự tháng hoặc năm, đó là mô hình hóa một số vật lý hoặc sinh học. Có lẽ bài đăng này về các cách khác nhau để thêm monthsvào system_clock::time_pointsẽ giúp làm rõ sự khác biệt giữa hai loại tính toán: stackoverflow.com/a/43018120/576911
Howard Hinnant
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.