lmi-commits
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[lmi-commits] [lmi] master 5e36b0a: Add bourn_cast, with unit tests, to


From: Greg Chicares
Subject: [lmi-commits] [lmi] master 5e36b0a: Add bourn_cast, with unit tests, to replace boost::numeric_cast
Date: Sat, 18 Mar 2017 09:35:57 -0400 (EDT)

branch: master
commit 5e36b0a6bc9fed0788b6f74a9896387c8a729cb4
Author: Gregory W. Chicares <address@hidden>
Commit: Gregory W. Chicares <address@hidden>

    Add bourn_cast, with unit tests, to replace boost::numeric_cast
---
 Makefile.am         |   8 ++
 bourn_cast.hpp      |  97 +++++++++++++++
 bourn_cast_test.cpp | 344 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 objects.make        |   6 +
 4 files changed, 455 insertions(+)

diff --git a/Makefile.am b/Makefile.am
index d818266..c43865d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -89,6 +89,7 @@ TESTS = \
     test_any_member \
     test_assert_lmi \
     test_authenticity \
+    test_bourn_cast \
     test_cache_file_reads \
     test_calendar_date \
     test_callback \
@@ -579,6 +580,12 @@ test_authenticity_CXXFLAGS = $(AM_CXXFLAGS)
 test_authenticity_LDADD = \
   $(BOOST_LIBS)
 
+test_bourn_cast_SOURCES = \
+  $(common_test_objects) \
+  bourn_cast_test.cpp \
+  timer.cpp
+test_bourn_cast_CXXFLAGS = $(AM_CXXFLAGS)
+
 test_cache_file_reads_SOURCES = \
   $(common_test_objects) \
   cache_file_reads_test.cpp \
@@ -1084,6 +1091,7 @@ noinst_HEADERS = \
     assert_lmi.hpp \
     authenticity.hpp \
     basic_values.hpp \
+    bourn_cast.hpp \
     cache_file_reads.hpp \
     calendar_date.hpp \
     callback.hpp \
