Skip to content

Commit 4d57918

Browse files
committed
Another attempt at 32-bit testing corrections
1 parent 79a3361 commit 4d57918

5 files changed

Lines changed: 126 additions & 51 deletions

File tree

test/test_float_exp_log.cpp

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,39 @@ import boost.safe_numbers;
2626

2727
#endif
2828

29+
#include <bit>
2930
#include <cmath>
31+
#include <cstdint>
3032
#include <limits>
3133
#include <stdexcept>
34+
#include <type_traits>
3235

3336
using namespace boost::safe_numbers;
3437

3538
// Route the argument through a volatile so the reference std:: call is evaluated by the
36-
// runtime libm rather than constant-folded by the compiler. On 32-bit x86 (x87) some
37-
// libm transcendentals are not correctly rounded and differ from the compiler's folded
38-
// value by an ULP; forcing both the wrapper and the reference onto the same runtime call
39-
// keeps the bit-exact comparison valid.
39+
// runtime libm, exactly as the wrapper evaluates it, rather than being constant-folded by
40+
// the compiler to a correctly-rounded value. On 32-bit x86 some libm transcendentals are
41+
// not correctly rounded, so the folded reference would not match the wrapper's faithful
42+
// passthrough of the runtime result.
4043
template <typename U>
4144
auto opaque(const U value) noexcept -> U
4245
{
4346
volatile U sink {value};
4447
return sink;
4548
}
4649

50+
// Compare the stored basis values bit-for-bit. Going through bit_cast forces each value
51+
// out of any 80-bit x87 register to its true 32/64-bit storage, so the comparison is not
52+
// corrupted by excess precision (FLT_EVAL_METHOD == 2), where float_basis::operator== can
53+
// otherwise compare 80-bit register values that differ even when the stored values match.
54+
template <typename T>
55+
auto same_bits(const T a, const T b) noexcept -> bool
56+
{
57+
using basis_type = typename T::basis_type;
58+
using bits_type = std::conditional_t<std::is_same_v<basis_type, float>, std::uint32_t, std::uint64_t>;
59+
return std::bit_cast<bits_type>(static_cast<basis_type>(a)) == std::bit_cast<bits_type>(static_cast<basis_type>(b));
60+
}
61+
4762
template <typename T>
4863
void test_finite()
4964
{
@@ -53,20 +68,20 @@ void test_finite()
5368
static_cast<basis_type>(2.5), static_cast<basis_type>(-1.0)})
5469
{
5570
const auto x {opaque(raw)};
56-
BOOST_TEST(exp(T{x}) == T{std::exp(x)});
57-
BOOST_TEST(exp2(T{x}) == T{std::exp2(x)});
58-
BOOST_TEST(expm1(T{x}) == T{std::expm1(x)});
71+
BOOST_TEST(same_bits(exp(T{x}), T{std::exp(x)}));
72+
BOOST_TEST(same_bits(exp2(T{x}), T{std::exp2(x)}));
73+
BOOST_TEST(same_bits(expm1(T{x}), T{std::expm1(x)}));
5974
}
6075

6176
for (const auto raw : {static_cast<basis_type>(0.5), static_cast<basis_type>(1.0),
6277
static_cast<basis_type>(2.0), static_cast<basis_type>(100.0)})
6378
{
6479
const auto x {opaque(raw)};
65-
BOOST_TEST(log(T{x}) == T{std::log(x)});
66-
BOOST_TEST(log2(T{x}) == T{std::log2(x)});
67-
BOOST_TEST(log10(T{x}) == T{std::log10(x)});
68-
BOOST_TEST(log1p(T{x}) == T{std::log1p(x)});
69-
BOOST_TEST(logb(T{x}) == T{std::logb(x)});
80+
BOOST_TEST(same_bits(log(T{x}), T{std::log(x)}));
81+
BOOST_TEST(same_bits(log2(T{x}), T{std::log2(x)}));
82+
BOOST_TEST(same_bits(log10(T{x}), T{std::log10(x)}));
83+
BOOST_TEST(same_bits(log1p(T{x}), T{std::log1p(x)}));
84+
BOOST_TEST(same_bits(logb(T{x}), T{std::logb(x)}));
7085
}
7186
}
7287

