[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[lmi-commits] [lmi] master ce9e251 4/4: Extend bourn_cast to all arithme
From: |
Greg Chicares |
Subject: |
[lmi-commits] [lmi] master ce9e251 4/4: Extend bourn_cast to all arithmetic types |
Date: |
Fri, 31 Mar 2017 10:05:38 -0400 (EDT) |
branch: master
commit ce9e251d912e660387c8382a99a33152d1156e80
Author: Gregory W. Chicares <address@hidden>
Commit: Gregory W. Chicares <address@hidden>
Extend bourn_cast to all arithmetic types
Given this major enhancement, it was no longer appropriate to
characterize bourn_cast as a derived work simply because one of the
four conversions it now performs uses the idea Henney expressed in
this single statement on his website:
return !(arg < 0 && !result_traits::is_signed) &&
!(arg_traits::is_signed && arg < result_traits::min()) &&
!(arg > result_traits::max());
although credit is of course still given for that fine work.
Unit tests will follow soon.
---
bourn_cast.hpp | 150 ++++++++++++++++++++++++++++++++++++++++++---------------
1 file changed, 110 insertions(+), 40 deletions(-)
diff --git a/bourn_cast.hpp b/bourn_cast.hpp
index 919de4c..358952f 100644
--- a/bourn_cast.hpp
+++ b/bourn_cast.hpp
@@ -24,50 +24,46 @@
#include "config.hpp"
+#include <cmath> // isinf(), isnan(), signbit()
#include <limits>
#include <stdexcept>
/// Numeric stinted cast, across whose bourn no value is returned.
///
/// Perform a static_cast between numeric types, but throw if the
-/// value is out of range. For example:
-/// bourn_cast<unsigned int>( 1); // Returns 1U.
-/// bourn_cast<unsigned int>(-1); // Throws.
-///
-/// Motivation: To convert between integral types that may differ in
-/// size and signedness, iff the value is between the maximum and
-/// minimum values permitted for the target (From) type. Because of
-/// the properties of integers, conversion between integral types
-/// either preserves the notional value, or throws.
+/// value cannot be preserved. For example:
+/// bourn_cast<unsigned int>( 1); // Returns 1U.
+/// bourn_cast<unsigned int>(-1); // Throws.
+/// bourn_cast<bool>(INT_MAX); // Throws: out of range.
+/// bourn_cast<float>((double)INFINITY); // Returns infinity.
+/// bourn_cast<int> ((double)INFINITY); // Throws.
+/// bourn_cast<unsigned int>(3.0); // Returns 3U.
+/// bourn_cast<unsigned int>(3.14); // Throws: 3.14 != 3.0U.
///
/// Both From and To must be types for which std::numeric_limits is
-/// specialized. Use with floating-point types is neither forbidden
-/// nor encouraged. Integral-to-floating conversion is highly unlikely
+/// specialized. Integral-to-floating conversion is highly unlikely
/// to exceed bounds, but may lose precision. Floating-to-integral
-/// conversion is extremely unlikely to preserve value, so a rounding
-/// facility is generally preferable. No special attention is given to
-/// exotic values such as infinities, NaNs, or negative zero. For now,
-/// bourn_cast<>() is intended as a simple replacement for the heavier
-/// "improved" boost::numeric_cast<>(), but in the future floating-
-/// point types may be forbidden.
-///
-/// This is a derived work based on Kevlin Henney's numeric_cast,
-/// which is presented on his site without any copyright notice:
-///
http://www.two-sdg.demon.co.uk/curbralan/code/numeric_cast/numeric_cast.hpp
-/// and also as part of boost, with the following notice:
-/// (C) Copyright Kevlin Henney and Dave Abrahams 1999.
-/// Distributed under the Boost Software License, Version 1.0.
-/// According to
-/// http://www.gnu.org/philosophy/license-list.html
-/// "This is a simple, permissive non-copyleft free software
-/// license, compatible with the GNU GPL."
+/// conversion is extremely unlikely to preserve value, in which case
+/// an exception is thrown; but bourn_cast is appropriate for casting
+/// an already-rounded integer-valued floating value to another type.
///
-/// Rewritten by Gregory W. Chicares in 2017. Any defect here should
-/// not reflect on Kevlin Henney's reputation.
+/// bourn_cast<>() is intended as a simple and correct replacement for
+/// boost::numeric_cast<>(), which does the wrong thing in some cases:
+/// http://lists.nongnu.org/archive/html/lmi/2017-03/msg00127.html
+/// http://lists.nongnu.org/archive/html/lmi/2017-03/msg00128.html
+/// It behaves the same way as boost::numeric_cast<>() except that,
+/// instead of quietly truncating, it throws on floating-to-integral
+/// conversions that would not preserve value.
///
-/// Also see:
-///
https://groups.google.com/forum/#!original/comp.std.c++/WHu6gUiwXkU/ZyV_ejRrXFYJ
-/// which may be an independent redesign.
+/// Facilities provided by <limits> are used to the exclusion of
+/// <type_traits> functions such as
+/// is_arithmetic()
+/// is_floating_point()
+/// is_integral()
+/// is_signed()
+/// is_unsigned()
+/// so that UDTs with std::numeric_limits specializations can work
+/// as expected.
template<typename To, typename From>
#if 201402L < __cplusplus
@@ -80,6 +76,9 @@ inline To bourn_cast(From from)
static_assert( to_traits::is_specialized, "");
static_assert(from_traits::is_specialized, "");
+ static_assert( to_traits::is_integer || to_traits::is_iec559, "");
+ static_assert(from_traits::is_integer || from_traits::is_iec559, "");
+
#if defined __GNUC__
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wsign-compare"
@@ -87,13 +86,84 @@ inline To bourn_cast(From from)
# pragma GCC diagnostic ignored "-Wbool-compare"
# endif // 5 <= __GNUC__
#endif // defined __GNUC__
- if(! to_traits::is_signed && from < 0)
- throw std::runtime_error("Cannot cast negative to unsigned.");
- if(from_traits::is_signed && from < to_traits::lowest())
- throw std::runtime_error("Cast would transgress lower limit.");
- if(to_traits::max() < from)
- throw std::runtime_error("Cast would transgress upper limit.");
- return static_cast<To>(from);
+
+ // Floating to floating.
+ //
+ // Handle special cases first:
+ // - infinities are interconvertible: no exception wanted;
+ // - C++11 [4.8/1] doesn't require static_cast to DTRT for NaNs.
+ if(!to_traits::is_integer && !from_traits::is_integer)
+ {
+ if(std::isnan(from))
+ return to_traits::quiet_NaN();
+ if(std::isinf(from))
+ return
+ std::signbit(from)
+ ? -to_traits::infinity()
+ : to_traits::infinity()
+ ;
+ if(from < to_traits::lowest())
+ throw std::runtime_error("Cast would transgress lower limit.");
+ if(to_traits::max() < from)
+ throw std::runtime_error("Cast would transgress upper limit.");
+ return static_cast<To>(from);
+ }
+
+ // Integral to floating.
+ if(!to_traits::is_integer && from_traits::is_integer)
+ {
+ if(from < to_traits::lowest())
+ throw std::runtime_error("Cast would transgress lower limit.");
+ if(to_traits::max() < from)
+ throw std::runtime_error("Cast would transgress upper limit.");
+ return static_cast<To>(from);
+ }
+
+ // Floating to integral.
+ //
+ // Assume integral types have a two's complement representation.
+ // Ones' complement might be handled thus [untested]:
+ // - if(from < to_traits::lowest())
+ // + if(from <= From(to_traits::lowest()) - 1)
+ if(to_traits::is_integer && !from_traits::is_integer)
+ {
+ if(std::isnan(from))
+ throw std::runtime_error("Cannot cast NaN to integral.");
+ if(from < to_traits::lowest())
+ throw std::runtime_error("Cast would transgress lower limit.");
+ if(From(to_traits::max()) + 1 <= from)
+ throw std::runtime_error("Cast would transgress upper limit.");
+ To const r = static_cast<To>(from);
+ if(r != from)
+ throw std::runtime_error("Cast would not preserve value.");
+ return r;
+ }
+
+ // Integral to integral.
+ //
+ // Converts between integral types that may differ in size and
+ // signedness, iff the value is between the maximum and minimum
+ // values permitted for the target (To) type. Because of the
+ // properties of integers, conversion between integral types
+ // either preserves the notional value, or throws.
+ //
+ // The underlying idea is discussed here:
+ //
https://groups.google.com/forum/#!original/comp.std.c++/WHu6gUiwXkU/ZyV_ejRrXFYJ
+ // and here:
+ //
http://www.two-sdg.demon.co.uk/curbralan/code/numeric_cast/numeric_cast.hpp
+ // and embodied in Kevlin Henney's original boost:numeric_cast,
+ // distributed under the GPL-compatible Boost Software License.
+ if(to_traits::is_integer && from_traits::is_integer)
+ {
+ if(! to_traits::is_signed && from < 0)
+ throw std::runtime_error("Cannot cast negative to unsigned.");
+ if(from_traits::is_signed && from < to_traits::lowest())
+ throw std::runtime_error("Cast would transgress lower limit.");
+ if(to_traits::max() < from)
+ throw std::runtime_error("Cast would transgress upper limit.");
+ return static_cast<To>(from);
+ }
+
#if defined __GNUC__
# pragma GCC diagnostic pop
#endif // defined __GNUC__