Search code examples
c++integer-overflowc++-chrono

Transition between std::chrono::seconds and std::chrono::nanoseconds without losing information


Following this thread nanoseconds-run-into-an-overflow, it was my intention to transit from nanoseconds and seconds freely but without losing any information (given the fact that nanoseconds is 64 bits according to std::chrono::nanoseconds). Basically, the following code will lose much information:

std::chrono::nanoseconds t1(1705218518267805576);
std::chrono::seconds t2(std::chrono::duration_cast<std::chrono::seconds>(t1));
std::chrono::nanoseconds t3(std::chrono::duration_cast<std::chrono::nanoseconds>(t2));
EXPECT_EQ(t1,t3);

The last line will return false. So, I thought about adding type such as long_data_seconds which can hold the lost information, but I don't know exactly how much has been lost and how can I store it for future use of transition. I would love to hear ideas.


Solution

  • The issue you are facing is not (really) due to the fact that the std::chrono::seconds type is not wide enough (i.e. does not have sufficient bits) to hold the full data of the nanoseconds value1; rather, it is because that seconds type is an integer. Thus, when you convert nanoseconds to seconds, all information on the fractional part of that value (i.e. data less than 109) is lost, due to truncation.

    In your case, what you will need is to define a floating-point duration type for your intermediate 'seconds' data. For the example you have given (1,705,218,518,267,805,576 ns), the IEEE-754 double type will not be sufficiently precise, so you will need a platform that supports an extended precision long double type, and use that.

    Here's a short example code:

    #include <iostream>
    #include <chrono>
    
    int main()
    {
        using ld_seconds = std::chrono::duration<long double, std::ratio<1>>;
        constexpr std::chrono::nanoseconds t1(1705218518267805576);
        constexpr ld_seconds t2(std::chrono::duration_cast<ld_seconds>(t1));
        constexpr std::chrono::nanoseconds t3(std::chrono::duration_cast<std::chrono::nanoseconds>(t2));
    
        static_assert(t1 == t3, "Not equal!");
        return 0;
    }
    

    And here is a demonstration on Compiler Explorer.

    If you have a platform on which long double is equivalent to double (like MSVC/Windows), then you will need to use a third-party extended-precision library.


    1 In fact, the Standard specifies that std::chrono::seconds have at least 35 bits – and many (most) current platforms don't have an in-built integer type larger than 32 bits but smaller than 64 bits, so it is likely to be an int64_t on those systems.