test/test_float_hyperbolic.cpp

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,39 @@ import boost.safe_numbers;
2626

2727
#endif
2828

29+
#include <bit>
2930
#include <cmath>
31+
#include <cstdint>
3032
#include <limits>
3133
#include <stdexcept>
34+
#include <type_traits>
3235

3336
using namespace boost::safe_numbers;
3437

3538
// Route the argument through a volatile so the reference std:: call is evaluated by the
36-
// runtime libm rather than constant-folded by the compiler. On 32-bit x86 (x87) some
37-
// libm transcendentals are not correctly rounded and differ from the compiler's folded
38-
// value by an ULP; forcing both the wrapper and the reference onto the same runtime call
39-
// keeps the bit-exact comparison valid.
39+
// runtime libm, exactly as the wrapper evaluates it, rather than being constant-folded by
40+
// the compiler to a correctly-rounded value. On 32-bit x86 some libm transcendentals are
41+
// not correctly rounded, so the folded reference would not match the wrapper's faithful
42+
// passthrough of the runtime result.
4043
template <typename U>
4144
auto opaque(const U value) noexcept -> U
4245
{
4346
volatile U sink {value};
4447
return sink;
4548
}
4649

50+
// Compare the stored basis values bit-for-bit. Going through bit_cast forces each value
51+
// out of any 80-bit x87 register to its true 32/64-bit storage, so the comparison is not
52+
// corrupted by excess precision (FLT_EVAL_METHOD == 2), where float_basis::operator== can
53+
// otherwise compare 80-bit register values that differ even when the stored values match.
54+
template <typename T>
55+
auto same_bits(const T a, const T b) noexcept -> bool
56+
{
57+
using basis_type = typename T::basis_type;
58+
using bits_type = std::conditional_t<std::is_same_v<basis_type, float>, std::uint32_t, std::uint64_t>;
59+
return std::bit_cast<bits_type>(static_cast<basis_type>(a)) == std::bit_cast<bits_type>(static_cast<basis_type>(b));
60+
}
61+
4762
template <typename T>
4863
void test_finite()
4964
{
@@ -53,25 +68,25 @@ void test_finite()
5368
static_cast<basis_type>(-0.5), static_cast<basis_type>(1.0)})
5469
{
5570
const auto x {opaque(raw)};
56-
BOOST_TEST(sinh(T{x}) == T{std::sinh(x)});
57-
BOOST_TEST(cosh(T{x}) == T{std::cosh(x)});
58-
BOOST_TEST(tanh(T{x}) == T{std::tanh(x)});
59-
BOOST_TEST(asinh(T{x}) == T{std::asinh(x)});
71+
BOOST_TEST(same_bits(sinh(T{x}), T{std::sinh(x)}));
72+
BOOST_TEST(same_bits(cosh(T{x}), T{std::cosh(x)}));
73+
BOOST_TEST(same_bits(tanh(T{x}), T{std::tanh(x)}));
74+
BOOST_TEST(same_bits(asinh(T{x}), T{std::asinh(x)}));
6075
}
6176

6277
// atanh domain is the open interval (-1, 1)
6378
for (const auto raw : {static_cast<basis_type>(0.0), static_cast<basis_type>(0.5),
6479
static_cast<basis_type>(-0.5), static_cast<basis_type>(0.9)})
6580
{
6681
const auto x {opaque(raw)};
67-
BOOST_TEST(atanh(T{x}) == T{std::atanh(x)});
82+
BOOST_TEST(same_bits(atanh(T{x}), T{std::atanh(x)}));
6883
}
6984

7085
// acosh domain is [1, inf)
7186
for (const auto raw : {static_cast<basis_type>(1.0), static_cast<basis_type>(2.0)})
7287
{
7388
const auto x {opaque(raw)};
74-
BOOST_TEST(acosh(T{x}) == T{std::acosh(x)});
89+
BOOST_TEST(same_bits(acosh(T{x}), T{std::acosh(x)}));
7590
}
7691
}
7792

