[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[lmi-commits] [lmi] master b777985: Test casts in a peculiarly ill-condi
From: |
Greg Chicares |
Subject: |
[lmi-commits] [lmi] master b777985: Test casts in a peculiarly ill-conditioned range |
Date: |
Tue, 4 Apr 2017 19:33:43 -0400 (EDT) |
branch: master
commit b777985b7b0102f665d1d451508d4dc7aabcb76a
Author: Gregory W. Chicares <address@hidden>
Commit: Gregory W. Chicares <address@hidden>
Test casts in a peculiarly ill-conditioned range
---
bourn_cast_test.cpp | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 126 insertions(+)
diff --git a/bourn_cast_test.cpp b/bourn_cast_test.cpp
index cc083dd..57c5d19 100644
--- a/bourn_cast_test.cpp
+++ b/bourn_cast_test.cpp
@@ -406,6 +406,128 @@ void test_conv_fpint(char const* file, int line)
);
}
+/// Test conversions between wide integral and narrow floating types.
+
+void test_m64_neighborhood()
+{
+ using ull_traits = std::numeric_limits<unsigned long long int>;
+ if(64 != ull_traits::digits)
+ {
+ std::cout
+ << "test_m64_neighborhood() not run because"
+ << "\nunsigned long long is not a 64-bit type."
+ << std::endl
+ ;
+ return;
+ }
+
+ // ULLONG_MAX must be at least 2^64 - 1 [C99 E/1], the 64th
+ // Mersenne number, M64. Converting that number between types
+ // float (IEEE 754 binary32) and unsigned long long int is
+ // interesting because
+ // (2^64 - 1)ULL = 18446744073709551615 = M64 = 2^64 - 1
+ // (2^64 - 1)f = 18446744073709551616 = M64 + 1 = 2^64
+ // Cast either to the type of the other, and they compare equal,
+ // at least with gcc-4.9.2, although casting 2^64 to a 64-bit
+ // unsigned integer is UB.
+
+ unsigned long long int const ull_max = ull_traits::max();
+ float const f_ull_max = ull_max;
+ BOOST_TEST(f_ull_max == static_cast<float>(ull_max));
+ // Suppressed because behavior is undefined:
+ // BOOST_TEST(ull_max == static_cast<unsigned long long int>(f_ull_max));
+
+ // However, unlike static_cast, bourn_cast refuses to cast 2^64
+ // to a 64-bit integer, because it is out of range and therefore
+ // would constitute UB.
+
+ BOOST_TEST_EQUAL(f_ull_max, bourn_cast<float>(ull_max));
+ BOOST_TEST_THROW
+ (bourn_cast<unsigned long long int>(f_ull_max)
+ ,std::runtime_error
+ ,"Cast would transgress upper limit."
+ );
+
+ // To show that this case is not unique, test a value that is
+ // lower by two.
+
+ unsigned long long int const ull_hi = ull_traits::max() - 2; // 2^64 - 3
+
+ float const f_ull_hi = bourn_cast<float>(ull_hi);
+ BOOST_TEST_THROW
+ (bourn_cast<unsigned long long int>(f_ull_hi)
+ ,std::runtime_error
+ ,"Cast would transgress upper limit."
+ );
+
+ // The same outcome is observed with a value that is lower by
+ // about half a trillion units.
+
+ double const d_2_64 = nonstd::power(2.0, 64);
+ double const d_interesting = 0.5 * (d_2_64 + std::nextafterf(d_2_64, 0));
+ unsigned long long int const ull_interesting = d_interesting;
+ float const f_interesting = bourn_cast<float>(ull_interesting);
+ BOOST_TEST_THROW
+ (bourn_cast<unsigned long long int>(f_interesting)
+ ,std::runtime_error
+ ,"Cast would transgress upper limit."
+ );
+ float const f_uninteresting = bourn_cast<float>(ull_interesting - 1ULL);
+ bourn_cast<unsigned long long int>(f_uninteresting);
+
+ // A similar cast must fail for IEEE 754 binary64, because its 53
+ // mantissa bits cannot represent a value this close to 2^64.
+
+ double const d_ull_hi = bourn_cast<double>(ull_hi);
+ BOOST_TEST_THROW
+ (bourn_cast<unsigned long long int>(d_ull_hi)
+ ,std::runtime_error
+ ,"Cast would transgress upper limit."
+ );
+
+ // However, the same cast succeeds when the floating-point type
+ // has at least as much precision as the integral type.
+
+ using ld_traits = std::numeric_limits<long double>;
+ if(ull_traits::digits <= ld_traits::digits)
+ {
+ long double const ld_ull_hi = bourn_cast<long double>(ull_hi);
+ BOOST_TEST_EQUAL(ull_hi, bourn_cast<unsigned long long
int>(ld_ull_hi));
+ }
+
+ // Off-by-one cases arise when the floating type has enough
+ // precision to represent a value exactly one unit outside the
+ // integral type's limits.
+
+ using sll_traits = std::numeric_limits<signed long long int>;
+ if(sll_traits::digits < ld_traits::digits)
+ {
+ signed long long int const sll_max = sll_traits::max();
+
+ long double const ld_sll_max = bourn_cast<long double>(sll_max);
+ BOOST_TEST_EQUAL(sll_max, bourn_cast<signed long long
int>(ld_sll_max));
+
+ long double const ld_sll_too_high = ld_sll_max + 1;
+ BOOST_TEST_THROW
+ (bourn_cast<signed long long int>(ld_sll_too_high)
+ ,std::runtime_error
+ ,"Cast would transgress upper limit."
+ );
+
+ signed long long int const sll_min = sll_traits::min();
+
+ long double const ld_sll_min = bourn_cast<long double>(sll_min);
+ BOOST_TEST_EQUAL(sll_min, bourn_cast<signed long long
int>(ld_sll_min));
+
+ long double const ld_sll_too_low = ld_sll_min - 1;
+ BOOST_TEST_THROW
+ (bourn_cast<signed long long int>(ld_sll_too_low)
+ ,std::runtime_error
+ ,"Cast would transgress lower limit."
+ );
+ }
+}
+
/// Test boost::numeric_cast anomalies reported here:
/// http://lists.nongnu.org/archive/html/lmi/2017-03/msg00127.html
/// and confirmed here:
@@ -636,6 +758,10 @@ int test_main(int, char*[])
test_conv_fpint<unsigned char, long double>(__FILE__, __LINE__);
test_conv_fpint< signed char, long double>(__FILE__, __LINE__);
+ // Test a peculiarly ill-conditioned range.
+
+ test_m64_neighborhood();
+
// Attempt forbidden conversion from negative to unsigned.
BOOST_TEST_THROW
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [lmi-commits] [lmi] master b777985: Test casts in a peculiarly ill-conditioned range,
Greg Chicares <=