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
32 changes: 12 additions & 20 deletions doc/modules/ROOT/pages/basics.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -119,27 +119,19 @@ For more information see: xref:cfenv.adoc[]
The entire library can be accessed using the convenience header `<boost/decimal.hpp>`.
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 <boost/decimal.hpp>
#include <iostream>
#include <iomanip>

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
....
====
83 changes: 32 additions & 51 deletions doc/modules/ROOT/pages/cfenv.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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 <boost/decimal.hpp>
#include <cassert>

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.
Expand All @@ -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 <boost/decimal.hpp>

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 `<boost/decimal/fenv.hpp>`, but has been changed to `<boost/decimal/cfenv.hpp>` for consistency with the STL naming convention.
26 changes: 26 additions & 0 deletions examples/first_example.cpp
Original file line number Diff line number Diff line change
@@ -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 <boost/decimal.hpp> // This includes the entire decimal library
#include <iostream>
#include <iomanip>

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;
}
84 changes: 66 additions & 18 deletions examples/rounding_mode.cpp
Original file line number Diff line number Diff line change
@@ -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 <boost/decimal.hpp>
#include <cassert>
#include <boost/decimal/decimal32_t.hpp> // For type decimal32_t
#include <boost/decimal/literals.hpp> // For decimal literals
#include <boost/decimal/cfenv.hpp> // For access to the rounding mode functions
#include <boost/decimal/iostream.hpp> // Decimal support to <iostream>
#include <iostream>

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;
}

57 changes: 51 additions & 6 deletions examples/rounding_mode_compile_time.cpp
Original file line number Diff line number Diff line change
@@ -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 <boost/decimal.hpp>

#include <boost/decimal/decimal32_t.hpp> // For type decimal32_t
#include <boost/decimal/literals.hpp> // For decimal literals
#include <boost/decimal/iostream.hpp> // For decimal <iostream> support
#include <boost/decimal/cfenv.hpp> // For rounding mode access
#include <iostream>

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());
}
1 change: 1 addition & 0 deletions test/Jamfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 ;
Expand Down