mirror of
https://github.com/fmtlib/fmt.git
synced 2025-07-30 10:47:35 +02:00
Merge safe-duration-cast.h into chrono.h
This commit is contained in:
@ -150,8 +150,7 @@ endfunction()
|
|||||||
|
|
||||||
# Define the fmt library, its includes and the needed defines.
|
# Define the fmt library, its includes and the needed defines.
|
||||||
add_headers(FMT_HEADERS chrono.h color.h compile.h core.h format.h format-inl.h
|
add_headers(FMT_HEADERS chrono.h color.h compile.h core.h format.h format-inl.h
|
||||||
locale.h ostream.h posix.h printf.h ranges.h
|
locale.h ostream.h posix.h printf.h ranges.h)
|
||||||
safe-duration-cast.h)
|
|
||||||
set(FMT_SOURCES src/format.cc src/posix.cc)
|
set(FMT_SOURCES src/format.cc src/posix.cc)
|
||||||
|
|
||||||
add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst)
|
add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst)
|
||||||
|
@ -16,16 +16,291 @@
|
|||||||
#include <locale>
|
#include <locale>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
// enable safe chrono durations, unless explicitly disabled
|
FMT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
// Enable safe chrono durations, unless explicitly disabled.
|
||||||
#ifndef FMT_SAFE_DURATION_CAST
|
#ifndef FMT_SAFE_DURATION_CAST
|
||||||
# define FMT_SAFE_DURATION_CAST 1
|
# define FMT_SAFE_DURATION_CAST 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if FMT_SAFE_DURATION_CAST
|
#if FMT_SAFE_DURATION_CAST
|
||||||
# include "safe-duration-cast.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
// 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
|
||||||
|
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 (fmt::internal::is_negative(from)) {
|
||||||
|
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:
|
||||||
|
struct 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) {
|
||||||
|
const auto max1 = internal::max_value<IntermediateRep>() / Factor::num;
|
||||||
|
if (count > max1) {
|
||||||
|
ec = 1;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const 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:
|
||||||
|
struct 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 = internal::max_value<IntermediateRep>() /
|
||||||
|
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
|
||||||
|
#endif
|
||||||
|
|
||||||
// Prevents expansion of a preceding token as a function-style macro.
|
// Prevents expansion of a preceding token as a function-style macro.
|
||||||
// Usage: f FMT_NOMACRO()
|
// Usage: f FMT_NOMACRO()
|
||||||
|
@ -1,293 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 (fmt::internal::is_negative(from)) {
|
|
||||||
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:
|
|
||||||
struct 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) {
|
|
||||||
const auto max1 = internal::max_value<IntermediateRep>() / Factor::num;
|
|
||||||
if (count > max1) {
|
|
||||||
ec = 1;
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
const 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:
|
|
||||||
struct 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 = internal::max_value<IntermediateRep>() /
|
|
||||||
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
|
|
Reference in New Issue
Block a user