From c1baf9d1e07552d285ddff99ad51347403fe0b24 Mon Sep 17 00:00:00 2001 From: Shen-Ta Hsieh Date: Wed, 23 Sep 2026 23:01:26 +0800 Subject: [PATCH 1/7] Classify NaN and the infinities without a pragma or an exception MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - The float constructors of the six types tested NaN with != and ±inf with == against numeric_limits under a -Wfloat-equal pragma. GCC 8 to 14 do not apply the pragmas of a precompiled header to the templates which they instantiate in the including file, so the pch commit needs that region gone. - A NaN now comes from __builtin_isnan, which raises nothing. An ordered compare raises FE_INVALID for a NaN, and == or != draws the warning. MSVC has no such warning, so it keeps the compare with !=. - An infinity now comes from an ordered compare with the infinity of the type, which raises nothing either. Arithmetic such as inf - inf would raise FE_INVALID, and it would also stop the constructor of a constant expression on GCC. - numeric_limits is empty for __float128 on libstdc++ before GCC 16, so infinity_value takes the infinity of double there, which converts exactly. The old test made a __float128 zero an infinity and let a __float128 infinity through as a finite value. - compute_float80_128 tests its magnitude against max() now and loses its region too. - test_construct_from_nonfinite covers the zero, the infinities and NaN for the six types and every binary float type. It makes sure that the constructor raises no FE_INVALID and still gives a constant expression. - On GCC 8 and 13 the new test fails 18 times without this change, once for the __float128 zero and twice for the __float128 infinities of each type. --- include/boost/decimal/decimal128_t.hpp | 24 ++----- include/boost/decimal/decimal32_t.hpp | 24 ++----- include/boost/decimal/decimal64_t.hpp | 24 ++----- include/boost/decimal/decimal_fast128_t.hpp | 24 ++----- include/boost/decimal/decimal_fast32_t.hpp | 24 ++----- include/boost/decimal/decimal_fast64_t.hpp | 24 ++----- .../detail/fast_float/compute_float80_128.hpp | 17 +---- include/boost/decimal/detail/utilities.hpp | 23 +++++++ test/Jamfile | 1 + test/test_construct_from_nonfinite.cpp | 65 +++++++++++++++++++ 10 files changed, 133 insertions(+), 117 deletions(-) create mode 100644 test/test_construct_from_nonfinite.cpp diff --git a/include/boost/decimal/decimal128_t.hpp b/include/boost/decimal/decimal128_t.hpp index e781ecd19..7c12eeec1 100644 --- a/include/boost/decimal/decimal128_t.hpp +++ b/include/boost/decimal/decimal128_t.hpp @@ -1072,14 +1072,6 @@ class numeric_limits : namespace boost { namespace decimal { -#if defined(__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 - #ifdef BOOST_DECIMAL_HAS_CONCEPTS template #else @@ -1088,15 +1080,19 @@ template , b BOOST_DECIMAL_CXX20_CONSTEXPR decimal128_t::decimal128_t(const Float val) noexcept { #ifndef BOOST_DECIMAL_FAST_MATH - if (val != val) + // Neither test raises an exception of the binary environment, nor draws -Wfloat-equal + // The infinity comes through double because numeric_limits<__float128> is empty on old libstdc++ + constexpr Float inf {detail::infinity_value()}; + + if (detail::is_nan_value(val)) { *this = from_bits(detail::d128_nan_mask); } - else if (val == std::numeric_limits::infinity()) + else if (!(val < inf)) { *this = from_bits(detail::d128_inf_mask); } - else if (val == -std::numeric_limits::infinity()) + else if (!(val > -inf)) { *this = -from_bits(detail::d128_inf_mask); } @@ -1115,12 +1111,6 @@ BOOST_DECIMAL_CXX20_CONSTEXPR decimal128_t::decimal128_t(const Float val) noexce } } -#if defined(__clang__) -# pragma clang diagnostic pop -#elif defined(__GNUC__) -# pragma GCC diagnostic pop -#endif - template BOOST_DECIMAL_CXX20_CONSTEXPR auto decimal128_t::operator=(const Float& val) noexcept BOOST_DECIMAL_REQUIRES_RETURN(detail::is_floating_point_v, Float, decimal128_t&) diff --git a/include/boost/decimal/decimal32_t.hpp b/include/boost/decimal/decimal32_t.hpp index af97316a8..7f1854381 100644 --- a/include/boost/decimal/decimal32_t.hpp +++ b/include/boost/decimal/decimal32_t.hpp @@ -1792,14 +1792,6 @@ BOOST_DECIMAL_CUDA_CONSTEXPR auto decimal32_t::edit_sign(const bool sign) noexce } } -#if defined(__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 - BOOST_DECIMAL_CUDA_CONSTEXPR auto decimal32_t::to_components() const noexcept -> detail::decimal32_t_components { detail::decimal32_t_components components {}; @@ -1835,15 +1827,19 @@ template , b BOOST_DECIMAL_CXX20_CONSTEXPR decimal32_t::decimal32_t(const Float val) noexcept { #ifndef BOOST_DECIMAL_FAST_MATH - if (val != val) + // Neither test raises an exception of the binary environment, nor draws -Wfloat-equal + // The infinity comes through double because numeric_limits<__float128> is empty on old libstdc++ + constexpr Float inf {detail::infinity_value()}; + + if (detail::is_nan_value(val)) { *this = boost::decimal::from_bits(boost::decimal::detail::d32_nan_mask); } - else if (val == std::numeric_limits::infinity()) + else if (!(val < inf)) { *this = boost::decimal::from_bits(boost::decimal::detail::d32_inf_mask); } - else if (val == -std::numeric_limits::infinity()) + else if (!(val > -inf)) { *this = -boost::decimal::from_bits(boost::decimal::detail::d32_inf_mask); } @@ -1862,12 +1858,6 @@ BOOST_DECIMAL_CXX20_CONSTEXPR decimal32_t::decimal32_t(const Float val) noexcept } } -#if defined(__clang__) -# pragma clang diagnostic pop -#elif defined(__GNUC__) -# pragma GCC diagnostic pop -#endif - template BOOST_DECIMAL_CXX20_CONSTEXPR auto decimal32_t::operator=(const Float& val) noexcept BOOST_DECIMAL_REQUIRES_RETURN(detail::is_floating_point_v, Float, decimal32_t&) diff --git a/include/boost/decimal/decimal64_t.hpp b/include/boost/decimal/decimal64_t.hpp index 2bbbf5c6a..5a029d4d9 100644 --- a/include/boost/decimal/decimal64_t.hpp +++ b/include/boost/decimal/decimal64_t.hpp @@ -1000,14 +1000,6 @@ class numeric_limits : namespace boost { namespace decimal { -#if defined(__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 - #ifdef BOOST_DECIMAL_HAS_CONCEPTS template #else @@ -1016,15 +1008,19 @@ template , b BOOST_DECIMAL_CXX20_CONSTEXPR decimal64_t::decimal64_t(const Float val) noexcept { #ifndef BOOST_DECIMAL_FAST_MATH - if (val != val) + // Neither test raises an exception of the binary environment, nor draws -Wfloat-equal + // The infinity comes through double because numeric_limits<__float128> is empty on old libstdc++ + constexpr Float inf {detail::infinity_value()}; + + if (detail::is_nan_value(val)) { *this = from_bits(detail::d64_nan_mask); } - else if (val == std::numeric_limits::infinity()) + else if (!(val < inf)) { *this = from_bits(detail::d64_inf_mask); } - else if (val == -std::numeric_limits::infinity()) + else if (!(val > -inf)) { *this = -from_bits(detail::d64_inf_mask); } @@ -1043,12 +1039,6 @@ BOOST_DECIMAL_CXX20_CONSTEXPR decimal64_t::decimal64_t(const Float val) noexcept } } -#if defined(__clang__) -# pragma clang diagnostic pop -#elif defined(__GNUC__) -# pragma GCC diagnostic pop -#endif - template BOOST_DECIMAL_CXX20_CONSTEXPR auto decimal64_t::operator=(const Float& val) noexcept BOOST_DECIMAL_REQUIRES_RETURN(detail::is_floating_point_v, Float, decimal64_t&) diff --git a/include/boost/decimal/decimal_fast128_t.hpp b/include/boost/decimal/decimal_fast128_t.hpp index f96c421f3..ddb059401 100644 --- a/include/boost/decimal/decimal_fast128_t.hpp +++ b/include/boost/decimal/decimal_fast128_t.hpp @@ -616,14 +616,6 @@ template , boo #endif constexpr decimal_fast128_t::decimal_fast128_t(const Integer val) noexcept : decimal_fast128_t{val, 0} {} -#if defined(__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 - #ifdef BOOST_DECIMAL_HAS_CONCEPTS template #else @@ -632,15 +624,19 @@ template , b BOOST_DECIMAL_CXX20_CONSTEXPR decimal_fast128_t::decimal_fast128_t(const Float val) noexcept { #ifndef BOOST_DECIMAL_FAST_MATH - if (val != val) + // Neither test raises an exception of the binary environment, nor draws -Wfloat-equal + // The infinity comes through double because numeric_limits<__float128> is empty on old libstdc++ + constexpr Float inf {detail::infinity_value()}; + + if (detail::is_nan_value(val)) { significand_ = detail::d128_fast_qnan; } - else if (val == std::numeric_limits::infinity()) + else if (!(val < inf)) { significand_ = detail::d128_fast_inf; } - else if (val == -std::numeric_limits::infinity()) + else if (!(val > -inf)) { significand_ = detail::d128_fast_inf; sign_ = true; @@ -653,12 +649,6 @@ BOOST_DECIMAL_CXX20_CONSTEXPR decimal_fast128_t::decimal_fast128_t(const Float v } } -#if defined(__clang__) -# pragma clang diagnostic pop -#elif defined(__GNUC__) -# pragma GCC diagnostic pop -#endif - constexpr auto direct_init_d128(const decimal_fast128_t::significand_type significand, const decimal_fast128_t::exponent_type exponent, const bool sign) noexcept -> decimal_fast128_t diff --git a/include/boost/decimal/decimal_fast32_t.hpp b/include/boost/decimal/decimal_fast32_t.hpp index e79d51723..2c55cf358 100644 --- a/include/boost/decimal/decimal_fast32_t.hpp +++ b/include/boost/decimal/decimal_fast32_t.hpp @@ -719,27 +719,23 @@ class numeric_limits : namespace boost { namespace decimal { -#if defined(__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 , bool>> BOOST_DECIMAL_CXX20_CONSTEXPR decimal_fast32_t::decimal_fast32_t(const Float val) noexcept { #ifndef BOOST_DECIMAL_FAST_MATH - if (val != val) + // Neither test raises an exception of the binary environment, nor draws -Wfloat-equal + // The infinity comes through double because numeric_limits<__float128> is empty on old libstdc++ + constexpr Float inf {detail::infinity_value()}; + + if (detail::is_nan_value(val)) { significand_ = detail::d32_fast_qnan; } - else if (val == std::numeric_limits::infinity()) + else if (!(val < inf)) { significand_ = detail::d32_fast_inf; } - else if (val == -std::numeric_limits::infinity()) + else if (!(val > -inf)) { significand_ = detail::d32_fast_inf; sign_ = true; @@ -752,12 +748,6 @@ BOOST_DECIMAL_CXX20_CONSTEXPR decimal_fast32_t::decimal_fast32_t(const Float val } } -#if defined(__clang__) -# pragma clang diagnostic pop -#elif defined(__GNUC__) -# pragma GCC diagnostic pop -#endif - constexpr auto signbit(const decimal_fast32_t val) noexcept -> bool { return val.sign_; diff --git a/include/boost/decimal/decimal_fast64_t.hpp b/include/boost/decimal/decimal_fast64_t.hpp index 3d4464eba..c87c9eca3 100644 --- a/include/boost/decimal/decimal_fast64_t.hpp +++ b/include/boost/decimal/decimal_fast64_t.hpp @@ -612,14 +612,6 @@ template , boo #endif constexpr decimal_fast64_t::decimal_fast64_t(const Integer val) noexcept : decimal_fast64_t{val, 0} {} -#if defined(__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 - #ifdef BOOST_DECIMAL_HAS_CONCEPTS template #else @@ -628,15 +620,19 @@ template , b BOOST_DECIMAL_CXX20_CONSTEXPR decimal_fast64_t::decimal_fast64_t(const Float val) noexcept { #ifndef BOOST_DECIMAL_FAST_MATH - if (val != val) + // Neither test raises an exception of the binary environment, nor draws -Wfloat-equal + // The infinity comes through double because numeric_limits<__float128> is empty on old libstdc++ + constexpr Float inf {detail::infinity_value()}; + + if (detail::is_nan_value(val)) { significand_ = detail::d64_fast_qnan; } - else if (val == std::numeric_limits::infinity()) + else if (!(val < inf)) { significand_ = detail::d64_fast_inf; } - else if (val == -std::numeric_limits::infinity()) + else if (!(val > -inf)) { significand_ = detail::d64_fast_inf; sign_ = true; @@ -649,12 +645,6 @@ BOOST_DECIMAL_CXX20_CONSTEXPR decimal_fast64_t::decimal_fast64_t(const Float val } } -#if defined(__clang__) -# pragma clang diagnostic pop -#elif defined(__GNUC__) -# pragma GCC diagnostic pop -#endif - constexpr auto direct_init_d64(const decimal_fast64_t::significand_type significand, const decimal_fast64_t::exponent_type exponent, const bool sign) noexcept -> decimal_fast64_t diff --git a/include/boost/decimal/detail/fast_float/compute_float80_128.hpp b/include/boost/decimal/detail/fast_float/compute_float80_128.hpp index 1eebab5c9..eb81f37b5 100644 --- a/include/boost/decimal/detail/fast_float/compute_float80_128.hpp +++ b/include/boost/decimal/detail/fast_float/compute_float80_128.hpp @@ -62,14 +62,6 @@ constexpr auto fast_path(const std::int64_t q, const Unsigned_Integer &w, bool n return ld; } -#if defined(__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 constexpr auto compute_float80_128(std::int64_t q, const Unsigned_Integer &w, const bool negative, bool &success) noexcept -> long double @@ -150,7 +142,8 @@ constexpr auto compute_float80_128(std::int64_t q, const Unsigned_Integer &w, ld *= result; } - if (BOOST_DECIMAL_UNLIKELY(ld == std::numeric_limits::infinity())) + // ld is a magnitude here, so only +inf is above max (an ordered compare avoids -Wfloat-equal) + if (BOOST_DECIMAL_UNLIKELY(ld > (std::numeric_limits::max)())) { success = false; ld = 0.0L; @@ -159,12 +152,6 @@ constexpr auto compute_float80_128(std::int64_t q, const Unsigned_Integer &w, return ld; } -#if defined(__clang__) -# pragma clang diagnostic pop -#elif defined(__GNUC__) -# pragma GCC diagnostic pop -#endif - } //namespace fast_float } //namespace detail diff --git a/include/boost/decimal/detail/utilities.hpp b/include/boost/decimal/detail/utilities.hpp index 158dc348b..2e8802e22 100644 --- a/include/boost/decimal/detail/utilities.hpp +++ b/include/boost/decimal/detail/utilities.hpp @@ -9,6 +9,7 @@ #ifndef BOOST_DECIMAL_BUILD_MODULE #include +#include #endif namespace boost { @@ -48,6 +49,28 @@ BOOST_DECIMAL_CUDA_CONSTEXPR auto (max)(const T& b, const T& a) noexcept -> cons return (a < b) ? b : a; } +// numeric_limits is not specialized for __float128 on the libstdc++ of GCC 15 and before, +// and the infinity of double takes its place there, because that conversion is exact +template +BOOST_DECIMAL_CUDA_CONSTEXPR auto infinity_value() noexcept -> Float +{ + return std::numeric_limits::is_specialized ? + (std::numeric_limits::infinity)() : + static_cast((std::numeric_limits::infinity)()); +} + +// An ordered compare with a NaN raises FE_INVALID, and == or != draws -Wfloat-equal, whose +// diagnostic pragma a precompiled header loses, so the compilers which have the builtin take it +template +BOOST_DECIMAL_CUDA_CONSTEXPR auto is_nan_value(const Float val) noexcept -> bool +{ + #if (defined(__GNUC__) || defined(__clang__)) && !defined(BOOST_DECIMAL_ENABLE_CUDA) + return static_cast(__builtin_isnan(val)); + #else + return val != val; + #endif +} + } // namespace detail } // namespace decimal } // namespace boost diff --git a/test/Jamfile b/test/Jamfile index fc3d39abb..774363c2c 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -150,6 +150,7 @@ run test_charconv_preservation.cpp ; run test_cmath.cpp ; run test_constants.cpp ; run test_constexpr_rounding_mode.cpp ; +run test_construct_from_nonfinite.cpp ; run test_construction_exponent_range.cpp ; run test_cosh.cpp ; #run test_decimal32.cpp ; diff --git a/test/test_construct_from_nonfinite.cpp b/test/test_construct_from_nonfinite.cpp new file mode 100644 index 000000000..72d0c8a0a --- /dev/null +++ b/test/test_construct_from_nonfinite.cpp @@ -0,0 +1,65 @@ +// Copyright 2026 Shen-Ta Hsieh +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include +#include +#include +#include + +using namespace boost::decimal; + +template +void test_nonfinite() +{ + // The zero and the non-finite values have their own paths in the constructor, and the + // volatile source keeps them out of a constant fold, so that the constructor really runs + volatile Float volatile_inf {static_cast(HUGE_VAL)}; + volatile Float volatile_nan {static_cast(std::numeric_limits::quiet_NaN())}; + const Float inf {volatile_inf}; + const Float nan {volatile_nan}; + std::feclearexcept(FE_ALL_EXCEPT); + BOOST_TEST_EQ(Decimal(Float(0)), Decimal(0)); + BOOST_TEST(isinf(Decimal(inf)) && !signbit(Decimal(inf))); + BOOST_TEST(isinf(Decimal(-inf)) && signbit(Decimal(-inf))); + BOOST_TEST(isnan(Decimal(nan))); + + // A constructor which did arithmetic on the value, as inf - inf is, would raise this + BOOST_TEST_EQ(std::fetestexcept(FE_INVALID), 0); + + // A conversion or a load of a signaling NaN can raise FE_INVALID, so only the result counts + volatile double volatile_snan {std::numeric_limits::signaling_NaN()}; + BOOST_TEST(isnan(Decimal(static_cast(volatile_snan)))); +} + +template +void test_all_floats() +{ + test_nonfinite(); + test_nonfinite(); + #ifndef BOOST_DECIMAL_UNSUPPORTED_LONG_DOUBLE + test_nonfinite(); + #endif + #if defined(BOOST_DECIMAL_HAS_FLOAT128) && (!defined(__clang_major__) || __clang_major__ >= 13) + test_nonfinite(); + #endif + + // Arithmetic on the value would also fail this constant expression + #ifdef BOOST_DECIMAL_HAS_CONSTEXPR_BITCAST + constexpr Decimal constant_inf {std::numeric_limits::infinity()}; + static_assert(constant_inf > Decimal {1}, "the constructor lost the infinity"); + #endif +} + +int main() +{ + test_all_floats(); + test_all_floats(); + test_all_floats(); + test_all_floats(); + test_all_floats(); + test_all_floats(); + + return boost::report_errors(); +} From f5cefb94ec7398c04e95cc64cf6a35fd16b5d2df Mon Sep 17 00:00:00 2001 From: Shen-Ta Hsieh Date: Tue, 22 Sep 2026 08:36:34 +0800 Subject: [PATCH 2/7] Keep the warnings which the diagnostic pragmas hide out of the headers - GCC 8 to 14 do not apply the diagnostic pragmas of a precompiled header to the templates which they instantiate in the including file. With -Werror every test then fails when boost/decimal.hpp is precompiled, which the pch commit does. - coefficient_rounding converts the shift to unsigned before T1 takes it. - memcpy into a uint128, an int128 or a bit_cast result goes through a void* (the documented way to keep -Wclass-memaccess quiet). - pow10 tells the optimizer that the index is in range, or GCC 8 to 12 report -Warray-bounds on dead paths. - The whole test directory compiles without a warning on gcc 8 to 16, with and without the pch, and on clang 22. --- include/boost/decimal/detail/bit_cast.hpp | 3 ++- include/boost/decimal/detail/fenv_rounding.hpp | 4 ++-- include/boost/decimal/detail/int128/detail/int128_imp.hpp | 4 ++-- include/boost/decimal/detail/int128/detail/uint128_imp.hpp | 7 ++++--- include/boost/decimal/detail/power_tables.hpp | 4 ++++ 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/include/boost/decimal/detail/bit_cast.hpp b/include/boost/decimal/detail/bit_cast.hpp index e38eea8f3..61c1c947a 100644 --- a/include/boost/decimal/detail/bit_cast.hpp +++ b/include/boost/decimal/detail/bit_cast.hpp @@ -35,7 +35,8 @@ auto bit_cast(const From& src) noexcept -> To { static_assert(sizeof(To) >= sizeof(From), "To and From must be the same size"); To dst; - std::memcpy(&dst, &src, sizeof(From)); + // The void* cast is the documented way to keep GCC's -Wclass-memaccess quiet + std::memcpy(static_cast(&dst), &src, sizeof(From)); return dst; } diff --git a/include/boost/decimal/detail/fenv_rounding.hpp b/include/boost/decimal/detail/fenv_rounding.hpp index e98473564..0ba39102e 100644 --- a/include/boost/decimal/detail/fenv_rounding.hpp +++ b/include/boost/decimal/detail/fenv_rounding.hpp @@ -722,9 +722,9 @@ BOOST_DECIMAL_CUDA_CONSTEXPR auto coefficient_rounding(T1& coeff, T2& exp, T3& b return 1; } - // Do shifting + // Do shifting (shift is non-negative, so widen it unsigned before T1 takes it) BOOST_DECIMAL_ASSERT(shift >= 0); - const auto shift_pow_ten {detail::pow10(static_cast(shift))}; + const auto shift_pow_ten {detail::pow10(static_cast(static_cast(shift)))}; // In the synthetic integer cases it's inexpensive to see if we can demote the type // relative to the cost of the division and modulo operation. diff --git a/include/boost/decimal/detail/int128/detail/int128_imp.hpp b/include/boost/decimal/detail/int128/detail/int128_imp.hpp index 3aad6a374..f7941672b 100644 --- a/include/boost/decimal/detail/int128/detail/int128_imp.hpp +++ b/include/boost/decimal/detail/int128/detail/int128_imp.hpp @@ -1326,7 +1326,7 @@ BOOST_DECIMAL_DETAIL_INT128_HOST_DEVICE int128 intrinsic_ls_impl(const int128 lh const auto res {value << rhs}; int128 return_value; - std::memcpy(&return_value, &res, sizeof(int128)); + std::memcpy(static_cast(&return_value), &res, sizeof(int128)); return return_value; #if defined(__GNUC__) && __GNUC__ >= 8 @@ -1519,7 +1519,7 @@ BOOST_DECIMAL_DETAIL_INT128_HOST_DEVICE int128 intrinsic_rs_impl(const int128 lh const auto res {value >> rhs}; int128 return_value; - std::memcpy(&return_value, &res, sizeof(int128)); + std::memcpy(static_cast(&return_value), &res, sizeof(int128)); return return_value; #if defined(__GNUC__) && __GNUC__ >= 8 diff --git a/include/boost/decimal/detail/int128/detail/uint128_imp.hpp b/include/boost/decimal/detail/int128/detail/uint128_imp.hpp index f33aabf32..8aff258a6 100644 --- a/include/boost/decimal/detail/int128/detail/uint128_imp.hpp +++ b/include/boost/decimal/detail/int128/detail/uint128_imp.hpp @@ -1515,7 +1515,7 @@ BOOST_DECIMAL_DETAIL_INT128_HOST_DEVICE uint128 intrinsic_ls_impl(const uint128 const auto res {value << rhs}; uint128 return_value; - std::memcpy(&return_value, &res, sizeof(uint128)); + std::memcpy(static_cast(&return_value), &res, sizeof(uint128)); return return_value; #if defined(__GNUC__) && __GNUC__ >= 8 @@ -1688,7 +1688,7 @@ BOOST_DECIMAL_DETAIL_INT128_HOST_DEVICE uint128 intrinsic_rs_impl(const uint128 const auto res {value >> rhs}; uint128 return_value; - std::memcpy(&return_value, &res, sizeof(uint128)); + std::memcpy(static_cast(&return_value), &res, sizeof(uint128)); return return_value; #if defined(__GNUC__) && __GNUC__ >= 8 @@ -2233,7 +2233,8 @@ BOOST_DECIMAL_DETAIL_INT128_HOST_DEVICE BOOST_DECIMAL_DETAIL_INT128_FORCE_INLINE uint128 library_res {}; - std::memcpy(&library_res, &res, sizeof(uint128)); + // The void* cast is the documented way to keep GCC's -Wclass-memaccess quiet + std::memcpy(static_cast(&library_res), &res, sizeof(uint128)); return library_res; } diff --git a/include/boost/decimal/detail/power_tables.hpp b/include/boost/decimal/detail/power_tables.hpp index c3a6efba6..d62ee40dd 100644 --- a/include/boost/decimal/detail/power_tables.hpp +++ b/include/boost/decimal/detail/power_tables.hpp @@ -234,6 +234,8 @@ BOOST_DECIMAL_CUDA_CONSTEXPR auto pow10(T n) noexcept -> T #else + // The callers keep n in range; the hint stops GCC 8 to 12 from reporting -Warray-bounds on dead paths + if (static_cast(n) >= 20U) { BOOST_DECIMAL_UNREACHABLE; } return static_cast(impl::powers_of_10[static_cast(n)]); #endif @@ -290,6 +292,8 @@ BOOST_DECIMAL_CUDA_CONSTEXPR auto pow10(const boost::int128::uint128_t n) noexce #else + // The callers keep n in range; the hint stops GCC 8 to 12 from reporting -Warray-bounds on dead paths + if (n.low >= 39U) { BOOST_DECIMAL_UNREACHABLE; } return impl::BOOST_DECIMAL_DETAIL_INT128_pow10[static_cast(n.low)]; #endif From 97be1eb2b7c7202aa19a6a7f14ef1829474b00fa Mon Sep 17 00:00:00 2001 From: Shen-Ta Hsieh Date: Wed, 23 Sep 2026 23:02:31 +0800 Subject: [PATCH 3/7] Pick the fast type of from_bid by an overload instead of a specialization - Clang 15 to 18 drop an explicit specialization of a constrained function template when it comes from a precompiled header, and the from_bid calls then fail to link. - The fast type now has its own overload, constrained with is_fast_type_v, and the primary is constrained with is_ieee_type_v, so exactly one overload matches. - The fast overload asks for is_decimal_floating_point_v as well, because is_fast_type_v is the negation of is_ieee_type_v and is true for int, double and every other type. - test_bid_conversions makes sure that from_bid takes the six decimal types and no other. - The four from_bid tests compile and run at C++14, 17 and 20 with gcc and clang, and link with the pch on clang 15 and 16. --- include/boost/decimal/bid_conversion.hpp | 24 ++++++++++++++--------- test/test_bid_conversions.cpp | 25 ++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/include/boost/decimal/bid_conversion.hpp b/include/boost/decimal/bid_conversion.hpp index bcf5a0fae..f18365dab 100644 --- a/include/boost/decimal/bid_conversion.hpp +++ b/include/boost/decimal/bid_conversion.hpp @@ -23,6 +23,9 @@ namespace decimal { # pragma GCC diagnostic ignored "-Wconversion" #endif +// from_bid picks the fast type by overload, not by an explicit specialization, because clang 15 to 18 +// drop an explicit specialization of a constrained template which comes from a precompiled header + BOOST_DECIMAL_EXPORT BOOST_DECIMAL_CUDA_CONSTEXPR auto to_bid_d32(const decimal32_t val) noexcept -> std::uint32_t { return val.bits_; @@ -200,39 +203,42 @@ BOOST_DECIMAL_CUDA_CONSTEXPR auto to_bid(const T val) noexcept BOOST_DECIMAL_EXPORT template BOOST_DECIMAL_CUDA_CONSTEXPR auto from_bid(const std::uint32_t bits) noexcept - BOOST_DECIMAL_REQUIRES(detail::is_decimal_floating_point_v, T) + BOOST_DECIMAL_REQUIRES(detail::is_ieee_type_v, T) { return from_bid_d32(bits); } -template <> -BOOST_DECIMAL_CUDA_CONSTEXPR auto from_bid(const std::uint32_t bits) noexcept -> decimal_fast32_t +BOOST_DECIMAL_EXPORT template +BOOST_DECIMAL_CUDA_CONSTEXPR auto from_bid(const std::uint32_t bits) noexcept + BOOST_DECIMAL_REQUIRES_TWO_RETURN(detail::is_decimal_floating_point_v, T, detail::is_fast_type_v, T, T) { return from_bid_d32f(bits); } BOOST_DECIMAL_EXPORT template BOOST_DECIMAL_CUDA_CONSTEXPR auto from_bid(const std::uint64_t bits) noexcept - BOOST_DECIMAL_REQUIRES(detail::is_decimal_floating_point_v, T) + BOOST_DECIMAL_REQUIRES(detail::is_ieee_type_v, T) { return from_bid_d64(bits); } -template <> -BOOST_DECIMAL_CUDA_CONSTEXPR auto from_bid(const std::uint64_t bits) noexcept -> decimal_fast64_t +BOOST_DECIMAL_EXPORT template +BOOST_DECIMAL_CUDA_CONSTEXPR auto from_bid(const std::uint64_t bits) noexcept + BOOST_DECIMAL_REQUIRES_TWO_RETURN(detail::is_decimal_floating_point_v, T, detail::is_fast_type_v, T, T) { return from_bid_d64f(bits); } BOOST_DECIMAL_EXPORT template BOOST_DECIMAL_CUDA_CONSTEXPR auto from_bid(const int128::uint128_t bits) noexcept - BOOST_DECIMAL_REQUIRES(detail::is_decimal_floating_point_v, T) + BOOST_DECIMAL_REQUIRES(detail::is_ieee_type_v, T) { return from_bid_d128(bits); } -template <> -BOOST_DECIMAL_CUDA_CONSTEXPR auto from_bid(const int128::uint128_t bits) noexcept -> decimal_fast128_t +BOOST_DECIMAL_EXPORT template +BOOST_DECIMAL_CUDA_CONSTEXPR auto from_bid(const int128::uint128_t bits) noexcept + BOOST_DECIMAL_REQUIRES_TWO_RETURN(detail::is_decimal_floating_point_v, T, detail::is_fast_type_v, T, T) { return from_bid_d128f(bits); } diff --git a/test/test_bid_conversions.cpp b/test/test_bid_conversions.cpp index d3a31a673..1f560be56 100644 --- a/test/test_bid_conversions.cpp +++ b/test/test_bid_conversions.cpp @@ -8,6 +8,7 @@ #include #include #include +#include using namespace boost::decimal; @@ -175,6 +176,30 @@ void test_non_finite_pass_through() #endif } +// from_bid selects the representation by overload, so only a decimal type may match one +template +using void_t = void; + +template +struct has_from_bid : std::false_type {}; + +template +struct has_from_bid(BitType {}))>> : std::true_type {}; + +struct not_a_decimal {}; + +static_assert(has_from_bid::value, "decimal32_t has no from_bid"); +static_assert(has_from_bid::value, "decimal_fast32_t has no from_bid"); +static_assert(has_from_bid::value, "decimal64_t has no from_bid"); +static_assert(has_from_bid::value, "decimal_fast64_t has no from_bid"); +static_assert(has_from_bid::value, "decimal128_t has no from_bid"); +static_assert(has_from_bid::value, "decimal_fast128_t has no from_bid"); + +static_assert(!has_from_bid::value, "from_bid takes an integer"); +static_assert(!has_from_bid::value, "from_bid takes a binary float"); +static_assert(!has_from_bid::value, "from_bid takes a pointer"); +static_assert(!has_from_bid::value, "from_bid takes an unrelated class"); + int main() { test(); From 126987603e0be6520a4193ac86d337522829690b Mon Sep 17 00:00:00 2001 From: Shen-Ta Hsieh Date: Tue, 22 Sep 2026 13:54:43 +0800 Subject: [PATCH 4/7] Name the file of the to_from_file example after its executable - b2 runs the variants of the example in one directory, some of them at the same time, and they all used example_values.txt. One variant then read a file which another one had removed, and exited with 1. - The name now comes from argv[0], so every variant has a file of its own. Two copies of the old example fail 4 times in 100 parallel runs, the new one never. - The standard allows argc to be 0, so the name falls back to a literal in that case. - The write side opens the file in binary mode like the read side, which matters on Windows. - The example exits with 1 when the write or the read fails, or when a value does not come back. Before, it read an uninitialized value after a short read, and a write to a full disk still exited with 0. --- example/to_from_file.cpp | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/example/to_from_file.cpp b/example/to_from_file.cpp index 3ab1dc745..06017344f 100644 --- a/example/to_from_file.cpp +++ b/example/to_from_file.cpp @@ -14,11 +14,16 @@ #include #include #include +#include -int main() +int main(int argc, char* argv[]) { using boost::decimal::decimal32_t; // The type decimal32_t + // The name comes from the executable, because the test harness runs every build of this + // example from one directory. The standard allows argc to be 0, so a literal stands in then + const std::string file_name {(argc > 0 ? std::string(argv[0]) : std::string("to_from_file")) + "_values.txt"}; + // First we need to generate some values that we will use for further usage // This constructs a decimal32_t from random significand and exponent within the domain of decimal32_t std::mt19937_64 rng {42}; @@ -38,7 +43,7 @@ int main() // It is more efficient than writing the string to file with to_chars, // and then recovering via the string constructor or from_chars - std::ofstream file("example_values.txt"); + std::ofstream file(file_name, std::ios::binary); if (!file.is_open()) { std::cerr << "Failed to open file for writing" << std::endl; @@ -55,10 +60,15 @@ int main() file.write(reinterpret_cast(&bid_value), sizeof(bid_value)); } file.close(); + if (!file) + { + std::cerr << "Failed to write the file" << std::endl; + return 1; + } // Now that we have written all the values to file we will read them in, // and then convert them back them to the decimal values using from_bid - std::ifstream read_file("example_values.txt", std::ios::binary); + std::ifstream read_file(file_name, std::ios::binary); if (!read_file.is_open()) { std::cerr << "Failed to open file for reading" << std::endl; @@ -68,19 +78,20 @@ int main() std::array recovered_values; for (auto& value : recovered_values) { - std::uint32_t bid_value; + std::uint32_t bid_value {}; read_file.read(reinterpret_cast(&bid_value), sizeof(bid_value)); value = boost::decimal::from_bid(bid_value); } + const bool read_ok {static_cast(read_file)}; read_file.close(); - if (std::remove("example_values.txt")) + if (std::remove(file_name.c_str())) { std::cerr << "Failed to remove file" << std::endl; } - // Verify that we recovered the same values - bool success {true}; + // Verify that we read every value, and that we recovered the same values + bool success {read_ok}; for (std::size_t i {}; i < values.size(); ++i) { if (values[i] != recovered_values[i]) @@ -96,7 +107,8 @@ int main() } else { - std::cout << "Warning: Some values did not match after recovery" << std::endl; + std::cout << "Error: Some values did not match after recovery" << std::endl; + return 1; } return 0; From 7b7e295e7a0f9c2cafff3e3a41630fc6cbb124d7 Mon Sep 17 00:00:00 2001 From: Shen-Ta Hsieh Date: Wed, 23 Sep 2026 23:02:54 +0800 Subject: [PATCH 5/7] Precompile boost/decimal.hpp for the tests - b2 builds test/pch.hpp once per variant and force-includes it into every test. - A private feature adds the pch as a source to every target but the pch itself. - A test which defines a library macro before the include opts out with off. - github_issue_1384 opts out as well, because it declares a namespace boost before the include, and that order is what the test is about. - The compile tests of the separate headers opt out as well. - Three tests which take a detail header on its own opt out too, so that a missing include of that header still shows. - The opt-out is on a second line, so the CMake test list stays the same. - The pch defines _SILENCE_CXX23_DENORM_DEPRECATION_WARNING, because the MSVC standard library reads it when the pch parses . The Boost.Math tests define it only later. - GCC pch is off on MinGW, as in Boost.Math. - test_asinh opts out on clang 6 to 8, which fail its constexpr at C++14 with the pch. - github_issue_1329 and test_cbrt_from_math opt out: 32-bit GCC 12 loses the -Wstringop-overflow pragma of libstdc++ with the pch and then rejects the cpp_dec_float output of Boost.Multiprecision. - MSVC on an ARM64 host builds without the pch, because it fails to map the pch there now and then. - Clang 22 needs 4562 s of CPU for the test directory without the pch and 1136 s with it. - GCC 16 needs 2227 s without and 1442 s with it. Both numbers include the test runs. - In CI the 32-bit gcc-14 job went from 171 min to 68 min, MSVC x64 from 118 min to 37 min, and clang-win x64 from 248 min to 51 min. Fixes #1461 --- test/Jamfile | 168 +++++++++++++++++++++++++++++++++++++-------------- test/pch.hpp | 11 ++++ 2 files changed, 133 insertions(+), 46 deletions(-) create mode 100644 test/pch.hpp diff --git a/test/Jamfile b/test/Jamfile index 774363c2c..0c783fbda 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -9,6 +9,19 @@ import-search /boost/config/checks ; import config : requires ; import modules ; import testing ; +import pch ; +import feature ; +import os ; + +# Private switch: the tests take the pch as a source, the pch target itself must not +feature.feature decimal-pch : on off ; + +# MSVC on an ARM64 host fails to map the pch now and then (C3859) +local msvc-arm64-pch ; +if [ os.platform ] = ARM64 +{ + msvc-arm64-pch = msvc:off ; +} project : requirements @@ -47,17 +60,29 @@ project : requirements gcc:on [ requires cxx14_decltype_auto cxx14_generic_lambdas cxx14_return_type_deduction cxx14_variable_templates cxx14_constexpr ] + + # Precompile boost/decimal.hpp once per variant and force-include it into every test + # A test which defines a library macro or declares code before the include opts out with off + on:pch + # GCC pch is broken on MinGW (Boost.Math turns it off there too) + gcc,windows:off + $(msvc-arm64-pch) ; +cpp-pch pch : pch.hpp : off ; + run-fail benchmarks.cpp ; -run-fail benchmark_uint256.cpp ; +# The test takes a detail header on its own, so the pch would hide a missing include of it +run-fail benchmark_uint256.cpp + : : : off ; run-fail bench_accumulation.cpp ; run compare_dec128_and_fast.cpp ; compile-fail concepts_test.cpp ; run crash_report_1.cpp ; -run github_issue_426.cpp ; +run github_issue_426.cpp + : : : off ; run github_issue_448.cpp ; run-fail github_issue_519.cpp ; run github_issue_798.cpp ; @@ -91,11 +116,16 @@ run github_issue_1304.cpp ; run github_issue_1306.cpp ; run github_issue_1312.cpp ; run github_issue_1319.cpp ; -run github_issue_1329.cpp ; +# The cpp_dec_float output of Boost.Multiprecision trips a libstdc++ -Wstringop-overflow false +# positive on 32-bit GCC 12, whose pragma in char_traits.h the pch loses +run github_issue_1329.cpp + : : : off ; run github_issue_1361.cpp ; run github_issue_1378.cpp ; run github_issue_1383.cpp ; -run github_issue_1384.cpp ; +# The test declares a namespace boost before the include, and the pch would put the library first +run github_issue_1384.cpp + : : : off ; run github_issue_1398.cpp ; run github_issue_1424.cpp ; run github_issue_1436.cpp ; @@ -105,9 +135,11 @@ run github_issue_1447.cpp ; run github_issue_1451.cpp ; run github_issue_1453.cpp ; run github_issue_1455.cpp ; -run github_issue_1455_downward.cpp ; +run github_issue_1455_downward.cpp + : : : off ; run github_issue_1459.cpp ; -run github_issue_1459_toward_zero.cpp ; +run github_issue_1459_toward_zero.cpp + : : : off ; run link_1.cpp link_2.cpp link_3.cpp ; run quick.cpp ; @@ -133,23 +165,30 @@ run roundtrip_decimal128_fast.cpp ; run test_acos.cpp ; run test_acosh.cpp ; run test_asin.cpp ; -run test_asinh.cpp ; +# clang 6 to 8 fail the constexpr of asinh_impl at C++14 when it comes from the pch +run test_asinh.cpp + : : : clang-6:off clang-7:off clang-8:off ; run test_assoc_laguerre.cpp ; run test_assoc_legendre.cpp ; run test_atan.cpp ; run test_atan2.cpp ; run test_atanh.cpp ; -compile-fail test_bad_evaluation_method.cpp ; +compile-fail test_bad_evaluation_method.cpp + : off ; run test_beta.cpp ; run test_bid_conversions.cpp ; run test_big_uints.cpp ; -run test_boost_math_univariate_stats.cpp ; +run test_boost_math_univariate_stats.cpp + : : : off ; run test_cbrt.cpp ; -run test_cbrt_from_math.cpp ; +# The same libstdc++ false positive as github_issue_1329 +run test_cbrt_from_math.cpp + : : : off ; run test_charconv_preservation.cpp ; run test_cmath.cpp ; run test_constants.cpp ; -run test_constexpr_rounding_mode.cpp ; +run test_constexpr_rounding_mode.cpp + : : : off ; run test_construct_from_nonfinite.cpp ; run test_construction_exponent_range.cpp ; run test_cosh.cpp ; @@ -163,18 +202,23 @@ run test_decimal64_fast_stream.cpp ; run test_decimal64_stream.cpp ; #run test_decimal128_basis.cpp ; run test_decimal_quantum.cpp ; -run test_downward_rounding.cpp ; +run test_downward_rounding.cpp + : : : off ; run test_dpd_conversions.cpp ; run test_edges_and_behave.cpp ; -run test_edit_members.cpp ; +run test_edit_members.cpp + : : : off ; run test_ellint_1.cpp ; run test_ellint_2.cpp ; run test_erf.cpp ; run test_exp.cpp ; compile-fail test_explicit_floats.cpp ; run test_expm1.cpp ; -run test_fast_float.cpp ; -run test_fast_math.cpp ; +# The test takes a detail header on its own, so the pch would hide a missing include of it +run test_fast_float.cpp + : : : off ; +run test_fast_math.cpp + : : : off ; run test_fenv.cpp ; run test_fixed_width_trunc.cpp ; run test_float_conversion.cpp ; @@ -183,7 +227,8 @@ run test_format.cpp ; run test_format_fmtlib.cpp ; run-fail test_fprintf.cpp ; run test_frexp_ldexp.cpp ; -run test_from_chars.cpp /boost/charconv//boost_charconv ; +run test_from_chars.cpp /boost/charconv//boost_charconv + : : : off ; run test_from_chars_nan_payloads.cpp ; run test_from_string.cpp ; run test_git_issue_266.cpp ; @@ -198,7 +243,8 @@ compile-fail test_illegal_decimal128_implicit_conversions.cpp ; compile-fail test_illegal_fast_quantexp.cpp ; compile-fail test_illegal_fast_quantize.cpp ; compile-fail test_illegal_fast_samequantum.cpp ; -run test_implicit_integral_conversion.cpp ; +run test_implicit_integral_conversion.cpp + : : : off ; run test_laguerre.cpp ; run test_legal_implicit_conversions.cpp ; run test_legendre.cpp ; @@ -221,9 +267,12 @@ run test_sinh.cpp ; run test_snprintf.cpp ; run test_sqrt.cpp ; run test_sqrt_rounding.cpp ; -run test_sqrt_rounding_upward.cpp ; +run test_sqrt_rounding_upward.cpp + : : : off ; run test_string_construction.cpp ; -run test_string_locale_conversion.cpp ; +# The test takes a detail header on its own, so the pch would hide a missing include of it +run test_string_locale_conversion.cpp + : : : off ; run test_strtod.cpp ; run test_tan.cpp ; run test_tanh.cpp ; @@ -231,8 +280,10 @@ run test_tgamma.cpp ; run test_to_chars.cpp ; run test_to_string.cpp ; run test_total_ordering.cpp ; -run test_toward_zero_rounding.cpp ; -run test_upward_rounding.cpp ; +run test_toward_zero_rounding.cpp + : : : off ; +run test_upward_rounding.cpp + : : : off ; run test_zeta.cpp ; run limits_link_1.cpp limits_link_2.cpp limits_link_3.cpp ; @@ -246,8 +297,10 @@ run ../example/charconv_cohort_preservation.cpp ; run ../example/charconv_cohort_preservation_fixed.cpp ; run ../example/literals.cpp ; run ../example/rounding_mode.cpp ; -run ../example/rounding_mode_compile_time.cpp ; -run ../example/statistics.cpp ; +run ../example/rounding_mode_compile_time.cpp + : : : off ; +run ../example/statistics.cpp + : : : off ; run ../example/format.cpp ; run ../example/fmt_format.cpp ; run ../example/print.cpp ; @@ -263,26 +316,49 @@ run ../example/integral_conversions.cpp ; run ../example/decompose_frexp10_normalize.cpp ; # Test compilation of separate headers -compile compile_tests/bid_conversion.cpp ; -compile compile_tests/dpd_conversion.cpp ; -compile compile_tests/cfloat.cpp ; -compile compile_tests/charconv_compile.cpp ; -compile compile_tests/charconv_before_types_compile.cpp ; -compile compile_tests/cmath_compile.cpp ; -compile compile_tests/cstdio_compile.cpp ; -compile compile_tests/cstdlib_compile.cpp ; -compile compile_tests/decimal32_compile.cpp ; -compile compile_tests/decimal32_fast_compile.cpp ; -compile compile_tests/decimal64_compile.cpp ; -compile compile_tests/decimal64_fast_compile.cpp ; -compile compile_tests/decimal128_compile.cpp ; -compile compile_tests/decimal128_fast_compile.cpp ; -compile compile_tests/main_header.cpp ; -compile compile_tests/fenv_compile.cpp ; -compile compile_tests/format_compile.cpp ; -compile compile_tests/fwd_compile.cpp ; -compile compile_tests/iostream_compile.cpp ; -compile compile_tests/literals_compile.cpp ; -compile compile_tests/numbers_compile.cpp ; -compile compile_tests/string_compile.cpp ; -compile compile_tests/uint128.cpp ; +compile compile_tests/bid_conversion.cpp + : off ; +compile compile_tests/dpd_conversion.cpp + : off ; +compile compile_tests/cfloat.cpp + : off ; +compile compile_tests/charconv_compile.cpp + : off ; +compile compile_tests/charconv_before_types_compile.cpp + : off ; +compile compile_tests/cmath_compile.cpp + : off ; +compile compile_tests/cstdio_compile.cpp + : off ; +compile compile_tests/cstdlib_compile.cpp + : off ; +compile compile_tests/decimal32_compile.cpp + : off ; +compile compile_tests/decimal32_fast_compile.cpp + : off ; +compile compile_tests/decimal64_compile.cpp + : off ; +compile compile_tests/decimal64_fast_compile.cpp + : off ; +compile compile_tests/decimal128_compile.cpp + : off ; +compile compile_tests/decimal128_fast_compile.cpp + : off ; +compile compile_tests/main_header.cpp + : off ; +compile compile_tests/fenv_compile.cpp + : off ; +compile compile_tests/format_compile.cpp + : off ; +compile compile_tests/fwd_compile.cpp + : off ; +compile compile_tests/iostream_compile.cpp + : off ; +compile compile_tests/literals_compile.cpp + : off ; +compile compile_tests/numbers_compile.cpp + : off ; +compile compile_tests/string_compile.cpp + : off ; +compile compile_tests/uint128.cpp + : off ; diff --git a/test/pch.hpp b/test/pch.hpp new file mode 100644 index 000000000..68e4da947 --- /dev/null +++ b/test/pch.hpp @@ -0,0 +1,11 @@ +// Copyright 2026 Shen-Ta Hsieh +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// Precompiled by b2 once per build variant and force-included into each test (see Jamfile) + +// The MSVC standard library reads this when the pch parses , not when the Boost.Math +// tests define it later, so it must come first here (the define in those tests is the same) +#define _SILENCE_CXX23_DENORM_DEPRECATION_WARNING + +#include From 97bb261f1b9819860756f4842f6b1fa50dc5f493 Mon Sep 17 00:00:00 2001 From: Matt Borland Date: Wed, 23 Sep 2026 09:48:25 -0400 Subject: [PATCH 6/7] Update actions version and drop all non-conforming containers --- .github/workflows/ci.yml | 92 ++++++---------------------------------- 1 file changed, 13 insertions(+), 79 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0f938ed7..85c85c030 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,34 +34,6 @@ jobs: fail-fast: false matrix: include: - - toolset: gcc-8 - cxxstd: "03,11,14,17,2a" - address_model: 32,64 - os: ubuntu-latest - container: ubuntu:18.04 - install: - - g++-8-multilib - - toolset: gcc-9 - cxxstd: "03,11,14,17,2a" - address_model: 32,64 - os: ubuntu-latest - container: ubuntu:20.04 - install: - - g++-9-multilib - - toolset: gcc-9 - cxxstd: "03-gnu,11-gnu,14-gnu,17-gnu,2a-gnu" - address_model: 32,64 - os: ubuntu-latest - container: ubuntu:20.04 - install: - - g++-9-multilib - - toolset: gcc-10 - cxxstd: "03,11,14,17,20" - address_model: 32,64 - os: ubuntu-latest - container: ubuntu:20.04 - install: - - g++-10-multilib - toolset: gcc-11 cxxstd: "03,11,14,17,20,23" address_model: 32 @@ -145,44 +117,6 @@ jobs: - g++-14-multilib # Linux, clang - - toolset: clang - compiler: clang++-6.0 - cxxstd: "03,11,14,17" - os: ubuntu-latest - container: ubuntu:18.04 - install: - - clang-6.0 - - toolset: clang - compiler: clang++-7 - cxxstd: "03,11,14,17" - os: ubuntu-latest - container: ubuntu:18.04 - install: - - clang-7 - # Note: clang-8 does not fully support C++20, so it is not compatible with libstdc++-8 in this mode - - toolset: clang - compiler: clang++-8 - cxxstd: "03,11,14,17,2a" - os: ubuntu-latest - container: ubuntu:18.04 - install: - - clang-8 - - g++-7 - gcc_toolchain: 7 - - toolset: clang - compiler: clang++-9 - cxxstd: "03,11,14,17,2a" - os: ubuntu-latest - container: ubuntu:20.04 - install: - - clang-9 - - toolset: clang - compiler: clang++-10 - cxxstd: "03,11,14,17,20" - os: ubuntu-latest - container: ubuntu:20.04 - install: - - clang-10 - toolset: clang compiler: clang++-11 cxxstd: "03,11,14,17" @@ -350,7 +284,7 @@ jobs: fi fi git config --global pack.threads 0 - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install packages if: matrix.install @@ -559,7 +493,7 @@ jobs: runs-on: ${{matrix.os}} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Setup Boost shell: cmd @@ -601,7 +535,7 @@ jobs: runs-on: ${{matrix.os}} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install packages if: matrix.install @@ -648,7 +582,7 @@ jobs: runs-on: ${{matrix.os}} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install packages if: matrix.install @@ -705,7 +639,7 @@ jobs: runs-on: ${{matrix.os}} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install packages if: matrix.install @@ -760,7 +694,7 @@ jobs: runs-on: ${{matrix.os}} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install packages if: matrix.install @@ -818,7 +752,7 @@ jobs: runs-on: ${{matrix.os}} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Configure and Install run: | @@ -870,7 +804,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install packages run: sudo apt install pkg-config @@ -950,7 +884,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install packages run: sudo apt install pkg-config @@ -1022,7 +956,7 @@ jobs: # runs-on: windows-latest # # steps: -# - uses: actions/checkout@v4 +# - uses: actions/checkout@v6 # # - name: Setup MSYS2 environment # uses: msys2/setup-msys2@v2 @@ -1033,7 +967,7 @@ jobs: # pacboy: gcc:p cmake:p ninja:p # # - name: Fetch Boost.CI -# uses: actions/checkout@v4 +# uses: actions/checkout@v6 # with: # repository: boostorg/boost-ci # ref: master @@ -1063,7 +997,7 @@ jobs: compiler: [ intel ] standard: [ 14, 17, 20, 23 ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: '0' @@ -1142,7 +1076,7 @@ jobs: IMPORT_STD_UUID: f35a9ac6-8463-4d38-8eec-5d6008153e7d steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install toolchain run: | From fb7d32cb11000eb96461bb0d90b6a419a888e93d Mon Sep 17 00:00:00 2001 From: Shen-Ta Hsieh Date: Wed, 23 Sep 2026 23:03:33 +0800 Subject: [PATCH 7/7] Drop the Node 20 workaround with the containers which needed it - Only the Ubuntu 16 and 18 containers could not run the Node 20 of actions/checkout, and the matrix has none of them any more. - The mount of /node20217 and the download into it are therefore dead, so both go. - ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION goes too, because no action of ci.yml needs an old Node any more. --- .github/workflows/ci.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 85c85c030..7bb06c2e9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,6 @@ env: GIT_FETCH_JOBS: 8 NET_RETRY_COUNT: 5 DEFAULT_BUILD_VARIANT: debug,release - ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true jobs: posix: @@ -252,9 +251,6 @@ jobs: runs-on: ${{matrix.os}} container: image: ${{matrix.container}} - volumes: - - /node20217:/node20217:rw,rshared - - ${{ startsWith(matrix.container, 'ubuntu:1') && '/node20217:/__e/node20:ro,rshared' || ' ' }} steps: - name: Setup environment @@ -278,10 +274,6 @@ jobs: fi apt-get -o Acquire::Retries=$NET_RETRY_COUNT install -y sudo software-properties-common tzdata wget curl apt-transport-https ca-certificates make build-essential g++ $PYTHON_PACKAGE python3 perl git cmake fi - if [[ "${{matrix.container}}" == "ubuntu:1"* ]]; then - # Node 20 doesn't work with Ubuntu 16/18 glibc: https://github.com/actions/checkout/issues/1590 - curl -sL https://archives.boost.io/misc/node/node-v20.9.0-linux-x64-glibc-217.tar.xz | tar -xJ --strip-components 1 -C /node20217 - fi fi git config --global pack.threads 0 - uses: actions/checkout@v6