I'm currently rounding a std::chrono::duration<float>
to std::chrono::duration<uint32_t>
, and when I give it out-of-range values it rounds to 1s
instead of 4294967295s
.
Looking at the standard, it says
template <class ToDuration, class Rep, class Period> constexpr ToDuration round(const duration<Rep, Period>& d);
[...]
Returns: The value t representable inToDuration
that is the closest tod
. [...]
Here is my exact code:
#include <chrono>
#include <cstdio>
#include <limits>
#include <cstdint>
int main()
{
std::chrono::duration<float> seconds{std::numeric_limits<float>::max()};
printf("float: %f\n", seconds.count());
printf("uint32_t: %u\n", std::chrono::round<std::chrono::duration<uint32_t>>(seconds).count());
printf(" int32_t: %d\n", std::chrono::round<std::chrono::duration<int32_t>>(seconds).count());
printf("uint64_t: %lu\n", std::chrono::round<std::chrono::duration<uint64_t>>(seconds).count());
printf(" int64_t: %ld\n", std::chrono::round<std::chrono::duration<int64_t>>(seconds).count());
}
which outputs
float: 340282346638528859811704183484516925440.000000
uint32_t: 1
int32_t: -2147483647
uint64_t: 9223372036854775809
int64_t: -9223372036854775807
As you can see, other integer types also behave strangely. Unlike std::lround
et al., std::chrono::round
doesn't say anything about being undefined if the floating-point input is out of range.
Am I missing anything?
(For context, I compiled this with clang
version 14.0.0-1ubuntu1.1
on x86_64, but I first noticed the issue on an ARMv7 system using gcc
.)
duration<Rep, Ratio>
is a simple, thin wrapper around Rep
. In your example Rep
is float
in the argument to round
and uint32_t
in the result.
As a thin wrapper, duration
does not alter the fundamental behavior of the Rep
for things like overflow and conversion. To do so would add overhead when it is often not desirable.
If specific behavior such as overflow checking is desired, chrono
facilitates that by allowing duration<safe_int>
where safe_int
is a hypothetical class type that emulates integral arithmetic but checks for overflow. Real world examples of such libraries exist, and do not require special adaptation to be used with chrono
.
For just float
and uint32_t
as asked about in this question, the undefined behavior results at the float
and uint32_t
and level, not at the chrono
level. Specifically, when converting float
to uint32_t
, the behavior is undefined if the truncated value in the float
can not be represented in the uint32_t
.
There is an open question as to whether the standard actually says this or not as discussed in the comments below. To pursue this further, an interested party should submit an LWG issue:
http://cplusplus.github.io/LWG/lwg-active.html#submit_issue
And then the LWG will decide if there is a bug, and if so, how best to remedy it.
Pertinent links: