簡體   English   中英

比率乘法期間chrono :: duration_cast問題

[英]chrono::duration_cast problems during ratio multiplication

我正在編寫一些必須處理NTP時間的代碼。 我決定使用std::chrono::duration來表示它們,希望它可以使我的時間數學更容易實現。

NTP時間由無符號的64位整數表示,從紀元以來的秒數在高32位中,小數秒在低32位中。 紀元日期是1900年1月1日,但這是一個與我在這里處理的問題不同的問題,我已經知道如何處理。 對於我正在使用的示例,假設紀元為1970年1月1日,以簡化數學運算。

持續時間表示很簡單: std::chrono::duration<std::uint64_t, std::ratio<1, INTMAX_C(0x100000000)>> 問題出在我嘗試在NTP時間和系統時鍾之間進行轉換時。

在我的系統上, std::chrono::system_clock::durationstd::chrono::duration<int64_t, std::nano> 當我嘗試使用std::chrono::duration_cast在這兩個持續時間之間進行轉換時,我在任一方向都被截斷。 在調試器中進行跟蹤,我發現它與duration_cast實現有關,該實現在libstdc ++,libc ++和boost :: chrono中以相同的方式失敗。 簡而言之,要從系統轉換為ntp表示,它要乘以8388608/1953125的比率,然后乘以另一個方向的反向比率。 它首先乘以分子,然后除以分母。 數學上是正確的,但是盡管實際轉換(后除法)仍然很容易在這些位中表示出來,但初始乘法會溢出64位表示並被截斷。

我可以手動進行此轉換,但是我的問題如下:

  1. 這是實現中的錯誤還是僅僅是局限性?
  2. 如果有限制,我是否應該意識到這是一個問題? 我沒有看到任何文檔似乎可以猜測這是行不通的。
  3. 有沒有一種通用的方法可以解決這個問題呢?
  4. 應該向誰報告?
  5. 最后,如果在C ++ 20出現時,我使用帶有手動策​​划的to_sysfrom_sys函數的NTP時鍾,我是否能夠使用std::chrono::clock_cast而不用擔心其他細微的問題?

這是我用來測試此代碼的代碼以及一些示例輸出:

// #define BOOST
#ifdef BOOST
#  include <boost/chrono.hpp>
#  define PREFIX boost
#else
#  include <chrono>
#  define PREFIX std
#endif
#include <cstdint>
#include <iostream>

namespace chrono = PREFIX::chrono;
using PREFIX::ratio;
using PREFIX::nano;

using ntp_dur = chrono::duration<uint64_t, ratio<1, INTMAX_C(0x100000000)>>;
using std_dur = chrono::duration<int64_t, nano>;

int main() {
  auto write = [](auto & label, auto & dur) {
    std::cout << label << ": "
              << std::dec << dur.count()
              << std::hex << " (" << dur.count() << ")" << std::endl;
  };

  auto now = chrono::system_clock::now().time_since_epoch();
  write("now", now);
  std::cout << '\n';

  std::cout << "Naive conversion to ntp and back\n";
  auto a = chrono::duration_cast<std_dur>(now);
  write("std", a);
  auto b = chrono::duration_cast<ntp_dur>(a);
  write("std -> ntp", b);
  auto c = chrono::duration_cast<std_dur>(b);
  write("ntp -> std", c);
  std::cout << '\n';

  std::cout << "Broken down conversion to ntp, naive back\n";
  write("std", a);
  auto d = chrono::duration_cast<chrono::seconds>(a);
  write("std -> sec", d);
  auto e = chrono::duration_cast<ntp_dur>(d);
  write("sec -> ntp sec", e);
  auto f = a - d;
  write("std -> std frac", f);
  auto g = chrono::duration_cast<ntp_dur>(f);
  write("std frac -> ntp frac", f);
  auto h = e + g;
  write("ntp sec + ntp frac-> ntp", h);
  auto i = chrono::duration_cast<std_dur>(h);
  write("ntp -> std", i);
  std::cout << '\n';

  std::cout << "Broken down conversion to std from ntp\n";
  write("ntp", h);
  auto j = chrono::duration_cast<chrono::seconds>(h);
  write("ntp -> sec", j);
  auto k = chrono::duration_cast<std_dur>(j);
  write("src -> std sec", j);
  auto l = h - j;
  write("ntp -> ntp frac", l);
  auto m = chrono::duration_cast<std_dur>(l);
  write("ntp frac -> std frac", m);
  auto n = k + m;
  write("std sec + std frac-> std", n);
}

