// Copyright (c) 2017 Steven Watanabe // // Distributed under the Boost Software License, Version 1.0. // See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt #include "../include/safe_integer.hpp" #include "../include/automatic.hpp" #include #include #include #include #include #include #include #define BOOST_TEST_MAIN #include // We need et_off because we're using auto everywhere. // et_on (which is the default for cpp_int) causes // dangling references. using cpp_int = boost::multiprecision::number, boost::multiprecision::et_off>; // returns true if x is in the range of T // works for both safe and raw types. template bool in_range(const cpp_int& x) { using boost::numeric::base_value; return base_value((std::numeric_limits::min)()) <= x && x <= base_value((std::numeric_limits::max)()); } template bool in_range(const U& u) { return in_range(cpp_int(boost::numeric::base_value(u))); } // Treat chars as ints for printing template auto as_integer(T t) { return t; } auto as_integer(char ch) { return (int)ch; } auto as_integer(signed char ch) { return (int)ch; } auto as_integer(unsigned char ch) { return (int)ch; } // Test a single operator call. // At least one of T and U should be safe template void test_binop_impl(const T& t, const U& u, F f, const cpp_int& expected, const std::string& op) { using boost::numeric::base_value; using boost::str; using boost::format; using boost::core::demangle; BOOST_TEST_CONTEXT(str( format("%d [%s] %s %d [%s] -> [%s]") % as_integer(base_value(t)) % demangle(typeid(T).name()) % op % as_integer(base_value(u)) % demangle(typeid(U).name()) % demangle(typeid(decltype(f(t, u))).name()) ) ) { if (in_range(expected) // I don't agree that this is correct, // but it matches the current implementation && in_range(t) && (in_range(u) || // The behavior for division with automatic // is even more confusing. (op == "/" && std::is_same< typename boost::numeric::get_promotion_policy::type, boost::numeric::automatic>::value))) { try { BOOST_TEST(base_value(f(t, u)) == expected); } catch(std::exception&) { BOOST_ERROR("Unexpected exception"); } } else { BOOST_CHECK_THROW(f(t, u), std::exception); } } } // Test a binary operator with every combination of // - safe op raw // - raw op safe // - safe op safe template class S, class T, class U, class F> void test_binop(const T& t, const U& u, F f, const std::string& op) { if (!in_range>(t) || !in_range>(u)) return; S t1(t); S u1(u); cpp_int expected; // catch division by zero try { expected = (f(cpp_int(t), cpp_int(u))); } catch(std::exception&) { BOOST_CHECK_THROW(f(t, u1), std::exception); BOOST_CHECK_THROW(f(t1, u), std::exception); BOOST_CHECK_THROW(f(t1, u1), std::exception); return; } test_binop_impl(t, u1, f, expected, op); test_binop_impl(t1, u, f, expected, op); test_binop_impl(t1, u1, f, expected, op); } // safe types to test template using safe1 = boost::numeric::safe; template using safe2 = boost::numeric::safe; // The number of random values of each type to use. #define N_RANDOM_VALUES 10 // Random engine used to produce test values. std::mt19937 global_rng; template void for_each_value(T t, F f) { // Hard-coded list of important edge cases f(T(0)); f(T(1)); if(std::is_signed::value) f(T(-1)); f((std::numeric_limits::min)()); f((std::numeric_limits::max)()); // Then pick some more test values randomly // Boost.Random is more forgiving than std::random. // (std::uniform_int_distribution balks at char.) boost::random::uniform_int_distribution uniform((std::numeric_limits::min)(),(std::numeric_limits::max)()); for(int i = 0; i < N_RANDOM_VALUES; ++i) f(uniform(global_rng)); } typedef boost::mpl::vector< char, unsigned char, signed char, short, unsigned short, int, unsigned int, long unsigned long, long long, unsigned long long > integer_types; // Test all pairs of values template class S, class F> void test_binop_loop(F f, const std::string& op) { boost::mpl::for_each([=](auto T) { boost::mpl::for_each([=](auto U) { for_each_value(T, [=](auto t) { for_each_value(U, [=](auto u) { test_binop(t, u, f, op); }); }); }); }); } // Apply a test case to all safe types. template void test_binop_all(F f, const std::string& op) { test_binop_loop(f, op); test_binop_loop(f, op); } BOOST_AUTO_TEST_CASE(test_add) { test_binop_all([](auto x, auto y) { return x + y; }, "+"); } BOOST_AUTO_TEST_CASE(test_sub) { test_binop_all([](auto x, auto y) { return x - y; }, "-"); } BOOST_AUTO_TEST_CASE(test_mul) { test_binop_all([](auto x, auto y) { return x * y; }, "*"); } BOOST_AUTO_TEST_CASE(test_div) { test_binop_all([](auto x, auto y) { return x / y; }, "/"); }