diff --git a/doc/modules/ROOT/pages/basics.adoc b/doc/modules/ROOT/pages/basics.adoc index a0b80bec2..188924cfe 100644 --- a/doc/modules/ROOT/pages/basics.adoc +++ b/doc/modules/ROOT/pages/basics.adoc @@ -119,27 +119,19 @@ For more information see: xref:cfenv.adoc[] The entire library can be accessed using the convenience header ``. A short example of the basic usage: +.This https://github.com/cppalliance/decimal/blob/develop/examples/first_example.cpp[example] shows the very basics on how to make a complete and working program using the decimal library +==== [source, c++] ---- -#include -#include -#include - -int main() -{ - using namespace boost::decimal; - - // Outputs 0.30000000000000004 - std::cout << std::setprecision(17) << 0.1 + 0.2 << std::endl; - - // Construct the two decimal values - constexpr decimal64_t a {1, -1}; // 1e-1 or 0.1 - constexpr decimal64_t b {2, -1}; // 2e-1 or 0.2 +include::example$first_example.cpp[] +---- - // Outputs 0.30000000000000000 - std::cout << a + b << std::endl; +.Expected Output +.... +Using doubles: +0.1 + 0.2 = 0.30000000000000004 - return 0; -} - ----- +Using decimal64_t: +0.1_DD + 0.2_DD = 0.3000000000000000 +.... +==== diff --git a/doc/modules/ROOT/pages/cfenv.adoc b/doc/modules/ROOT/pages/cfenv.adoc index d46320907..77ccd7803 100644 --- a/doc/modules/ROOT/pages/cfenv.adoc +++ b/doc/modules/ROOT/pages/cfenv.adoc @@ -57,55 +57,40 @@ rounding_mode fesetround(rounding_mode round) noexcept; 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: +[#examples_rounding_mode] +.This https://github.com/cppalliance/decimal/blob/develop/examples/rounding_mode.cpp[example] demonstrates how to set, get, and effects of the global rounding mode. +==== [source, c++] ---- -#include -#include - -int main() -{ - using namespace boost::decimal::literals; - - 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); - - 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; -} +include::example$rounding_mode.cpp[] ---- +.Expected Output: +.... +The default rounding mode is: fe_dec_to_nearest +The current rounding mode is: fe_dec_upward +lhs equals: 5e+50 +rhs equals: 4e+40 + Sum with upward rounding: 5.000001e+50 +The current rounding mode is: fe_dec_downward +Sum with downward rounding: 4.999999e+50 +.... +==== + 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 +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). +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. @@ -117,22 +102,18 @@ This same example can be reduced for the cases where: 2. You want to change the compile time rounding mode +.This https://github.com/cppalliance/decimal/blob/develop/examples/rounding_mode.cpp[example] demonstrates how the compile time rounding mode can be set, and it's effects on compile time and run time evaluation. +==== [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; -} +include::example$rounding_mode_compile_time.cpp[] ---- +.Expected Output +.... +The default rounding mode is: fe_dec_to_nearest +The current rounding mode is: fe_dec_downward +.... +==== + IMPORTANT: Prior to v5.2.0 this header was ``, but has been changed to `` for consistency with the STL naming convention. diff --git a/examples/first_example.cpp b/examples/first_example.cpp new file mode 100644 index 000000000..9c2f486c5 --- /dev/null +++ b/examples/first_example.cpp @@ -0,0 +1,26 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include // This includes the entire decimal library +#include +#include + +int main() +{ + using namespace boost::decimal::literals; // The literals are in their own namespace like std::literals + + // First we show the result of 0.1 + 0.2 using regular doubles + std::cout << std::fixed << std::setprecision(17) + << "Using doubles:\n" + << "0.1 + 0.2 = " << 0.1 + 0.2 << "\n\n"; + + // Construct the two decimal values + // We construct using the literals defined by the library + constexpr boost::decimal::decimal64_t a {0.1_DD}; + constexpr boost::decimal::decimal64_t b {0.2_DD}; + + // Now we display the result of the same calculation using decimal64_t + std::cout << "Using decimal64_t:\n" + << "0.1_DD + 0.2_DD = " << a + b << std::endl; +} diff --git a/examples/rounding_mode.cpp b/examples/rounding_mode.cpp index 3f6feae4f..f41c0788a 100644 --- a/examples/rounding_mode.cpp +++ b/examples/rounding_mode.cpp @@ -1,39 +1,87 @@ -// Copyright 2024 Matt Borland +// Copyright 2024 - 2025 Matt Borland // Distributed under the Boost Software License, Version 1.0. // https://www.boost.org/LICENSE_1_0.txt +// +// This example demonstrates how to set and get the global rounding mode +// as well as the effects on numerical results -#include -#include +#include // For type decimal32_t +#include // For decimal literals +#include // For access to the rounding mode functions +#include // Decimal support to +#include + +void print_rounding_mode(const boost::decimal::rounding_mode current_mode) +{ + // All 5 rounding modes are defined by the enum rounding_mode + using boost::decimal::rounding_mode; + + switch (current_mode) + { + case rounding_mode::fe_dec_downward: + std::cout << "fe_dec_downward\n"; + break; + case rounding_mode::fe_dec_to_nearest: + std::cout << "fe_dec_to_nearest\n"; + break; + case rounding_mode::fe_dec_to_nearest_from_zero: + std::cout << "fe_dec_to_nearest_from_zero\n"; + break; + case rounding_mode::fe_dec_toward_zero: + std::cout << "fe_dec_toward_zero\n"; + break; + case rounding_mode::fe_dec_upward: + std::cout << "fe_dec_upward\n"; + break; + } +} int main() { - #ifndef BOOST_DECIMAL_NO_CONSTEVAL_DETECTION + // The rounding mode can only be changed at run-time if the compiler supports + // 1. C++20 std::is_constant_evaluated() + // 2. Intrinsics that do the same + // If neither of the above are defined the library defines BOOST_DECIMAL_NO_CONSTEVAL_DETECTION - using namespace boost::decimal::literals; + // The current rounding mode can be queried with boost::decimal::fegetround + const boost::decimal::rounding_mode default_rounding_mode = boost::decimal::fegetround(); + std::cout << "The default rounding mode is: "; + print_rounding_mode(default_rounding_mode); - BOOST_DECIMAL_ATTRIBUTE_UNUSED auto default_rounding_mode = boost::decimal::fegetround(); // Default is fe_dec_to_nearest + // To set a new rounding mode use boost::decimal::fesetround + // fesetround returns current mode after updating the global state + // + // If your compiler set defines BOOST_DECIMAL_NO_CONSTEVAL_DETECTION the global state can not be updated, + // so this can be a useful check to make sure that state is what you expect it to be + auto new_rounding_mode = boost::decimal::fesetround(boost::decimal::rounding_mode::fe_dec_upward); + std::cout << "The current rounding mode is: "; + print_rounding_mode(new_rounding_mode); - BOOST_DECIMAL_ATTRIBUTE_UNUSED auto new_rounding_mode = boost::decimal::fesetround(boost::decimal::rounding_mode::fe_dec_upward); + #ifndef BOOST_DECIMAL_NO_CONSTEVAL_DETECTION + + using namespace boost::decimal::literals; + using boost::decimal::decimal32_t; - assert(default_rounding_mode != new_rounding_mode); + const decimal32_t lhs {"5e+50"_DF}; + const decimal32_t rhs {"4e+40"_DF}; - const auto lhs {"5e+50"_DF}; - const auto rhs {"4e+40"_DF}; + std::cout << "lhs equals: " << lhs << '\n' + << "rhs equals: " << rhs << '\n'; // 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); + const decimal32_t upward_res {lhs + rhs}; + std::cout << " Sum with upward rounding: " << upward_res << '\n'; - boost::decimal::fesetround(boost::decimal::rounding_mode::fe_dec_downward); + + new_rounding_mode = boost::decimal::fesetround(boost::decimal::rounding_mode::fe_dec_downward); + std::cout << "The current rounding mode is: "; + print_rounding_mode(new_rounding_mode); // 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); + const decimal32_t downward_res {lhs - rhs}; + std::cout << "Sum with downward rounding: " << downward_res << '\n'; #endif // BOOST_DECIMAL_NO_CONSTEVAL_DETECTION - - return 0; } - diff --git a/examples/rounding_mode_compile_time.cpp b/examples/rounding_mode_compile_time.cpp index 18ce6b96c..0ae0a5843 100644 --- a/examples/rounding_mode_compile_time.cpp +++ b/examples/rounding_mode_compile_time.cpp @@ -1,18 +1,63 @@ // Copyright 2024 Matt Borland // Distributed under the Boost Software License, Version 1.0. // https://www.boost.org/LICENSE_1_0.txt - +// +// To define a global compile-time rounding mode +// you must define the macro before inclusion of *ANY* decimal header #define BOOST_DECIMAL_FE_DEC_DOWNWARD -#include + +#include // For type decimal32_t +#include // For decimal literals +#include // For decimal support +#include // For rounding mode access +#include + +void print_rounding_mode(const boost::decimal::rounding_mode current_mode) +{ + // All 5 rounding modes are defined by the enum rounding_mode + using boost::decimal::rounding_mode; + + switch (current_mode) + { + case rounding_mode::fe_dec_downward: + std::cout << "fe_dec_downward\n"; + break; + case rounding_mode::fe_dec_to_nearest: + std::cout << "fe_dec_to_nearest\n"; + break; + case rounding_mode::fe_dec_to_nearest_from_zero: + std::cout << "fe_dec_to_nearest_from_zero\n"; + break; + case rounding_mode::fe_dec_toward_zero: + std::cout << "fe_dec_toward_zero\n"; + break; + case rounding_mode::fe_dec_upward: + std::cout << "fe_dec_upward\n"; + break; + } +} int main() { using namespace boost::decimal::literals; + using boost::decimal::decimal32_t; - constexpr auto lhs {"5e+50"_DF}; - constexpr auto rhs {"4e+40"_DF}; - constexpr auto downward_res {lhs - rhs}; + // This uses one of the same examples from our runtime rounding mode example + // Now we can see the effects on the generation of constants, + // since we can static_assert the result + + constexpr decimal32_t lhs {"5e+50"_DF}; + constexpr decimal32_t rhs {"4e+40"_DF}; + constexpr decimal32_t downward_res {lhs - rhs}; static_assert(downward_res == "4.999999e+50"_DF, "Incorrectly rounded result"); - return 0; + std::cout << "The default rounding mode is: "; + print_rounding_mode(boost::decimal::rounding_mode::fe_dec_default); + + // Here we can see that the rounding mode has been set to something besides default + // without having had to call fesetround + // + // This works with all compilers unlike changing the rounding mode at run-time + std::cout << "The current rounding mode is: "; + print_rounding_mode(boost::decimal::fegetround()); } diff --git a/test/Jamfile b/test/Jamfile index 9627c25b6..8902b0a3a 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -205,6 +205,7 @@ run ../examples/fmt_format.cpp ; run ../examples/print.cpp ; run ../examples/promotion.cpp ; run ../examples/numerical_parsing.cpp ; +run ../examples/first_example.cpp ; # Test compilation of separate headers compile compile_tests/bid_conversion.cpp ;