test/test_float_power.cpp

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,39 @@ import boost.safe_numbers;
2626

2727
#endif
2828

29+
#include <bit>
2930
#include <cmath>
31+
#include <cstdint>
3032
#include <limits>
3133
#include <stdexcept>
34+
#include <type_traits>
3235

3336
using namespace boost::safe_numbers;
3437

3538
// Route the argument through a volatile so the reference std:: call is evaluated by the
36-
// runtime libm rather than constant-folded by the compiler. On 32-bit x86 (x87) some
37-
// libm transcendentals are not correctly rounded and differ from the compiler's folded
38-
// value by an ULP; forcing both the wrapper and the reference onto the same runtime call
39-
// keeps the bit-exact comparison valid.
39+
// runtime libm, exactly as the wrapper evaluates it, rather than being constant-folded by
40+
// the compiler to a correctly-rounded value. On 32-bit x86 some libm transcendentals are
41+
// not correctly rounded, so the folded reference would not match the wrapper's faithful
42+
// passthrough of the runtime result.
4043
template <typename U>
4144
auto opaque(const U value) noexcept -> U
4245
{
4346
volatile U sink {value};
4447
return sink;
4548
}
4649

50+
// Compare the stored basis values bit-for-bit. Going through bit_cast forces each value
51+
// out of any 80-bit x87 register to its true 32/64-bit storage, so the comparison is not
52+
// corrupted by excess precision (FLT_EVAL_METHOD == 2), where float_basis::operator== can
53+
// otherwise compare 80-bit register values that differ even when the stored values match.
54+
template <typename T>
55+
auto same_bits(const T a, const T b) noexcept -> bool
56+
{
57+
using basis_type = typename T::basis_type;
58+
using bits_type = std::conditional_t<std::is_same_v<basis_type, float>, std::uint32_t, std::uint64_t>;
59+
return std::bit_cast<bits_type>(static_cast<basis_type>(a)) == std::bit_cast<bits_type>(static_cast<basis_type>(b));
60+
}
61+
4762
template <typename T>
4863
void test_finite()
4964
{
@@ -54,17 +69,17 @@ void test_finite()
5469
BOOST_TEST(sqrt(T{static_cast<basis_type>(0.0)}) == T{static_cast<basis_type>(0.0)});
5570

5671
const auto twenty_seven {opaque(static_cast<basis_type>(27.0))};
57-
BOOST_TEST(cbrt(T{twenty_seven}) == T{std::cbrt(twenty_seven)});
72+
BOOST_TEST(same_bits(cbrt(T{twenty_seven}), T{std::cbrt(twenty_seven)}));
5873
// cbrt of a negative value is well defined
5974
const auto neg_eight {opaque(static_cast<basis_type>(-8.0))};
60-
BOOST_TEST(cbrt(T{neg_eight}) == T{std::cbrt(neg_eight)});
75+
BOOST_TEST(same_bits(cbrt(T{neg_eight}), T{std::cbrt(neg_eight)}));
6176

6277
const auto two {opaque(static_cast<basis_type>(2.0))};
6378
const auto ten {opaque(static_cast<basis_type>(10.0))};
64-
BOOST_TEST(pow(T{two}, T{ten}) == T{std::pow(two, ten)});
79+
BOOST_TEST(same_bits(pow(T{two}, T{ten}), T{std::pow(two, ten)}));
6580
const auto nine {opaque(static_cast<basis_type>(9.0))};
6681
const auto half {opaque(static_cast<basis_type>(0.5))};
67-
BOOST_TEST(pow(T{nine}, T{half}) == T{std::pow(nine, half)});
82+
BOOST_TEST(same_bits(pow(T{nine}, T{half}), T{std::pow(nine, half)}));
6883

6984
// hypot(3, 4) == 5 exactly
7085
BOOST_TEST(hypot(T{static_cast<basis_type>(3.0)}, T{static_cast<basis_type>(4.0)})

test/test_float_special.cpp

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,25 +26,40 @@ import boost.safe_numbers;
2626

2727
#endif
2828

29+
#include <bit>
2930
#include <cmath>
31+
#include <cstdint>
3032
#include <exception>
3133
#include <limits>
3234
#include <stdexcept>
35+
#include <type_traits>
3336

3437
using namespace boost::safe_numbers;
3538

3639
// Route the argument through a volatile so the reference std:: call is evaluated by the
37-
// runtime libm rather than constant-folded by the compiler. On 32-bit x86 (x87) some
38-
// libm transcendentals are not correctly rounded and differ from the compiler's folded
39-
// value by an ULP; forcing both the wrapper and the reference onto the same runtime call
40-
// keeps the bit-exact comparison valid.
40+
// runtime libm, exactly as the wrapper evaluates it, rather than being constant-folded by
41+
// the compiler to a correctly-rounded value. On 32-bit x86 some libm transcendentals are
42+
// not correctly rounded, so the folded reference would not match the wrapper's faithful
43+
// passthrough of the runtime result.
4144
template <typename U>
4245
auto opaque(const U value) noexcept -> U
4346
{
4447
volatile U sink {value};
4548
return sink;
4649
}
4750

51+
// Compare the stored basis values bit-for-bit. Going through bit_cast forces each value
52+
// out of any 80-bit x87 register to its true 32/64-bit storage, so the comparison is not
53+
// corrupted by excess precision (FLT_EVAL_METHOD == 2), where float_basis::operator== can
54+
// otherwise compare 80-bit register values that differ even when the stored values match.
55+
template <typename T>
56+
auto same_bits(const T a, const T b) noexcept -> bool
57+
{
58+
using basis_type = typename T::basis_type;
59+
using bits_type = std::conditional_t<std::is_same_v<basis_type, float>, std::uint32_t, std::uint64_t>;
60+
return std::bit_cast<bits_type>(static_cast<basis_type>(a)) == std::bit_cast<bits_type>(static_cast<basis_type>(b));
61+
}
62+
4863
template <typename T>
4964
void test_finite()
5065
{
@@ -54,16 +69,16 @@ void test_finite()
5469
static_cast<basis_type>(-0.5), static_cast<basis_type>(1.0)})
5570
{
5671
const auto x {opaque(raw)};
57-
BOOST_TEST(erf(T{x}) == T{std::erf(x)});
58-
BOOST_TEST(erfc(T{x}) == T{std::erfc(x)});
72+
BOOST_TEST(same_bits(erf(T{x}), T{std::erf(x)}));
73+
BOOST_TEST(same_bits(erfc(T{x}), T{std::erfc(x)}));
5974
}
6075

6176
// tgamma(5) = 4! = 24, lgamma of a positive value is finite
6277
const auto five {opaque(static_cast<basis_type>(5.0))};
63-
BOOST_TEST(tgamma(T{five}) == T{std::tgamma(five)});
64-
BOOST_TEST(lgamma(T{five}) == T{std::lgamma(five)});
78+
BOOST_TEST(same_bits(tgamma(T{five}), T{std::tgamma(five)}));
79+
BOOST_TEST(same_bits(lgamma(T{five}), T{std::lgamma(five)}));
6580
const auto half {opaque(static_cast<basis_type>(0.5))};
66-
BOOST_TEST(tgamma(T{half}) == T{std::tgamma(half)});
81+
BOOST_TEST(same_bits(tgamma(T{half}), T{std::tgamma(half)}));
6782
}
6883

