Skip to content

Commit

Permalink
add safe_duration_cast into fmt
Browse files Browse the repository at this point in the history
  • Loading branch information
pauldreik committed Jun 9, 2019
1 parent 7d5b0ec commit a4d36ea
Show file tree
Hide file tree
Showing 3 changed files with 313 additions and 2 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ endfunction()

# Define the fmt library, its includes and the needed defines.
add_headers(FMT_HEADERS chrono.h color.h core.h format.h format-inl.h locale.h
ostream.h prepare.h printf.h time.h ranges.h)
ostream.h prepare.h printf.h time.h ranges.h safe_duration_cast.h)
set(FMT_SOURCES src/format.cc)
if (HAVE_OPEN)
add_headers(FMT_HEADERS posix.h)
Expand Down
2 changes: 1 addition & 1 deletion include/fmt/chrono.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
#include <sstream>

#ifdef FMT_SAFE_DURATION_CAST
# include <safe_duration_cast/chronoconv.hpp>
# include "safe_duration_cast.h"
#endif

FMT_BEGIN_NAMESPACE
Expand Down
311 changes: 311 additions & 0 deletions include/fmt/safe_duration_cast.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
/*
* For conversion between std::chrono::durations without undefined
* behaviour or erroneous results.
* This is a stripped down version of duration_cast, for inclusion in libfmt.
* 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"

// see
// https://en.cppreference.com/w/User:D41D8CD98F/feature_testing_macros#C.2B.2B17

#if __cpp_constexpr >= 201304
#define SDC_RELAXED_CONSTEXPR constexpr
#else
#define SDC_RELAXED_CONSTEXPR
#endif

FMT_BEGIN_NAMESPACE

namespace safe_duration_cast {

/**
* 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) >
SDC_RELAXED_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)
{
// 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 {};
}
}
}

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 > 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 > 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) >
SDC_RELAXED_CONSTEXPR To
lossless_integral_conversion(const From from, int& ec) {
ec=0;
return from;
} // function

/**
* converts From to To if possible, otherwise ec is set.
*
* input | output
* ---------------------------------|---------------
* NaN | NaN
* Inf | Inf
* normal, fits in output | converted (possibly with loss of precision)
* normal, does not fit in output | ec is set
* subnormal | best effort
* -Inf | -Inf
*/
template<typename To, typename From,
FMT_ENABLE_IF(!std::is_same<From,To>::value) >
SDC_RELAXED_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)>
SDC_RELAXED_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() / Factor::num;
if (count > max1) {
ec = 1;
return {};
}
constexpr auto min1 =
std::numeric_limits<IntermediateRep>::lowest() / 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 = safe_float_conversion<ToRep>(count, ec);
if (ec) {
return {};
}
return To{ tocount };
}

} // namespace safe_duration_cast

FMT_END_NAMESPACE

0 comments on commit a4d36ea

Please sign in to comment.