Search code examples
c++c++-chrono

Subtraction, float and duration_cast


Let say we have a timestamp in nanoseconds and we need to substract 2.5 seconds from it. So I created code like that:

nanoseconds current_ts{1645546551805673592};                            

float diff = 2.5;
nanoseconds target_ts = duration_cast<nanoseconds>(current_ts - (seconds{1} * diff));

but instead of expected result (1645546549305673592) it returns 1645546556876652544. After some experiments I found out that the problem somehow relates to float type, because if I change it to double it works fine. Also it works fine if I move current_ts outside of duration_cast

Why that happens? Is it related to precision of float?

here is minimal example:

#include <chrono>
#include <iostream>
#include <list>


int main() {
  using namespace std::chrono;


  nanoseconds current_ts{1645546551805673592};
  float context_pre_event_image_sec_ = 2.5;
  nanoseconds target_ts = current_ts - duration_cast<nanoseconds>((seconds{1} * context_pre_event_image_sec_));
  nanoseconds target_ts_2 = duration_cast<nanoseconds>(current_ts - (seconds{1} * context_pre_event_image_sec_));

  std::cout << current_ts.count() << std::endl;
  std::cout << target_ts.count() << std::endl;
  std::cout << target_ts_2.count() << std::endl;
}

second output is expected and third is unexpected


Solution

  • An std::duration object is parameterized by a Period (represented as a fraction respective to seconds) and Rep, an arbitrary numeric type. The operator- of std::duration uses the std::common_type helper template type to deduce both the best type for the Period (intuitively, the greatest common divisor) and Rep.

    For Rep, this rule is used:

    if std::decay<decltype(false ? std::declval<T1>() : std::declval<T2>())>::type is a valid type, the member type [of std::common_type] denotes that type

    In your case T1 and T2 are int64_t and float, respectively. The type of the ternary operator is the result of applying implicit conversion to both types, which is again float.

    And, of course, a float cannot represent the expected result accurately. You can quickly verify this by plugging 1645546549305673592 into this handy IEEE-754 tool.