diff --git a/doc/modules/ROOT/pages/cfenv.adoc b/doc/modules/ROOT/pages/cfenv.adoc index bb3bba9fd..51ce044dd 100644 --- a/doc/modules/ROOT/pages/cfenv.adoc +++ b/doc/modules/ROOT/pages/cfenv.adoc @@ -8,6 +8,9 @@ https://www.boost.org/LICENSE_1_0.txt = `` support :idprefix: cfenv_ +WARNING: This is an expert feature. +If you have never used `std::fegetround` nor `std::fesetround` before, it is unlikely you will need anything described on this page + == IEEE 754 defines 5 rounding modes for Decimal Floating Point Types. @@ -18,7 +21,7 @@ IEEE 754 defines 5 rounding modes for Decimal Floating Point Types. 4. Toward zero 5. Upward -NOTE: The default rounding mode is to nearest with ties to even (#2) as specified in IEEE 754 Section 4.3.3 +NOTE: The default rounding mode is to nearest with ties to even (#2) as specified in IEEE 754 Section 4.3.3. This is also colloquially known as "Banker's Rounding" Using the following `enum class` and functions you can change the rounding mode from the default at *RUNTIME* only if xref:config.adoc#configuration_automatic[`BOOST_DECIMAL_NO_CONSTEVAL_DETECTION`] is not defined. @@ -31,11 +34,11 @@ namespace decimal { enum class rounding_mode : unsigned { - fe_dec_downward = 1 << 0, - fe_dec_to_nearest = 1 << 1, - fe_dec_to_nearest_from_zero = 1 << 2, - fe_dec_toward_zero = 1 << 3, - fe_dec_upward = 1 << 4, + fe_dec_downward, + fe_dec_to_nearest, + fe_dec_to_nearest_from_zero, + fe_dec_toward_zero, + fe_dec_upward, fe_dec_default = fe_dec_to_nearest }; @@ -52,35 +55,84 @@ rounding_mode fesetround(rounding_mode round) noexcept; } //namespace boost ---- -You can similarly change the default rounding mode at compile time with any compiler (unlike at runtime) using similarly named macros: +IMPORTANT: Much like `std::fesetround`, `boost::decimal::fesetround` is not thread safe. + +Below is an example of the effects of changing the runtime rounding mode if your compiler supports it: [source, c++] ---- +#include +#include -namespace boost { -namespace decimal { +int main() +{ + using namespace boost::decimal::literals; -static constexpr rounding_mode _boost_decimal_global_rounding_mode { - #if defined(BOOST_DECIMAL_FE_DEC_DOWNWARD) - rounding_mode::fe_dec_downward - #elif defined(BOOST_DECIMAL_FE_DEC_TO_NEAREST) - rounding_mode::fe_dec_to_nearest - #elif defined(BOOST_DECIMAL_FE_DEC_TO_NEAREST_FROM_ZERO) - rounding_mode::fe_dec_to_nearest_from_zero - #elif defined (BOOST_DECIMAL_FE_DEC_TOWARD_ZERO) - rounding_mde::fe_dec_toward_zero - #elif defined(BOOST_DECIMAL_FE_DEC_UPWARD) - rounding_mode::fe_dec_upward - #else - rounding_mode::fe_dec_default - #endif -}; + auto default_rounding_mode = boost::decimal::fegetround(); // Default is fe_dec_to_nearest + + auto new_rounding_mode = boost::decimal::fesetround(boost::decimal::rounding_mode::fe_dec_upward); -} // namespace decimal -} // namespace boost + assert(default_rounding_mode != new_rounding_mode); + const auto lhs {"5e+50"_DF}; + const auto rhs {"4e+40"_DF}; + + // With upward rounding the result will be "5.000001e+50"_DF + // Even though the difference in order of magnitude is greater than the precision of the type, + // any addition in this mode will result in at least a one ULP difference + const auto upward_res {lhs + rhs}; + assert(upward_res == "5.000001e+50"_DF); + + boost::decimal::fesetround(boost::decimal::rounding_mode::fe_dec_downward); + + // Similar to above in the downward rounding mode any subtraction will result in at least a one ULP difference + const auto downward_res {lhs - rhs}; + assert(downward_res == "4.999999e+50"_DF); + + return 0; +} ---- -The default rounding mode for compile time is the same as the default runtime. +As shown, changing the rounding mode *WILL* change your numerical results. +If you are coming from the Intel library (or other C-style libs) where every mathematical function takes a rounding mode, that is not the case in this library; the only way to change the rounding mode for individual operations is via the global rounding mode. +Before attempting to change the rounding mode ensure this is actually what you want to happen. + +You can similarly change the default rounding mode at compile time with *ANY* compiler (unlike at runtime) using similarly named macros: + +1. BOOST_DECIMAL_FE_DEC_DOWNWARD +2. BOOST_DECIMAL_FE_DEC_TO_NEAREST +3. BOOST_DECIMAL_FE_DEC_TO_NEAREST_FROM_ZERO +4. BOOST_DECIMAL_FE_DEC_TOWARD_ZERO +5. BOOST_DECIMAL_FE_DEC_UPWARD + +If none of the above macros are defined, the default rounding mode for compile time is the same as the default runtime (i.e. as if BOOST_DECIMAL_FE_DEC_TO_NEAREST were defined). +At most *ONE* of these macros are allowed to be user defined. +A `#error` will be emitted if more than one is detected. +The macro must be defined before the inclusion of any decimal library header. +The rounding mode set at compile time is thread-safe as it is read only. + +This same example can be reduced for the cases where: + +1. Compiler does not support runtime rounding mode changes + +2. You want to change the compile time rounding mode + +[source, c++] +---- +#define BOOST_DECIMAL_FE_DEC_DOWNWARD +#include + +int main() +{ + using namespace boost::decimal::literals; + + constexpr auto lhs {"5e+50"_DF}; + constexpr auto rhs {"4e+40"_DF}; + constexpr auto downward_res {lhs - rhs}; + static_assert(downward_res == "4.999999e+50"_DF, "Incorrectly rounded result"); + + return 0; +} +---- IMPORTANT: Prior to v5.2.0 this header was ``, but has been changed to `` for consistency with the STL naming convention. diff --git a/doc/modules/ROOT/pages/examples.adoc b/doc/modules/ROOT/pages/examples.adoc index a09b7ec65..fde8055a2 100644 --- a/doc/modules/ROOT/pages/examples.adoc +++ b/doc/modules/ROOT/pages/examples.adoc @@ -101,25 +101,6 @@ Output: Returned Value: 0.25 ---- -[#examples_rounding_mode] -== Rounding Mode -[source, c++] ----- -#include -#include - -int main() -{ - auto default_rounding_mode = boost::decimal::fegetround(); // Default is fe_dec_to_nearest_from_zero - - auto new_rounding_mode = boost::decimal::fesetround(boost::decimal::rounding_mode::fe_dec_to_nearest); - - assert(default_rounding_mode != new_rounding_mode); - - return 0; -} ----- - [#examples_generic_programming] == Generic Programming [source, c++] diff --git a/examples/rounding_mode.cpp b/examples/rounding_mode.cpp index 67445d1d1..3f6feae4f 100644 --- a/examples/rounding_mode.cpp +++ b/examples/rounding_mode.cpp @@ -7,12 +7,33 @@ int main() { + #ifndef BOOST_DECIMAL_NO_CONSTEVAL_DETECTION + + using namespace boost::decimal::literals; + BOOST_DECIMAL_ATTRIBUTE_UNUSED auto default_rounding_mode = boost::decimal::fegetround(); // Default is fe_dec_to_nearest - BOOST_DECIMAL_ATTRIBUTE_UNUSED auto new_rounding_mode = boost::decimal::fesetround(boost::decimal::rounding_mode::fe_dec_to_nearest_from_zero); + BOOST_DECIMAL_ATTRIBUTE_UNUSED auto new_rounding_mode = boost::decimal::fesetround(boost::decimal::rounding_mode::fe_dec_upward); assert(default_rounding_mode != new_rounding_mode); + const auto lhs {"5e+50"_DF}; + const auto rhs {"4e+40"_DF}; + + // With upward rounding the result will be "5.000001e+50"_DF + // Even though the difference in order of magnitude is greater than the precision of the type, + // any addition in this mode will result in at least a one ULP difference + BOOST_DECIMAL_ATTRIBUTE_UNUSED const auto upward_res {lhs + rhs}; + assert(upward_res == "5.000001e+50"_DF); + + boost::decimal::fesetround(boost::decimal::rounding_mode::fe_dec_downward); + + // Similar to above in the downward rounding mode any subtraction will result in at least a one ULP difference + BOOST_DECIMAL_ATTRIBUTE_UNUSED const auto downward_res {lhs - rhs}; + assert(downward_res == "4.999999e+50"_DF); + + #endif // BOOST_DECIMAL_NO_CONSTEVAL_DETECTION + return 0; } diff --git a/examples/rounding_mode_compile_time.cpp b/examples/rounding_mode_compile_time.cpp new file mode 100644 index 000000000..18ce6b96c --- /dev/null +++ b/examples/rounding_mode_compile_time.cpp @@ -0,0 +1,18 @@ +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#define BOOST_DECIMAL_FE_DEC_DOWNWARD +#include + +int main() +{ + using namespace boost::decimal::literals; + + constexpr auto lhs {"5e+50"_DF}; + constexpr auto rhs {"4e+40"_DF}; + constexpr auto downward_res {lhs - rhs}; + static_assert(downward_res == "4.999999e+50"_DF, "Incorrectly rounded result"); + + return 0; +} diff --git a/include/boost/decimal/cfenv.hpp b/include/boost/decimal/cfenv.hpp index 6acba7ad4..a995ef33a 100644 --- a/include/boost/decimal/cfenv.hpp +++ b/include/boost/decimal/cfenv.hpp @@ -7,6 +7,46 @@ #include +#ifdef BOOST_DECIMAL_FE_DEC_DOWNWARD +# define BOOST_DECIMAL_MODE_1 1 +#else +# define BOOST_DECIMAL_MODE_1 0 +#endif + +#ifdef BOOST_DECIMAL_FE_DEC_TO_NEAREST +# define BOOST_DECIMAL_MODE_2 1 +#else +# define BOOST_DECIMAL_MODE_2 0 +#endif + +#ifdef BOOST_DECIMAL_FE_DEC_TO_NEAREST_FROM_ZERO +# define BOOST_DECIMAL_MODE_3 1 +#else +# define BOOST_DECIMAL_MODE_3 0 +#endif + +#ifdef BOOST_DECIMAL_FE_DEC_TOWARD_ZERO +# define BOOST_DECIMAL_MODE_4 1 +#else +# define BOOST_DECIMAL_MODE_4 0 +#endif + +#ifdef BOOST_DECIMAL_FE_DEC_UPWARD +# define BOOST_DECIMAL_MODE_5 1 +#else +# define BOOST_DECIMAL_MODE_5 0 +#endif + +// Now we can safely use arithmetic in preprocessor +#define BOOST_DECIMAL_MODE_COUNT \ +(BOOST_DECIMAL_MODE_1 + BOOST_DECIMAL_MODE_2 + \ +BOOST_DECIMAL_MODE_3 + BOOST_DECIMAL_MODE_4 + \ +BOOST_DECIMAL_MODE_5) + +#if BOOST_DECIMAL_MODE_COUNT > 1 +# error "Only one rounding mode macro can be defined" +#endif + #ifndef BOOST_DECIMAL_BUILD_MODULE #include #endif @@ -57,7 +97,7 @@ BOOST_DECIMAL_EXPORT inline auto fesetround(BOOST_DECIMAL_ATTRIBUTE_UNUSED const _boost_decimal_global_runtime_rounding_mode = round; #endif - return round; + return _boost_decimal_global_runtime_rounding_mode; } } // namespace decimal diff --git a/include/boost/decimal/detail/config.hpp b/include/boost/decimal/detail/config.hpp index 848ccca92..72be6f1b7 100644 --- a/include/boost/decimal/detail/config.hpp +++ b/include/boost/decimal/detail/config.hpp @@ -230,6 +230,18 @@ typedef unsigned __int128 builtin_uint128_t; # endif #endif +// Clang < 12 will detect availability, but has issues +#ifdef BOOST_DECIMAL_HAS_BUILTIN_IS_CONSTANT_EVALUATED +# if defined(__clang__) && __clang_major__ < 12 +# ifdef BOOST_DECIMAL_HAS_BUILTIN_IS_CONSTANT_EVALUATED +# undef BOOST_DECIMAL_HAS_BUILTIN_IS_CONSTANT_EVALUATED +# endif +# ifdef BOOST_DECIMAL_HAS_IS_CONSTANT_EVALUATED +# undef BOOST_DECIMAL_HAS_IS_CONSTANT_EVALUATED +# endif +# endif +#endif + // // MSVC also supports __builtin_is_constant_evaluated if it's recent enough: // diff --git a/test/Jamfile b/test/Jamfile index e7a56586c..dc1b781dc 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -68,6 +68,7 @@ run github_issue_1055.cpp ; run github_issue_1057.cpp ; compile-fail github_issue_1087.cpp ; run github_issue_1091.cpp ; +run github_issue_1094.cpp ; run link_1.cpp link_2.cpp link_3.cpp ; run quick.cpp ; run random_decimal32_comp.cpp ; @@ -181,6 +182,7 @@ run ../examples/bit_conversions.cpp ; run ../examples/charconv.cpp ; run ../examples/literals.cpp ; run ../examples/rounding_mode.cpp ; +run ../examples/rounding_mode_compile_time.cpp ; run ../examples/moving_average.cpp ; run ../examples/currency_conversion.cpp ; run ../examples/statistics.cpp ; diff --git a/test/github_issue_1026.cpp b/test/github_issue_1026.cpp index e82d2c00c..5a75afc42 100644 --- a/test/github_issue_1026.cpp +++ b/test/github_issue_1026.cpp @@ -66,10 +66,14 @@ int main() BOOST_TEST_EQ("1"_DF / "5.24289e-96"_DF, "1.907345e+95"_DF); + #ifndef BOOST_DECIMAL_NO_CONSTEVAL_DETECTION + const auto new_rounding_mode = fesetround(rounding_mode::fe_dec_to_nearest_from_zero); BOOST_TEST(new_rounding_mode == rounding_mode::fe_dec_to_nearest_from_zero); BOOST_TEST_EQ(decimal32_t(100000, -105), "1e-100"_df); + #endif // BOOST_DECIMAL_NO_CONSTEVAL_DETECTION + return boost::report_errors(); } diff --git a/test/github_issue_1094.cpp b/test/github_issue_1094.cpp new file mode 100644 index 000000000..33e7eaf91 --- /dev/null +++ b/test/github_issue_1094.cpp @@ -0,0 +1,34 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// +// See: https://github.com/cppalliance/decimal/issues/1094 + +#include +#include + +using namespace boost::decimal; + +int main() +{ + BOOST_TEST(boost::decimal::fegetround() == _boost_decimal_global_rounding_mode); + BOOST_TEST(boost::decimal::fegetround() == _boost_decimal_global_runtime_rounding_mode); + + #ifndef BOOST_DECIMAL_NO_CONSTEVAL_DETECTION + + BOOST_TEST(fesetround(rounding_mode::fe_dec_upward) == rounding_mode::fe_dec_upward); + BOOST_TEST(boost::decimal::fegetround() != _boost_decimal_global_rounding_mode); + BOOST_TEST(boost::decimal::fegetround() == _boost_decimal_global_runtime_rounding_mode); + BOOST_TEST(boost::decimal::fegetround() == rounding_mode::fe_dec_upward); + + #else + + BOOST_TEST(fesetround(rounding_mode::fe_dec_upward) == _boost_decimal_global_rounding_mode); + BOOST_TEST(boost::decimal::fegetround() == _boost_decimal_global_rounding_mode); + BOOST_TEST(boost::decimal::fegetround() == _boost_decimal_global_runtime_rounding_mode); + BOOST_TEST(boost::decimal::fegetround() == rounding_mode::fe_dec_default); + + #endif + + return boost::report_errors(); +}