diff --git a/bourn_cast.hpp b/bourn_cast.hpp
new file mode 100644
index 0000000..87b61bb
--- /dev/null
+++ b/bourn_cast.hpp
@@ -0,0 +1,97 @@
+// Numeric stinted cast, across whose bourn no value is returned.
+//
+// Copyright (C) 2017 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// http://savannah.nongnu.org/projects/lmi
+// email: <address@hidden>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+#ifndef bourn_cast_hpp
+#define bourn_cast_hpp
+
+#include "config.hpp"
+
+#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.
+///
+/// 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
+/// 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."
+///
+/// Rewritten by Gregory W. Chicares in 2017. Any defect here should
+/// not reflect on Kevlin Henney's reputation.
+///
+/// Also see:
+///   
https://groups.google.com/forum/#!original/comp.std.c++/WHu6gUiwXkU/ZyV_ejRrXFYJ
+/// which may be an independent redesign.
+
+template<typename To, typename From>
+To bourn_cast(From from)
+{
+#   if defined __GNUC__
+#       pragma GCC diagnostic push
+#       pragma GCC diagnostic ignored "-Wsign-compare"
+#   endif // defined __GNUC__
+    using to_traits   = std::numeric_limits<To>;
+    using from_traits = std::numeric_limits<From>;
+    static_assert(  to_traits::is_specialized, "");
+    static_assert(from_traits::is_specialized, "");
+
+    if(! to_traits::is_signed && from < 0)
+        throw std::runtime_error("Cast would convert 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__
+}
+
+#endif // bourn_cast_hpp
+
diff --git a/bourn_cast_test.cpp b/bourn_cast_test.cpp
new file mode 100644
index 0000000..1732e98
--- /dev/null
+++ b/bourn_cast_test.cpp
@@ -0,0 +1,344 @@
+// Numeric stinted cast, across whose bourn no value is returned--unit test.
+//
+// Copyright (C) 2017 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// http://savannah.nongnu.org/projects/lmi
+// email: <address@hidden>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+#include "pchfile.hpp"
+
+#include "bourn_cast.hpp"
+
+#include "miscellany.hpp"               // stifle_warning_for_unused_variable()
+#include "test_tools.hpp"
+#include "timer.hpp"
+
+#include <climits>                      // LLONG_MIN, SCHAR_MIN, etc.
+#include <type_traits>                  // std::conditional
+
+/// Test trivial casts between identical types.
+
+template<typename T>
+void test_same(char const* file, int line)
+{
+    using limits = std::numeric_limits<T>;
+    T upper = limits::max();
+    T lower = limits::lowest();
+    INVOKE_BOOST_TEST_EQUAL(upper, bourn_cast<T>(upper), file, line);
+    INVOKE_BOOST_TEST_EQUAL(T( 1), bourn_cast<T>(T( 1)), file, line);
+    INVOKE_BOOST_TEST_EQUAL(T( 0), bourn_cast<T>(T( 0)), file, line);
+    INVOKE_BOOST_TEST_EQUAL(lower, bourn_cast<T>(lower), file, line);
+    if(limits::is_signed)
+        {
+        INVOKE_BOOST_TEST_EQUAL(T(-1), bourn_cast<T>(T(-1)), file, line);
+        }
+}
+
+/// Test casts involving two possibly different signednesses.
+
+template<bool SignedFrom, bool SignedTo>
+void test_signednesses(char const* file, int line)
+{
+    using CS = signed char;
+    using IS = signed int;
+    using LS = signed long long int;
+
+    using CU = unsigned char;
+    using IU = unsigned int;
+    using LU = unsigned long long int;
+
+    using CFrom = typename std::conditional<SignedFrom, CS, CU>::type;
+    using IFrom = typename std::conditional<SignedFrom, IS, IU>::type;
+    using LFrom = typename std::conditional<SignedFrom, LS, LU>::type;
+
+    using CTo   = typename std::conditional<SignedTo  , CS, CU>::type;
+    using ITo   = typename std::conditional<SignedTo  , IS, IU>::type;
+    using LTo   = typename std::conditional<SignedTo  , LS, LU>::type;
+
+    // For any pair of corresponding signed and unsigned integral
+    // types, the maximum signed value is interconvertible. Thus,
+    // corresponding elements of these two triplets have the same
+    // value--and indeed the same bit representation--but different
+    // types.
+
+    CFrom CFrom_max = SCHAR_MAX;
+    IFrom IFrom_max = INT_MAX;
+    LFrom LFrom_max = LLONG_MAX;
+
+    CTo   CTo_max   = SCHAR_MAX;
+    ITo   ITo_max   = INT_MAX;
+    LTo   LTo_max   = LLONG_MAX;
+
+    // SCHAR_MAX must be at least 127, so 99 must be representable.
+
+    // Both char.
+    INVOKE_BOOST_TEST_EQUAL(CTo( 0), bourn_cast<CTo>(CFrom( 0)), file, line);
+    INVOKE_BOOST_TEST_EQUAL(CTo( 1), bourn_cast<CTo>(CFrom( 1)), file, line);
+    INVOKE_BOOST_TEST_EQUAL(CTo(99), bourn_cast<CTo>(CFrom(99)), file, line);
+    INVOKE_BOOST_TEST_EQUAL(CTo_max, bourn_cast<CTo>(CFrom_max), file, line);
+
+    // Both int.
+    INVOKE_BOOST_TEST_EQUAL(ITo( 0), bourn_cast<ITo>(IFrom( 0)), file, line);
+    INVOKE_BOOST_TEST_EQUAL(ITo( 1), bourn_cast<ITo>(IFrom( 1)), file, line);
+    INVOKE_BOOST_TEST_EQUAL(ITo(99), bourn_cast<ITo>(IFrom(99)), file, line);
+    INVOKE_BOOST_TEST_EQUAL(ITo_max, bourn_cast<ITo>(IFrom_max), file, line);
+
+    // Both long long.
+    INVOKE_BOOST_TEST_EQUAL(LTo( 0), bourn_cast<LTo>(LFrom( 0)), file, line);
+    INVOKE_BOOST_TEST_EQUAL(LTo( 1), bourn_cast<LTo>(LFrom( 1)), file, line);
+    INVOKE_BOOST_TEST_EQUAL(LTo(99), bourn_cast<LTo>(LFrom(99)), file, line);
+    INVOKE_BOOST_TEST_EQUAL(LTo_max, bourn_cast<LTo>(LFrom_max), file, line);
+
+    // To wider than From.
+    INVOKE_BOOST_TEST_EQUAL(CTo_max, bourn_cast<ITo>(CFrom_max), file, line);
+    INVOKE_BOOST_TEST_EQUAL(CTo_max, bourn_cast<ITo>(CFrom_max), file, line);
+    INVOKE_BOOST_TEST_EQUAL(ITo_max, bourn_cast<LTo>(IFrom_max), file, line);
+
+    // From wider than To.
+    INVOKE_BOOST_TEST_EQUAL(CTo( 0), bourn_cast<CTo>(IFrom( 0)), file, line);
+    INVOKE_BOOST_TEST_EQUAL(CTo( 1), bourn_cast<CTo>(LFrom( 1)), file, line);
+    INVOKE_BOOST_TEST_EQUAL(CTo(99), bourn_cast<CTo>(LFrom(99)), file, line);
+
+    if(!SignedFrom || !SignedTo) return;
+
+    CFrom CFrom_min = SCHAR_MIN;
+    IFrom IFrom_min = INT_MIN;
+    LFrom LFrom_min = LLONG_MIN;
+
+    CTo   CTo_min   = SCHAR_MIN;
+    ITo   ITo_min   = INT_MIN;
+    LTo   LTo_min   = LLONG_MIN;
+
+    // SCHAR_MIN must be <= -127, so -9 must be representable.
+
+    // Both char.
+    INVOKE_BOOST_TEST_EQUAL(CTo(-1), bourn_cast<CTo>(CFrom(-1)), file, line);
+    INVOKE_BOOST_TEST_EQUAL(CTo(-9), bourn_cast<CTo>(CFrom(-9)), file, line);
+    INVOKE_BOOST_TEST_EQUAL(CTo_min, bourn_cast<CTo>(CFrom_min), file, line);
+
+    // Both int.
+    INVOKE_BOOST_TEST_EQUAL(ITo(-1), bourn_cast<ITo>(IFrom(-1)), file, line);
+    INVOKE_BOOST_TEST_EQUAL(ITo(-9), bourn_cast<ITo>(IFrom(-9)), file, line);
+    INVOKE_BOOST_TEST_EQUAL(ITo_min, bourn_cast<ITo>(IFrom_min), file, line);
+
+    // Both long long.
+    INVOKE_BOOST_TEST_EQUAL(LTo(-1), bourn_cast<LTo>(LFrom(-1)), file, line);
+    INVOKE_BOOST_TEST_EQUAL(LTo(-9), bourn_cast<LTo>(LFrom(-9)), file, line);
+    INVOKE_BOOST_TEST_EQUAL(LTo_min, bourn_cast<LTo>(LFrom_min), file, line);
+
+    // To wider than From.
+    INVOKE_BOOST_TEST_EQUAL(CTo_min, bourn_cast<ITo>(CFrom_min), file, line);
+    INVOKE_BOOST_TEST_EQUAL(CTo_min, bourn_cast<ITo>(CFrom_min), file, line);
+    INVOKE_BOOST_TEST_EQUAL(ITo_min, bourn_cast<LTo>(IFrom_min), file, line);
+
+    // From wider than To.
+    INVOKE_BOOST_TEST_EQUAL(CTo(-9), bourn_cast<CTo>(IFrom(-9)), file, line);
+    INVOKE_BOOST_TEST_EQUAL(CTo(-9), bourn_cast<CTo>(LFrom(-9)), file, line);
+    INVOKE_BOOST_TEST_EQUAL(ITo(-9), bourn_cast<ITo>(LFrom(-9)), file, line);
+}
+
+/// Directly assign unsigned to signed a million times, for timing.
+///
+/// One million is guaranteed to be within range, because LONG_MAX
+/// can be no lower than the eighth Mersenne prime, and ULONG_MAX
+/// must be even greater.
+
+void mete_direct()
+{
+    volatile signed long int z = 0;
+    for(unsigned long int j = 0; j < 1000000; ++j)
+        {
+        z = j;
+        }
+    stifle_warning_for_unused_variable(z);
+}
+
+/// Convert signed to unsigned a million times, for timing.
+
+void mete_signed_to_unsigned()
+{
+    volatile unsigned long int z = 0;
+    for(signed long int j = 0; j < 1000000; ++j)
+        {
+        z = bourn_cast<unsigned long int>(j);
+        }
+    stifle_warning_for_unused_variable(z);
+}
+
+/// Convert unsigned to signed a million times, for timing.
+
+void mete_unsigned_to_signed()
+{
+    volatile signed long int z = 0;
+    for(unsigned long int j = 0; j < 1000000; ++j)
+        {
+        z = bourn_cast<signed long int>(j);
+        }
+    stifle_warning_for_unused_variable(z);
+}
+
+void assay_speed()
+{
+    std::cout
+        << "\n  Speed tests..."
+        << "\n  direct: " << TimeAnAliquot(mete_direct)
+        << "\n  S to U: " << TimeAnAliquot(mete_signed_to_unsigned)
+        << "\n  U to S: " << TimeAnAliquot(mete_unsigned_to_signed)
+        << std::endl
+        ;
+}
+
+int test_main(int, char*[])
+{
+    // Motivating case. This test fails: -1 really isn't less than 1U.
+    // (The test is suppressed to avoid a compiler warning.)
+//  BOOST_TEST(-1 < 1U);
+    // This test succeeds: -1 is less than 1, as expected.
+    BOOST_TEST(-1 < bourn_cast<int>(1U));
+    // This test throws: instead of converting a negative value to
+    // unsigned, bourn_cast throws an exception.
+//  BOOST_TEST(bourn_cast<unsigned int>(-1) < 1U);
+
+    // Trivially cast to same type.
+
+    test_same<bool>                  (__FILE__, __LINE__);
+    test_same<char>                  (__FILE__, __LINE__);
+    test_same<signed char>           (__FILE__, __LINE__);
+    test_same<unsigned char>         (__FILE__, __LINE__);
+    test_same<char16_t>              (__FILE__, __LINE__);
+    test_same<char32_t>              (__FILE__, __LINE__);
+    test_same<wchar_t>               (__FILE__, __LINE__);
+    test_same<short int>             (__FILE__, __LINE__);
+    test_same<int>                   (__FILE__, __LINE__);
+    test_same<long int>              (__FILE__, __LINE__);
+    test_same<long long int>         (__FILE__, __LINE__);
+    test_same<unsigned short int>    (__FILE__, __LINE__);
+    test_same<unsigned int>          (__FILE__, __LINE__);
+    test_same<unsigned long int>     (__FILE__, __LINE__);
+    test_same<unsigned long long int>(__FILE__, __LINE__);
+    test_same<float>                 (__FILE__, __LINE__);
+    test_same<double>                (__FILE__, __LINE__);
+    test_same<long double>           (__FILE__, __LINE__);
+
+    // Cast between bool and int. C++11 [18.3.2.7/3] specifies that
+    // std::numeric_limits<bool>is_signed is false, so the types
+    // {bool, signed char} must have opposite signedness and different
+    // [lowest(), max()] ranges. Therefore, the tests in this block
+    // are guaranteed to cover such diversity, even on a machine where
+    // unsigned char and unsigned long long int are synonyms.
+
+    BOOST_TEST_EQUAL(true , bourn_cast<bool>((signed char)(1)));
+    BOOST_TEST_EQUAL(false, bourn_cast<bool>((signed char)(0)));
+
+    BOOST_TEST_THROW
+        (bourn_cast<bool>((signed char)(2))
+        ,std::runtime_error
+        ,"Cast would transgress upper limit."
+        );
+
+    BOOST_TEST_THROW
+        (bourn_cast<bool>((signed char)(-1))
+        ,std::runtime_error
+        ,"Cast would convert negative to unsigned."
+        );
+
+    // Cast from signed to unsigned.
+
+    test_signednesses<true,false>(__FILE__, __LINE__);
+
+    // Cast from unsigned to signed.
+
+    test_signednesses<false,true>(__FILE__, __LINE__);
+
+    // Cast from signed to signed.
+
+    test_signednesses<true,true>(__FILE__, __LINE__);
+
+    // Cast from unsigned to unsigned.
+
+    test_signednesses<false,false>(__FILE__, __LINE__);
+
+    // Attempt forbidden conversion from negative to unsigned.
+
+    BOOST_TEST_THROW
+        (bourn_cast<unsigned char>(std::numeric_limits<signed char>::lowest())
+        ,std::runtime_error
+        ,"Cast would convert negative to unsigned."
+        );
+
+    BOOST_TEST_THROW
+        (bourn_cast<unsigned int >(std::numeric_limits<signed int >::lowest())
+        ,std::runtime_error
+        ,"Cast would convert negative to unsigned."
+        );
+
+    // Still forbidden even if unsigned type is wider.
+    BOOST_TEST_THROW
+        (bourn_cast<unsigned long>(std::numeric_limits<signed char>::lowest())
+        ,std::runtime_error
+        ,"Cast would convert negative to unsigned."
+        );
+
+    // Still forbidden even if value is only "slightly" negative.
+    BOOST_TEST_THROW
+        (bourn_cast<unsigned long>(-1)
+        ,std::runtime_error
+        ,"Cast would convert negative to unsigned."
+        );
+
+    // Transgress lower limit. It is not possible to write a unit test
+    // that is guaranteed to throw this particular exception, because
+    // the present bourn_cast<>() implementation tests first for
+    // attempted conversion of a negative value to an unsigned type.
+
+#if LLONG_MIN < SCHAR_MIN
+    BOOST_TEST_THROW
+        (bourn_cast<signed char>(LLONG_MIN)
+        ,std::runtime_error
+        ,"Cast would transgress lower limit."
+        );
+#endif // LLONG_MIN < SCHAR_MIN
+
+    // Transgress upper limit.
+
+#if UCHAR_MAX < ULLONG_MAX
+    BOOST_TEST_THROW
+        (bourn_cast<unsigned char>(ULLONG_MAX)
+        ,std::runtime_error
+        ,"Cast would transgress upper limit."
+        );
+#endif // UCHAR_MAX < ULLONG_MAX
+
+    BOOST_TEST_THROW
+        (bourn_cast<signed char>(std::numeric_limits<unsigned char>::max())
+        ,std::runtime_error
+        ,"Cast would transgress upper limit."
+        );
+
+    BOOST_TEST_THROW
+        (bourn_cast<signed int >(std::numeric_limits<unsigned int >::max())
+        ,std::runtime_error
+        ,"Cast would transgress upper limit."
+        );
+
+    // Time representative casts.
+
+    assay_speed();
+
+    return EXIT_SUCCESS;
+}
+
diff --git a/objects.make b/objects.make
index 9eaa9ff..6e2105e 100644
--- a/objects.make
+++ b/objects.make
@@ -400,6 +400,7 @@ unit_test_targets := \
   any_member_test \
   assert_lmi_test \
   authenticity_test \
+  bourn_cast_test \
   cache_file_reads_test \
   calendar_date_test \
   callback_test \
@@ -517,6 +518,11 @@ authenticity_test$(EXEEXT): \
   system_command.o \
   system_command_non_wx.o \
 
+bourn_cast_test$(EXEEXT): \
+  $(common_test_objects) \
+  bourn_cast_test.o \
+  timer.o \
+
 cache_file_reads_test$(EXEEXT): \
   $(boost_filesystem_objects) \
   $(common_test_objects) \



reply via email to

[Prev in Thread] Current Thread [Next in Thread]