23.17.5.7 duration_cast case 2.4 CF::num != 1 and CF::den != 1

24 views
Skip to first unread message

maybe.not...@gmail.com

unread,
Jun 11, 2017, 12:47:01 AM6/11/17
to ISO C++ Standard - Discussion
Hello,

I imagine this has been discussed but cannot find any traces of it.

The standard mandates that duration_cast returns
    ToDuration(static_cast<typename ToDuration::rep>(static_cast<CR>(d.count()) * static_cast<CR>(CF::num) / static_cast<CR>(CF::den)))
when the conversion ration has non 1 denominator and numerator (case 2.4 of 23.17.5.7).

For integer representations this has a good probability of int overflow even when the result is within the representation range.
One example:

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

//fixed point 32 bit seconds 32 bit second fractions 
using D = duration<int64_t, ratio<1, 1L<<32 > >;
seconds s{1490000000}; //fits in 32 bit int

cout << duration_cast<D>(s).count() << '\n';
cout << duration_cast<D>(milliseconds{s}).count() << '\n';
cout << duration_cast<D>(microseconds{s}).count() << '\n';
cout << duration_cast<D>(nanoseconds{s}).count() << '\n';
}
outputs :
6399501271040000000
53821309683914244
-485904869086675
-4223487833972

and only the first result is correct

an alternative for int representation would be to request case 2.4 to do 

constexpr auto q = static_cast<CR>(CF::num) / static_cast<CR>(CF::den); //may be compile time 0
constexpr auto r = static_cast<CR>(CF::num) % static_cast<CR>(CF::den);
return  ToDuration( static_cast<TR>( static_cast<CR>(d.count()) * q + 
                                             static_cast<CR>(d.count()) / static_cast<CR>(CF::den) * r + 
                                             static_cast<CR>(s.count()) % static_cast<CR>(CF::den) * r / static_cast<CR>(CF::den)));

If the end result can be represented with CR this cannot overflow if static_cast<CR>(CF::num) % static_cast<CR>(CF::den) * (static_cast<CR>(CF::num) % static_cast<CR>(CF::den) - 1)
does not overflow.


using namespace std;
using namespace std::chrono;

template<typename ToDuration, typename SR, typename SP>
constexpr ToDuration example_cast(const duration<SR, SP>& d) {
typedef typename ToDuration::period TP;
typedef typename ToDuration::rep TR;
typedef ratio_divide<SP, TP> CF;
typedef typename common_type<TR, SR, intmax_t>::type CR;

constexpr auto q = static_cast<CR>(CF::num) / static_cast<CR>(CF::den);
constexpr auto r = static_cast<CR>(CF::num) % static_cast<CR>(CF::den);
return  ToDuration( static_cast<TR>( static_cast<CR>(d.count()) * q + 
                                             static_cast<CR>(d.count()) / static_cast<CR>(CF::den) * r + 
                                             static_cast<CR>(d.count()) % static_cast<CR>(CF::den) * r / static_cast<CR>(CF::den)));
}

int main(){

using D = duration<int64_t, ratio<1, 1ull<<32 > >;
seconds since_epoch{1490000000};

cout << example_cast<D>(since_epoch).count() << '\n';
cout << example_cast<D>(milliseconds{since_epoch}).count() << '\n';
cout << example_cast<D>(microseconds{since_epoch}).count() << '\n';
cout << example_cast<D>(nanoseconds{since_epoch}).count() << '\n';
cout << example_cast<seconds>(example_cast<D>(since_epoch)).count() << '\n';
cout << example_cast<milliseconds>(example_cast<D>(milliseconds{since_epoch})).count() << '\n';
cout << example_cast<microseconds>(example_cast<D>(microseconds{since_epoch})).count() << '\n';
cout << example_cast<nanoseconds>(example_cast<D>(nanoseconds{since_epoch})).count() << '\n';
}

outputs:
6399501271040000000
6399501271040000000
6399501271040000000
6399501271040000000
1490000000
1490000000000
1490000000000000
1490000000000000000


Is this a valid issue with the duration_cast specification?

Reply all
Reply to author
Forward
0 new messages