6984
// tgamma at a negative integer is a pole. Most standard libraries report it as a

test/test_float_trig.cpp

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,39 @@ import boost.safe_numbers;
2626

2727
#endif
2828

29+
#include <bit>
2930
#include <cmath>
31+
#include <cstdint>
3032
#include <limits>
3133
#include <stdexcept>
34+
#include <type_traits>
3235

3336
using namespace boost::safe_numbers;
3437

3538
// Route the argument through a volatile so the reference std:: call is evaluated by the
36-
// runtime libm rather than constant-folded by the compiler. On 32-bit x86 (x87) some
37-
// libm transcendentals are not correctly rounded and differ from the compiler's folded
38-
// value by an ULP; forcing both the wrapper and the reference onto the same runtime call
39-
// keeps the bit-exact comparison valid.
39+
// runtime libm, exactly as the wrapper evaluates it, rather than being constant-folded by
40+
// the compiler to a correctly-rounded value. On 32-bit x86 some libm transcendentals are
41+
// not correctly rounded, so the folded reference would not match the wrapper's faithful
42+
// passthrough of the runtime result.
4043
template <typename U>
4144
auto opaque(const U value) noexcept -> U
4245
{
4346
volatile U sink {value};
4447
return sink;
4548
}
4649

50+
// Compare the stored basis values bit-for-bit. Going through bit_cast forces each value
51+
// out of any 80-bit x87 register to its true 32/64-bit storage, so the comparison is not
52+
// corrupted by excess precision (FLT_EVAL_METHOD == 2), where float_basis::operator== can
53+
// otherwise compare 80-bit register values that differ even when the stored values match.
54+
template <typename T>
55+
auto same_bits(const T a, const T b) noexcept -> bool
56+
{
57+
using basis_type = typename T::basis_type;
58+
using bits_type = std::conditional_t<std::is_same_v<basis_type, float>, std::uint32_t, std::uint64_t>;
59+
return std::bit_cast<bits_type>(static_cast<basis_type>(a)) == std::bit_cast<bits_type>(static_cast<basis_type>(b));
60+
}
61+
4762
// Finite inputs: the wrapper delegates to the same std function on the same basis
4863
// value, so the result is bit-identical and can be compared with exact equality.
4964
template <typename T>
@@ -55,20 +70,20 @@ void test_finite()
5570
static_cast<basis_type>(-0.5), static_cast<basis_type>(1.0)})
5671
{
5772
const auto x {opaque(raw)};
58-
BOOST_TEST(sin(T{x}) == T{std::sin(x)});
59-
BOOST_TEST(cos(T{x}) == T{std::cos(x)});
60-
BOOST_TEST(tan(T{x}) == T{std::tan(x)});
61-
BOOST_TEST(atan(T{x}) == T{std::atan(x)});
62-
BOOST_TEST(asin(T{x}) == T{std::asin(x)});
63-
BOOST_TEST(acos(T{x}) == T{std::acos(x)});
73+
BOOST_TEST(same_bits(sin(T{x}), T{std::sin(x)}));
74+
BOOST_TEST(same_bits(cos(T{x}), T{std::cos(x)}));
75+
BOOST_TEST(same_bits(tan(T{x}), T{std::tan(x)}));
76+
BOOST_TEST(same_bits(atan(T{x}), T{std::atan(x)}));
77+
BOOST_TEST(same_bits(asin(T{x}), T{std::asin(x)}));
78+
BOOST_TEST(same_bits(acos(T{x}), T{std::acos(x)}));
6479
}
6580

6681
const auto a {opaque(static_cast<basis_type>(1.0))};
6782
const auto b {opaque(static_cast<basis_type>(1.0))};
68-
BOOST_TEST(atan2(T{a}, T{b}) == T{std::atan2(a, b)});
83+
BOOST_TEST(same_bits(atan2(T{a}, T{b}), T{std::atan2(a, b)}));
6984
const auto c {opaque(static_cast<basis_type>(-1.0))};
7085
const auto d {opaque(static_cast<basis_type>(2.0))};
71-
BOOST_TEST(atan2(T{c}, T{d}) == T{std::atan2(c, d)});
86+
BOOST_TEST(same_bits(atan2(T{c}, T{d}), T{std::atan2(c, d)}));
7287
}
7388

7489
// asin/acos outside [-1, 1] produce NAN -> domain_error

0 commit comments

Comments
 (0)