@@ -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
3336using 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 .
4043template <typename U>
4144auto 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.
4964template <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