Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ jobs:
exclude_compiler: 'clang-3.5,clang-3.6,clang-3.7,clang-3.8,clang-3.9,clang-4.0,clang-5.0,clang-6.0,clang-7,
clang-8,clang-9,clang-10,clang-11,clang-12,
gcc-4.7,gcc-4.8,gcc-4.9,gcc-5,gcc-6,gcc-7,gcc-8,gcc-9,gcc-10'
enable_mingw: false # We blow the stack on the 32-bit with the library
# Example of customization:
# with:
# enable_reflection: true
Expand Down Expand Up @@ -91,9 +92,9 @@ jobs:
cp -r $GITHUB_WORKSPACE/* libs/$LIBRARY
git submodule update --init tools/boostdep
python3 tools/boostdep/depinst/depinst.py --git_args "--jobs 3" $LIBRARY
- name: Test C++20/23
- name: Test C++20
run: |
for std in 20 23; do
for std in 20; do
echo "======== Testing C++${std} ========"
cd ../boost-root
rm -rf __build__
Expand Down
101 changes: 81 additions & 20 deletions include/boost/safe_numbers/detail/float_basis.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,35 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto constexpr_isnormal(c
return !(val == T{} || constexpr_isinf(val) || constexpr_isnan(val) || constexpr_abs(val) < std::numeric_limits<T>::min());
}

// Bit-pattern test for true zero, used in IEEE 754 error classification where
// a subnormal must not be confused with zero, such as with optimized builds on the Intel Compiler
#ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wfloat-equal"
#elif defined(__GNUC__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wfloat-equal"
#endif

template <compatible_float_type T>
BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto is_true_zero(const T val) noexcept -> bool
{
#if defined(__INTEL_COMPILER) || defined(__INTEL_LLVM_COMPILER)
using bit_type = std::conditional_t<std::is_same_v<T, float>, std::uint32_t, std::uint64_t>;
constexpr bit_type sign_mask {bit_type{1} << (std::numeric_limits<bit_type>::digits - 1)};
const auto bits {std::bit_cast<bit_type>(val)};
return static_cast<bit_type>(bits & ~sign_mask) == bit_type{0};
#else
return val == T{};
#endif
}

#ifdef __clang__
# pragma clang diagnostic pop
#elif defined(__GNUC__)
# pragma GCC diagnostic pop
#endif

template <compatible_float_type T>
BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto constexpr_fpclassify(const T val) noexcept -> int
{
Expand Down Expand Up @@ -480,6 +509,12 @@ enum class error_category

namespace impl {

// It's ok that we overflow since we will check post-op
#ifdef _MSC_VER
# pragma warning(push)
# pragma warning(disable:4756)
#endif

// Follows the conventions from IEEE 754 section 6 and 7 on what should happen with mixed non-finite operation:
// 1) Saturation to positive infinity -> Overflow
// 2) Saturation to negative infinity -> Underflow
Expand Down Expand Up @@ -525,6 +560,10 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] auto checked_float_addition(const T
BOOST_SAFE_NUMBERS_UNREACHABLE; // LCOV_EXCL_LINE
}

#ifdef _MSC_VER
# pragma warning(pop)
#endif

template <compatible_float_type BasisType>
BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto throw_overflow_add() -> void
{
Expand Down Expand Up @@ -723,6 +762,12 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE

namespace impl {

// It's ok that we overflow since we will check post-op
#ifdef _MSC_VER
# pragma warning(push)
# pragma warning(disable:4756)
#endif

// See comment above on checked_float_add
template <compatible_float_type T>
BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] auto checked_float_subtraction(const T lhs, const T rhs, T& res) -> error_category
Expand Down Expand Up @@ -763,6 +808,10 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] auto checked_float_subtraction(cons
BOOST_SAFE_NUMBERS_UNREACHABLE; // LCOV_EXCL_LINE
}

#ifdef _MSC_VER
# pragma warning(pop)
#endif

template <compatible_float_type BasisType>
BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto throw_overflow_sub() -> void
{
Expand Down Expand Up @@ -963,11 +1012,14 @@ namespace impl {

// Our comparison to zero is fine
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wfloat-equal"
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wfloat-equal"
#elif defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wfloat-equal"
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wfloat-equal"
#elif defined(_MSC_VER)
# pragma warning(push)
# pragma warning(disable:4756)
#endif

// See comment above on checked_float_addition
Expand All @@ -990,7 +1042,7 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] auto checked_float_multiplication(c
return error_category::invalid_op;
}
// 7.2.c: multiplication of zero by an infinity, in either order
if ((lhs == T{} && constexpr_isinf(rhs)) || (constexpr_isinf(lhs) && rhs == T{}))
if ((is_true_zero(lhs) && constexpr_isinf(rhs)) || (constexpr_isinf(lhs) && is_true_zero(rhs)))
{
return error_category::invalid_op;
}
Expand All @@ -1011,9 +1063,11 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] auto checked_float_multiplication(c
}

#ifdef __clang__
#pragma clang diagnostic pop
# pragma clang diagnostic pop
#elif defined(__GNUC__)
#pragma GCC diagnostic pop
# pragma GCC diagnostic pop
#elif defined(_MSC_VER)
# pragma warning(pop)
#endif

template <compatible_float_type BasisType>
Expand Down Expand Up @@ -1216,14 +1270,16 @@ namespace impl {

// Our comparison to zero is fine
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wfloat-equal"
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wfloat-equal"
#elif defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wfloat-equal"
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wfloat-equal"
#elif defined(_MSC_VER)
# pragma warning(push)
# pragma warning(disable:4756)
#endif


// See comment above on checked_float_addition
template <compatible_float_type T>
BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] auto checked_float_division(const T lhs, const T rhs, T& res) -> error_category
Expand All @@ -1243,8 +1299,10 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] auto checked_float_division(const T
{
return error_category::invalid_op;
}
// 7.2.b: division of zero by zero
if (lhs == T{} && rhs == T{})
// 7.2.b: division of zero by zero. Use is_true_zero so a denormal divisor
// flushed to zero by DAZ (Intel C++ optimized builds, etc.) does not get
// misclassified as a true zero here.
if (is_true_zero(lhs) && is_true_zero(rhs))
{
return error_category::invalid_op;
}
Expand All @@ -1255,9 +1313,10 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] auto checked_float_division(const T
}

// Section 7.3: divideByZero is signaled when a finite non-zero dividend
// is divided by zero. The 0/0 case was already handled above as invalid_op,
// is divided by zero.
// The 0/0 case was already handled above as invalid_op,
// and inf/0 falls through to the section 6.1 infinity classification below.
if (rhs == T{} && !constexpr_isinf(lhs) && !constexpr_isnan(lhs))
if (is_true_zero(rhs) && !constexpr_isinf(lhs) && !constexpr_isnan(lhs))
{
return error_category::divide_by_zero;
}
Expand All @@ -1281,9 +1340,10 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] auto checked_float_division(const T
#pragma clang diagnostic pop
#elif defined(__GNUC__)
#pragma GCC diagnostic pop
#elif defined(_MSC_VER)
#pragma warning(pop)
#endif


template <compatible_float_type BasisType>
BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto throw_overflow_div() -> void
{
Expand Down Expand Up @@ -1552,15 +1612,16 @@ BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] auto checked_float_modulo(const T l
{
return error_category::invalid_op;
}
// 7.2.f sub-case 1: zero modulo zero (matches operator/ treatment of 0/0)
if (lhs == T{} && rhs == T{})
// 7.2.f sub-case 1: zero modulo zero (matches operator/ treatment of 0/0).
// is_true_zero so a denormal flushed to zero by DAZ does not match here.
if (is_true_zero(lhs) && is_true_zero(rhs))
{
return error_category::invalid_op;
}
// Modulo by zero with a finite non-zero dividend. Strict IEEE 7.2.f
// classifies this as invalid_op, but we surface it separately to mirror
// operator/'s divide-by-zero behavior.
if (rhs == T{} && !constexpr_isinf(lhs) && !constexpr_isnan(lhs))
if (is_true_zero(rhs) && !constexpr_isinf(lhs) && !constexpr_isnan(lhs))
{
return error_category::divide_by_zero;
}
Expand Down
2 changes: 1 addition & 1 deletion test/cmake_test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,6 @@ target_link_libraries(quick Boost::safe_numbers)
target_compile_features(quick PRIVATE cxx_std_20)

enable_testing()
add_test(quick quick)
add_test(NAME quick COMMAND quick)

add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure -C $<CONFIG>)
Loading