Std :: chrono :: yıl depolama alanı gerçekten en az 17 bit mi?


14

Gönderen cppreference

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

Kullanılması libc++, bunun altını depolama görünüyor std::chrono::yearsDİR shortimzalanır 16 bit .

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

Cppreference veya başka bir şey üzerinde bir yazım hatası var mı ?

Misal:

#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 bağlantısı ]


2
yeararalık: eel.is/c++draft/time.cal.year#members-19 years aralık: eel.is/c++draft/time.syn . yearsivil yılın "adı" dır ve 16 bit gerektirir. yearsbir chrono süresidir, a ile aynı şey değildir year. Biri iki çıkartabilir yearve sonucun türü vardır years. yearssonucunu tutabilmek için gereklidir year::max() - year::min().
Howard Hinnant

1
std::chrono::years( 30797 ) + 365dderlemez.
Howard Hinnant

1
Sonuç years{30797} + days{365}, 216'lık birimlerle 204528013'tür.
Howard Hinnant

1
Bu sadece iki süre ekleniyor. Yasaklamak yasaklamak anlamına gelir hours{2} + seconds{5}.
Howard Hinnant

4
Tahminimce, benzer türlere sahip oldukları için kalendrik bileşenleri süre tipleriyle karıştırıyorsunuz . İşte genel bir kural var: durationisimler çoğul şunlardır: years, months, days. Takvimsel bileşen isimleri tekil şunlardır: year, month, day. year{30797} + day{365}derleme zamanı hatasıdır. year{2020}bu yıl. years{2020}2020 yıl sürmektedir.
Howard Hinnant

Yanıtlar:


8

Cppreference makalesi doğrudur . Libc ++ daha küçük bir tip kullanıyorsa, bu libc ++ 'da bir hata gibi görünüyor.


Ancak word, neredeyse hiç kullanılmayan başka bir tane eklemek , year_month_dayvektörleri gereksiz yere toplulaştırmaz mı? Bu at least 17 bitsnormal metin olarak sayılmaz mı?
sandthorn

3
@sandthorn year_month_dayiçerir year, içermez years. Temsili yeartürü her ne kadar, 16 bit olarak gerekli değildir shortmaruz olarak kullanılır. OTOH, yearstanımdaki 17 bitlik kısım sadece açıklama olarak işaretlenmediğinden normatiftir. Ve açıkçası, en az 17 bit olduğunu ve sonra gerektirmediğini söylemek anlamsızdır.
Andrey Semashev

1
Ah yeariçinde year_month_daygörünüyor intgerçekten. => operator int Bence bu at least 17 bits yearsuygulamayı destekliyor .
sandthorn

Cevabınızı düzenler misiniz? Görünüşe göre std :: chrono :: yıl aslında int ve std :: chrono :: yıl 32767 keyfi olarak maksimum ..
sandthorn

@sandthorn Cevap doğru, neden düzenlemem gerektiğini anlamıyorum.
Andrey Semashev

4

Https://godbolt.org/z/SNivyp adresindeki örneği parça parça çözüyorum:

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

Basitleştirme ve varsayma using namespace std::chronokapsam dahilindedir:

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

Alt-ifade years{0}a, durationbir ile periodeşit ratio<31'556'952>bir değere eşit 0. Not years{1}kayan nokta olarak ifade edilen days, tam olarak 365.2425 olup. Bu, sivil yılın ortalama uzunluğudur.

Alt-ifade days{365}a, durationbir ile periodeşit ratio<86'400>bir değere eşit 365.

Alt-ifade years{0} + days{365}a, durationbir ile periodeşit ratio<216>bir değere eşit 146'000. Bu, ilk önce GCD (31'556'952, 86'400) veya 216 common_type_tolan ratio<31'556'952>ve bularak oluşturulur ratio<86'400>. Kütüphane ilk olarak her iki işleneni de bu ortak birime dönüştürür ve daha sonra ortak birime ekleme yapar.

years{0}216 saniyelik birimlere dönüştürmek için 0 ile 146'097 arasında çarpılmalıdır. Bu çok önemli bir nokta. Bu dönüştürme, yalnızca 32 bit ile yapıldığında kolayca taşmaya neden olabilir.

<Kenara>

Bu noktada kafanız karışırsa, bunun nedeni kodun muhtemelen bir hesaplamalı hesaplama yapmasıdır , ancak aslında kronolojik bir hesaplama yapıyor olmasıdır . Calendrical hesaplamalar takvimlerle yapılan hesaplamalardır.

Takvimler, aylar ve yıllar günler bakımından farklı fiziksel uzunluklarda olmak gibi her türlü düzensizliğe sahiptir. Kalendrik bir hesaplama bu düzensizlikleri dikkate alır.

Kronolojik bir hesaplama sabit birimlerle çalışır ve sadece takvimlerden bağımsız olarak sayıları çıkarır. Gregoryen takvimini, Julian takvimini, Hindu takvimini, Çin takvimini vb. Kullanırsanız kronolojik bir hesaplama umursamaz.

</ Kenara>

Bizim almak Sonraki 146000[216]ssüresini ve bir süre dönüştürmek periodarasında ratio<86'400>(adında bir tip-takma adı days). İşlev floor<days>()bu dönüşümü yapar ve sonuç 365[86400]s, ya da daha basit olarak, sadece 365d.

Bir sonraki adım alır durationve a'ya dönüştürür time_point. Türü time_pointolan time_point<system_clock, days>adında bir tip-takma adı olan sys_days. Bu, artık saniye hariç 1970-01-01 00:00:00 UTC olan çağdan daysbu yana sadece bir system_clocksayıdır.

Son olarak, değeri sys_daysolan bir a dönüştürülür .year_month_day1971-01-01

Bu hesaplamayı yapmanın daha basit bir yolu:

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

Bu benzer hesaplamayı düşünün:

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

Bu tarihle sonuçlanır 16668-12-31. Muhtemelen beklediğinizden bir gün önce ((14699 + 1970) -01-01). Subexpression years{14699} + days{0}artık şudur: 2'147'479'803[216]s. Çalışma zamanı değeri üzeredir unutmayın INT_MAX( 2'147'483'647), ve altta yatan repher ikisinin yearsve daysbir int.

Dönüştürmek Gerçekten de years{14700}birimlere ait [216]staşma olsun: -2'147'341'396[216]s.

Bunu düzeltmek için kalendrik hesaplamaya geçin:

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

Sonuçların tamamı https://godbolt.org/z/SNivyp ekliyoruz yearsve daysve bir değer kullanılarak yearso yaşıyoruz büyüktür 14699 daha inttaşması.

Eğer gerçekten yearsve daysbu şekilde kronolojik hesaplamalar yapmak istiyorsa , 64 bit aritmetik kullanmak akıllıca olacaktır. Bu , hesaplamanın başında 32 bit'ten daha fazla kullanan yearsbirimlere dönüştürülerek gerçekleştirilebilir rep. Örneğin:

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

Ekleyerek 0siçin years, ( secondsdaha sonra en az 35 bit olması gerekir) common_type rep(birinci sıra için 64 bit zorlanır years{14700} + 0s) ve 64 bit ekleyerek devam days{0}:

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

Yine (bu mesafeden) ara taşmasını önlemek için başka bir yol kesecek şekilde olduğu yearsiçin dayshassas önce daha ekleyerek days:

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

jdeğeri vardır 16669-12-31. Bu, sorunu önler çünkü [216]sünite asla ilk etapta yaratılmamıştır. Ve asla years, daysveya sınırına yaklaşmıyoruz year.

Eğer bekliyorsanız 16700-01-01, o zaman hala bir sorununuz var ve bunu düzeltmenin yolu bunun yerine bir kalender hesaplaması yapmaktır:

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

1
Harika bir açıklama. Kronolojik hesaplama konusunda endişeliyim. years{14700} + 0s + days{0}Bir kod tabanında görürsem , 0sorada ne yaptığını ve ne kadar önemli olduğunu bilmiyordum. Alternatif, belki daha açık bir yol var mı? Daha duration_cast<seconds>(years{14700}) + days{0}iyi bir şey ister misiniz ?
bolov

duration_castdaha da kötü olur çünkü duration_castkesilmeyen dönüşümler için kullanmak kötü bir biçimdir . Dönüşümleri kısaltmak bir mantık hatası kaynağı olabilir ve kodunuzdaki kısaltılmış dönüşümleri kolayca tespit edebilmeniz için yalnızca ihtiyacınız olduğunda "büyük çekici" kullanmak en iyisidir.
Howard Hinnant

1
Biri özel bir süre oluşturabilir: use llyears = duration<long long, years::period>;ve bunun yerine bunu kullanın. Ama muhtemelen en iyi şey neyi başarmaya çalıştığınızı düşünmek ve doğru yolda olup olmadığınızı sormaktır. Örneğin, gerçekten 10 bin yıllık bir zaman diliminde gün hassasiyetine ihtiyacınız var mı? Sivil takvim 4 bin yılda sadece 1 gün için doğrudur. Belki de kayan nokta bin yıl daha iyi bir birim olurdu?
Howard Hinnant

Açıklama: Chrono'nun sivil takvimi modellemesi -32767/1/1 ila 32767/12/31 aralığındadır. Medeni takvimin güneş sistemini modelleme konusundaki doğruluğu 4 bin yılda sadece 1 gündür.
Howard Hinnant

1
Gerçekten kullanım durumuna bağlı olacağını ve şu anda eklemek için motive edici bir kullanım davanın sorun düşünme yaşıyorum yearsve days. Bu, kelimenin tam anlamıyla bazı integral günlere 365.2425 günün katlarını ekliyor. Normalde ay veya yıl sırasıyla kronolojik bir hesaplama yapmak istiyorsanız, bazı fizik veya biyolojiyi modellemektir. Belki de eklemek monthsiçin farklı yollar hakkındaki bu yazı system_clock::time_point, iki hesaplama türü arasındaki farkı netleştirmeye yardımcı olacaktır: stackoverflow.com/a/43018120/576911
Howard Hinnant
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.