|
- /*
- * For conversion between std::chrono::durations without undefined
- * behaviour or erroneous results.
- * This is a stripped down version of duration_cast, for inclusion in fmt.
- * See https://github.com/pauldreik/safe_duration_cast
- *
- * Copyright Paul Dreik 2019
- *
- * This file is licensed under the fmt license, see format.h
- */
-
- #include <chrono>
- #include <cmath>
- #include <limits>
- #include <type_traits>
-
- #include "format.h"
-
- FMT_BEGIN_NAMESPACE
-
- namespace safe_duration_cast {
-
- template <typename To, typename From,
- FMT_ENABLE_IF(!std::is_same<From, To>::value &&
- std::numeric_limits<From>::is_signed ==
- std::numeric_limits<To>::is_signed)>
- FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
- ec = 0;
- using F = std::numeric_limits<From>;
- using T = std::numeric_limits<To>;
- static_assert(F::is_integer, "From must be integral");
- static_assert(T::is_integer, "To must be integral");
-
- // A and B are both signed, or both unsigned.
- if (F::digits <= T::digits) {
- // From fits in To without any problem.
- } else {
- // From does not always fit in To, resort to a dynamic check.
- if (from < T::min() || from > T::max()) {
- // outside range.
- ec = 1;
- return {};
- }
- }
- return static_cast<To>(from);
- }
-
- /**
- * converts From to To, without loss. If the dynamic value of from
- * can't be converted to To without loss, ec is set.
- */
- template <typename To, typename From,
- FMT_ENABLE_IF(!std::is_same<From, To>::value &&
- std::numeric_limits<From>::is_signed !=
- std::numeric_limits<To>::is_signed)>
- FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
- ec = 0;
- using F = std::numeric_limits<From>;
- using T = std::numeric_limits<To>;
- static_assert(F::is_integer, "From must be integral");
- static_assert(T::is_integer, "To must be integral");
-
- if (F::is_signed && !T::is_signed) {
- // From may be negative, not allowed!
- if (from < 0) {
- ec = 1;
- return {};
- }
-
- // From is positive. Can it always fit in To?
- if (F::digits <= T::digits) {
- // yes, From always fits in To.
- } else {
- // from may not fit in To, we have to do a dynamic check
- if (from > static_cast<From>(T::max())) {
- ec = 1;
- return {};
- }
- }
- }
-
- if (!F::is_signed && T::is_signed) {
- // can from be held in To?
- if (F::digits < T::digits) {
- // yes, From always fits in To.
- } else {
- // from may not fit in To, we have to do a dynamic check
- if (from > static_cast<From>(T::max())) {
- // outside range.
- ec = 1;
- return {};
- }
- }
- }
-
- // reaching here means all is ok for lossless conversion.
- return static_cast<To>(from);
-
- } // function
-
- template <typename To, typename From,
- FMT_ENABLE_IF(std::is_same<From, To>::value)>
- FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
- ec = 0;
- return from;
- } // function
-
- // clang-format off
- /**
- * converts From to To if possible, otherwise ec is set.
- *
- * input | output
- * ---------------------------------|---------------
- * NaN | NaN
- * Inf | Inf
- * normal, fits in output | converted (possibly lossy)
- * normal, does not fit in output | ec is set
- * subnormal | best effort
- * -Inf | -Inf
- */
- // clang-format on
- template <typename To, typename From,
- FMT_ENABLE_IF(!std::is_same<From, To>::value)>
- FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
- ec = 0;
- using T = std::numeric_limits<To>;
- static_assert(std::is_floating_point<From>::value, "From must be floating");
- static_assert(std::is_floating_point<To>::value, "To must be floating");
-
- // catch the only happy case
- if (std::isfinite(from)) {
- if (from >= T::lowest() && from <= T::max()) {
- return static_cast<To>(from);
- }
- // not within range.
- ec = 1;
- return {};
- }
-
- // nan and inf will be preserved
- return static_cast<To>(from);
- } // function
-
- template <typename To, typename From,
- FMT_ENABLE_IF(std::is_same<From, To>::value)>
- FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
- ec = 0;
- static_assert(std::is_floating_point<From>::value, "From must be floating");
- return from;
- }
-
- /**
- * safe duration cast between integral durations
- */
- template <typename To, typename FromRep, typename FromPeriod,
- FMT_ENABLE_IF(std::is_integral<FromRep>::value),
- FMT_ENABLE_IF(std::is_integral<typename To::rep>::value)>
- To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
- int& ec) {
- using From = std::chrono::duration<FromRep, FromPeriod>;
- ec = 0;
- // the basic idea is that we need to convert from count() in the from type
- // to count() in the To type, by multiplying it with this:
- using Factor = std::ratio_divide<typename From::period, typename To::period>;
-
- static_assert(Factor::num > 0, "num must be positive");
- static_assert(Factor::den > 0, "den must be positive");
-
- // the conversion is like this: multiply from.count() with Factor::num
- // /Factor::den and convert it to To::rep, all this without
- // overflow/underflow. let's start by finding a suitable type that can hold
- // both To, From and Factor::num
- using IntermediateRep =
- typename std::common_type<typename From::rep, typename To::rep,
- decltype(Factor::num)>::type;
-
- // safe conversion to IntermediateRep
- IntermediateRep count =
- lossless_integral_conversion<IntermediateRep>(from.count(), ec);
- if (ec) {
- return {};
- }
- // multiply with Factor::num without overflow or underflow
- if (Factor::num != 1) {
- constexpr auto max1 =
- std::numeric_limits<IntermediateRep>::max() / Factor::num;
- if (count > max1) {
- ec = 1;
- return {};
- }
- constexpr auto min1 =
- std::numeric_limits<IntermediateRep>::min() / Factor::num;
- if (count < min1) {
- ec = 1;
- return {};
- }
- count *= Factor::num;
- }
-
- // this can't go wrong, right? den>0 is checked earlier.
- if (Factor::den != 1) {
- count /= Factor::den;
- }
- // convert to the to type, safely
- using ToRep = typename To::rep;
- const ToRep tocount = lossless_integral_conversion<ToRep>(count, ec);
- if (ec) {
- return {};
- }
- return To{tocount};
- }
-
- /**
- * safe duration_cast between floating point durations
- */
- template <typename To, typename FromRep, typename FromPeriod,
- FMT_ENABLE_IF(std::is_floating_point<FromRep>::value),
- FMT_ENABLE_IF(std::is_floating_point<typename To::rep>::value)>
- To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
- int& ec) {
- using From = std::chrono::duration<FromRep, FromPeriod>;
- ec = 0;
- if (std::isnan(from.count())) {
- // nan in, gives nan out. easy.
- return To{std::numeric_limits<typename To::rep>::quiet_NaN()};
- }
- // maybe we should also check if from is denormal, and decide what to do about
- // it.
-
- // +-inf should be preserved.
- if (std::isinf(from.count())) {
- return To{from.count()};
- }
-
- // the basic idea is that we need to convert from count() in the from type
- // to count() in the To type, by multiplying it with this:
- using Factor = std::ratio_divide<typename From::period, typename To::period>;
-
- static_assert(Factor::num > 0, "num must be positive");
- static_assert(Factor::den > 0, "den must be positive");
-
- // the conversion is like this: multiply from.count() with Factor::num
- // /Factor::den and convert it to To::rep, all this without
- // overflow/underflow. let's start by finding a suitable type that can hold
- // both To, From and Factor::num
- using IntermediateRep =
- typename std::common_type<typename From::rep, typename To::rep,
- decltype(Factor::num)>::type;
-
- // force conversion of From::rep -> IntermediateRep to be safe,
- // even if it will never happen be narrowing in this context.
- IntermediateRep count =
- safe_float_conversion<IntermediateRep>(from.count(), ec);
- if (ec) {
- return {};
- }
-
- // multiply with Factor::num without overflow or underflow
- if (Factor::num != 1) {
- constexpr auto max1 = std::numeric_limits<IntermediateRep>::max() /
- static_cast<IntermediateRep>(Factor::num);
- if (count > max1) {
- ec = 1;
- return {};
- }
- constexpr auto min1 = std::numeric_limits<IntermediateRep>::lowest() /
- static_cast<IntermediateRep>(Factor::num);
- if (count < min1) {
- ec = 1;
- return {};
- }
- count *= static_cast<IntermediateRep>(Factor::num);
- }
-
- // this can't go wrong, right? den>0 is checked earlier.
- if (Factor::den != 1) {
- using common_t = typename std::common_type<IntermediateRep, intmax_t>::type;
- count /= static_cast<common_t>(Factor::den);
- }
-
- // convert to the to type, safely
- using ToRep = typename To::rep;
-
- const ToRep tocount = safe_float_conversion<ToRep>(count, ec);
- if (ec) {
- return {};
- }
- return To{tocount};
- }
-
- } // namespace safe_duration_cast
-
- FMT_END_NAMESPACE
|