樣本輸出:

now: 1530980834103467738 (153f22f506ab1eda)

Naive conversion to ntp and back
std: 1530980834103467738 (153f22f506ab1eda)
std -> ntp: 4519932809765 (41c60fd5225)
ntp -> std: 1052378865369 (f506ab1ed9)

Broken down conversion to ntp, naive back
std: 1530980834103467738 (153f22f506ab1eda)
std -> sec: 1530980834 (5b40e9e2)
sec -> ntp sec: 6575512612832804864 (5b40e9e200000000)
std -> std frac: 103467738 (62acada)
std frac -> ntp frac: 103467738 (62acada)
ntp sec + ntp frac-> ntp: 6575512613277195414 (5b40e9e21a7cdc96)
ntp -> std: 1052378865369 (f506ab1ed9)

Broken down conversion to std from ntp
ntp: 6575512613277195414 (5b40e9e21a7cdc96)
ntp -> sec: 1530980834 (5b40e9e2)
src -> std sec: 1530980834 (5b40e9e2)
ntp -> ntp frac: 444390550 (1a7cdc96)
ntp frac -> std frac: 103467737 (62acad9)
std sec + std frac-> std: 1530980834103467737 (153f22f506ab1ed9)
  1. 這是實現中的錯誤還是僅僅是局限性?

只是一個限制。 根據規范規范的實現。

  1. 如果有限制,我是否應該意識到這是一個問題? 我沒有看到任何文檔似乎可以猜測這是行不通的。

規范在C ++標准的23.17.5.7 [time.duration.cast]中。 它記錄了轉換算法的行為,如您在問題中所述。

  1. 有沒有一種通用的方法可以解決這個問題呢?

當處理非常好的單位或很大的范圍時,您需要意識到潛在的溢出錯誤。 chrono::duration_cast被設計為最低級別的工具,可盡可能高效地處理最常見的轉換。 chrono::duration_cast盡可能准確,只要有可能,就完全消除除法。

但是,沒有任何一種轉換算法可以始終使用有限的存儲量來為任意轉換提供正確的答案。 C ++ 17引入了三種基於duration_cast新轉換算法,旨在指導存在時截斷的方向:

floor  // truncate towards negative infinity
ceil   // truncate towards positive infinity
round  // truncate towards nearest, to even on tie

您可以編寫自己的通用轉換函數來處理困難的情況,例如您所描述的情況。 這種由客戶提供的轉換不太可能適合一般用途。 例如:

template <class DOut, class Rep, class Period>
DOut
via_double(std::chrono::duration<Rep, Period> d)
{
    using namespace std::chrono;
    using dsec = duration<long double>;
    return duration_cast<DOut>(dsec{d});
}

上面的示例由客戶端提供的轉換從duration<long double>反彈。 這不太容易溢出(盡管不是免疫的),通常計算起來會更昂貴,並且會遇到精度問題(並取決於numeric_limits<long double>::digits )。

對於我( numeric_limits<long double>::digits == 64 ),的輸入1530996658751420125ns往返1530996658751420124ns (由1ns的關閉)。 該算法可以通過使用來改善roundduration_cast (在更多的計算成本再次):

template <class DOut, class Rep, class Period>
DOut
via_double(std::chrono::duration<Rep, Period> d)
{
    using namespace std::chrono;
    using dsec = duration<long double>;
    return round<DOut>(dsec{d});
}

現在,我的往返行程非常適合輸入1530996658751420125ns 但是,如果您的long double精度只具有53位精度,那么即使是round也不能提供完美的往返。

  1. 應該向誰報告?

任何人都可以按照此鏈接上說明針對C ++標准庫的一半提交缺陷報告。 C ++標准委員會的LWG小組委員會將研究此類報告。 它可能已被執行或被聲明為NAD(不是缺陷)。 如果某個問題包含建議的措辭(有關如何更改規范的詳細說明),則成功的機會更大。

即您認為標准應該怎么說?

  1. 最后,如果在C ++ 20出現時,我使用帶有手動策​​划的to_sysfrom_sys函數的NTP時鍾,我是否能夠使用std::chrono::clock_cast而不用擔心其他細微的問題?

一種發現的方法是試驗用於<chrono>擴展的C ++ 20草案規范的現有原型

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM