Examples
All examples can be found in the library examples/ folder as well.
Basic construction
This example can be found in the examples/ folder as basic_construction.cpp
// Copyright 2024 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
//
// This file demonstrates some of the very basic ways you con construct decimal types
// This includes: from integer; integer and exponent; integer exponent and sign; string
#include <boost/decimal/decimal32_t.hpp> // For decimal32_t type
#include <boost/decimal/iostream.hpp> // For <iostream> support of decimal types
#include <boost/decimal/cmath.hpp> // For isnan and isinf
#include <string>
#include <iostream>
int main()
{
using boost::decimal::decimal32_t; // The decimal32_t type
using boost::decimal::construction_sign; // An enum class for specifying sign during certain instances of construction
using boost::decimal::isinf; // Analogous to std::isinf but for decimal types
using boost::decimal::isnan; // Analogous to std::isnan but for decimal types
// Construction from an integer
constexpr decimal32_t val_1 {100};
// Construction from a signed integer and exponent
constexpr decimal32_t val_2 {10, 1};
// Construction from an unsigned integer, exponent, and sign
// The sign enum is named construction_sign, and has two members: positive and negative
constexpr decimal32_t val_3 {1U, 2, construction_sign::negative};
std::cout << "Val_1: " << val_1 << '\n'
<< "Val_2: " << val_2 << '\n'
<< "Val_3: " << val_3 << '\n';
if (val_1 == val_2 && val_2 == val_3 && val_1 == val_3)
{
std::cout << "All equal values" << std::endl;
}
// Demonstration of the overflow and underflow handling
// A value that overflows constructs an infinity (which can be queried using isinf)
// A value that underflows constructs a zero
constexpr decimal32_t overflow_value {100, 10000};
if (isinf(overflow_value))
{
std::cout << "Overflow constructs infinity" << std::endl;
}
constexpr decimal32_t underflow_value {100, -10000};
constexpr decimal32_t zero {0};
if (underflow_value == zero)
{
std::cout << "Underflow constructs zero" << std::endl;
}
// Construction of NANs can be done using numeric limits,
// and checked using the normal isnan function
constexpr decimal32_t non_finite_from_float {std::numeric_limits<decimal32_t>::quiet_NaN()};
if (isnan(non_finite_from_float))
{
std::cout << "NaN constructs NaN" << std::endl;
}
// We can also construct both from a C-string (const char*) and from a std::string
const char* c_string_value {"4.3e-02"};
const decimal32_t from_c_string {c_string_value};
const decimal32_t from_std_string {std::string(c_string_value)};
if (from_c_string == from_std_string)
{
std::cout << "Values constructed from const char* and std::string are the same" << '\n';
}
// If we attempt construction for a string that cannot be converted into a decimal value,
// the constructor will do 1 of 2 things:
// 1) In a noexcept environment the constructor will return a quiet NaN
// 2) Otherwise it will throw
//
// The exception environment is detected automatically,
// or can be set by defining BOOST_DECIMAL_DISABLE_EXCEPTIONS
const char* bad_string {"Junk_String"};
#ifndef BOOST_DECIMAL_DISABLE_EXCEPTIONS
try
{
const decimal32_t throwing_value {bad_string};
std::cout << throwing_value << '\n';
}
catch (const std::runtime_error& e)
{
std::cout << e.what() << std::endl;
}
#else
const decimal32_t nan_value {bad_string};
if (isnan(nan_value))
{
std::cout << "Bad string construction has formed a NAN" << std::endl;
}
#endif
}
The expected output from this example is:
Val_1: 100 Val_2: 100 Val_3: -100 Overflow constructs infinity Underflow constructs zero NaN constructs NaN Values constructed from const char* and std::string are the same Can not construct from invalid string
Promotion and Mixed Decimal Arithmetic
This example can be found in the examples/ folder as promotion.cpp
// This file briefly demonstrates the results of mixed decimal comparisons and arithmetic
#include <boost/decimal/decimal32_t.hpp> // For the type decimal32_t
#include <boost/decimal/decimal64_t.hpp> // For the type decimal64_t
#include <boost/decimal/iostream.hpp> // For decimal type support to <iostream>
#include <type_traits>
#include <iostream>
int main()
{
using boost::decimal::decimal32_t;
using boost::decimal::decimal64_t;
using boost::decimal::decimal128_t;
// First construct two values that we will perform arithmetic with
const decimal32_t a {"5.2"};
const decimal64_t b {"3.9"};
std::cout << "decimal32_t value (a): " << a << '\n'
<< "decimal64_t value (b): " << b << '\n';
// Mixed decimal comparisons are allowed by default
if (a > b)
{
std::cout << "a is greater than b" << '\n';
}
// Even comparison of unrepresentable values is fine
// For example decimal32_t can't represent decimal64_t max value
constexpr decimal64_t dec64_max {std::numeric_limits<decimal64_t>::max()};
if (a < dec64_max)
{
std::cout << a << " is less than " << dec64_max << '\n';
}
// Danger awaits if you decide to do this yourself instead of letting the system do it for you,
// since in this example the two should compare equal but overflowing decimal32_t makes infinity
if (static_cast<decimal32_t>(dec64_max) < dec64_max)
{
std::cout << dec64_max << " is less than " << static_cast<decimal32_t>(dec64_max) << '\n';
}
// With mixed operations like +, -, *, / we promote to the higher precision type
// Example: decimal32_t + decimal64_t -> decimal64_t
// We use auto here for two reasons
// 1) To demonstrate that it's safe
// 2) To show the promotion with the conditional logic that follows
const auto c {a + b};
using c_type = std::remove_cv_t<decltype(c)>; // We used const auto so the result is const decimal64_t
static_assert(std::is_same<c_type, decimal64_t>::value, "decimal32_t + decimal64_t is supposed to yield decimal64_t");
std::cout << "The result of a + b is a decimal64_t: " << c << '\n';
return 0;
}
Expected Output:
decimal32_t value (a): 5.2 decimal64_t value (b): 3.9 a is greater than b 5.2 is less than 1e+385 1e+385 is now less than inf The result of a + b is a decimal64_t: 9.1
<charconv>
This example can be found in the examples/ folder as charconv.cpp
// This file demonstrates the various ways that <charconv> support can be used with the decimal library
// NOTE: <charconv> need not be included to use this functionality
#include <boost/decimal/decimal64_t.hpp> // For the type decimal64_t
#include <boost/decimal/charconv.hpp> // For <charconv> support
#include <boost/decimal/iostream.hpp> // For decimal support <iostream>
#include <iostream> // <iostream>
#include <cstring> // For std::strlen
int main()
{
using boost::decimal::decimal64_t; // The type decimal64_t
using boost::decimal::to_chars; // The to_chars functions
using boost::decimal::to_chars_result; // The return type of to_chars
using boost::decimal::from_chars; // The from_chars functions
using boost::decimal::from_chars_result;// The return type of from_chars
using boost::decimal::chars_format; // The enum class of the different formatting options
using boost::decimal::formatting_limits;// Allows the user to correctly size buffers
const char* initial_value {"-7.12345e+06"};
decimal64_t initial_decimal;
const from_chars_result r_initial {from_chars(initial_value, initial_value + std::strlen(initial_value), initial_decimal)};
// from_chars_result contains a value of std::errc, but also has a bool operator for better checks like this
// Regular <charconv> should be getting this bool operator in C++26
if (!r_initial)
{
// Here you can handle an error condition in any way you see fit
// For the purposes of our example we log and abort
std::cout << "Unexpected failure" << std::endl;
return 1;
}
else
{
std::cout << "Initial decimal: " << initial_decimal << '\n';
}
// boost::decimal::from_chars deviates from the C++ standard by allowing a std::string,
// or a std::string_view (when available)
//
// It is also perfectly acceptable to use an auto return type for even more brevity when using from_chars
const std::string string_value {"3.1415"};
decimal64_t string_decimal;
const auto r_string {from_chars(string_value, string_decimal)};
if (r_string)
{
std::cout << "Value from string: " << string_decimal << '\n';
}
// We can now compare the various ways to print the value
// First we will review the formatting_limits struct
// This struct contains a number of members that allow the to_chars buffer to be correctly sized
//
// First formatting_limits takes a type and optionally a precision as template parameters
// It then has members each corresponding to the maximum number of characters needed to print the type
//
// 1) scientific_format_max_chars
// 2) fixed_format_max_chars
// 3) hex_format_max_chars
// 4) cohort_preserving_scientific_max_chars
// 5) general_format_max_chars
// 6) max_chars - Equal to the maximum value of 1 to 5 to allow to_chars of any format
//
// Each of these will give you one additional character so you can write a null terminator to the end
// NOTE: to_chars IS NOT default null terminated
char scientific_buffer[formatting_limits<decimal64_t>::scientific_format_max_chars];
const to_chars_result r_sci {to_chars(scientific_buffer,
scientific_buffer + sizeof(scientific_buffer), initial_decimal, chars_format::scientific)};
if (r_sci)
{
*r_sci.ptr = '\0'; // to_chars does not null terminate per the C++ standard
std::cout << "Value in scientific format: " << scientific_buffer << '\n';
}
// else handle the error how you would like
// If we went to print the value to some specified precision our buffer will need more space
// Formatting limits takes a precision in this case
//
// Also as with from_chars it's perfectly fine to use an auto return type with to_chars
constexpr int required_precision {20};
char precision_20_scientific_buffer[formatting_limits<decimal64_t, required_precision>::scientific_format_max_chars];
const auto r_sci20 {to_chars(precision_20_scientific_buffer,
precision_20_scientific_buffer + sizeof(precision_20_scientific_buffer),
initial_decimal, chars_format::scientific, required_precision)};
if (r_sci20)
{
*r_sci20.ptr = '\0';
std::cout << "Value in scientific format with precision 20: " << precision_20_scientific_buffer << '\n';
}
// else handle the error how you would like
}
Output:
Initial decimal: -7123450 Value from string: 3.1415 Value in scientific format: -7.12345e+06 Value in scientific format with precision 20: -7.12345000000000000000e+06
Generic Programming
This example can be found in the examples/ folder as adl.cpp.
#include <boost/decimal/decimal32_t.hpp> // For type decimal32_t
#include <boost/decimal/decimal64_t.hpp> // For type decimal64_t
#include <boost/decimal/decimal128_t.hpp> // For type decimal128_t
#include <boost/decimal/iostream.hpp> // For <iostream> support
#include <boost/decimal/cmath.hpp> // For sin function
#include <iostream>
#include <cmath>
template <typename T>
void sin_identity(T val)
{
// ADL allows builtin and decimal types to both be used
// Boost.Decimal is not allowed to overload std::sin so it must be provided in its own namespace
// You must also include using std::sin to ensure that it is found for the float, double, and long double cases.
// It is preferred to have using statements for the functions you intend to use instead of using namespace XXX.
using std::sin;
using boost::decimal::sin;
// sin(x) = -sin(-x)
// The call here MUST be unqualified, or you will get compiler errors
// For example calling std::sin here would not allow any of the decimal types to be used
std::cout << "sin(" << val << ") = " << sin(val) << '\n'
<< "-sin(" << -val << ") = " << -sin(-val) << "\n\n";
}
int main()
{
// Because of the two using statements in the above function we can now call it with built-in floating point,
// or our decimal types as show below
std::cout << "Float:\n";
sin_identity(-0.5F);
std::cout << "Double:\n";
sin_identity(-0.5);
std::cout << "Long Double:\n";
sin_identity(-0.5L);
std::cout << "decimal32_t:\n";
sin_identity(boost::decimal::decimal32_t{"-0.5"});
std::cout << "decimal64_t:\n";
sin_identity(boost::decimal::decimal64_t{"-0.5"});
std::cout << "decimal128_t:\n";
sin_identity(boost::decimal::decimal128_t{"-0.5"});
}
The expected output of this is:
Float: sin(-0.5) = -0.479426 -sin(0.5) = -0.479426 Double: sin(-0.5) = -0.479426 -sin(0.5) = -0.479426 Long Double: sin(-0.5) = -0.479426 -sin(0.5) = -0.479426 decimal32_t: sin(-0.5) = -0.479426 -sin(0.5) = -0.479426 decimal64_t: sin(-0.5) = -0.479426 -sin(0.5) = -0.479426 decimal128_t: sin(-0.5) = -0.479426 -sin(0.5) = -0.479426
Literals and Constants
#include <boost/decimal.hpp>
#include <cassert>
template <typename T>
bool float_equal(T lhs, T rhs)
{
using std::fabs;
return fabs(lhs - rhs) < std::numeric_limits<T>::epsilon(); // numeric_limits is specialized for all decimal types
}
int main()
{
using namespace boost::decimal;
using namespace boost::decimal::literals;
const auto pi_32 {"3.141592653589793238"_DF};
const auto pi_64 {"3.141592653589793238"_DD};
assert(float_equal(pi_32, static_cast<decimal32_t>(pi_64))); // Explicit conversion between decimal types
assert(float_equal(pi_32, boost::decimal::numbers::pi_v<decimal32_t>)); // Constants available in numbers namespace
assert(float_equal(pi_64, numbers::pi)); // Default constant type is decimal64_t
return 0;
}
Financial Applications
Boost.Math Integration
Bollinger Bands
In the examples folder there is a file named statistics.cpp.
This example demonstrates how to parse a file, and then leverage Boost.Math to compute statistics of that data set culminating with the values of the Bollinger Bands.
This example could be extended with the simple moving average to create full bands based on the period of the moving average you would like.
Formatting
Boost.Decimal allows you to format your output with both <format> and <fmt/format.h> depending on your compiler support.
<format>
If your compiler provides <format> you can use that to format the output of your values:
#include <boost/decimal.hpp>
#include <iostream>
#include <format>
int main()
{
constexpr boost::decimal::decimal64_t val1 {314, -2};
constexpr boost::decimal::decimal32_t val2 {3141, -3};
std::cout << std::format("{:10.3e}", val1) << '\n';
std::cout << std::format("{:10.3e}", val2) << std::endl;
return 0;
}
<fmt/format.hpp>
We also provide support for {fmt} so you can easily just swap the namespaces and headers on the above example:
#include <boost/decimal.hpp>
#include <iostream>
#include <fmt/format.h>
int main()
{
constexpr boost::decimal::decimal64_t val1 {314, -2};
constexpr boost::decimal::decimal32_t val2 {3141, -3};
std::cout << fmt::format("{:10.3e}", val1) << '\n';
std::cout << fmt::format("{:10.3e}", val2) << std::endl;
return 0;
}
<print>
We can make one final change to our <format> example where instead of using std::cout, we use C++23's <print>:
#include <boost/decimal.hpp>
#include <print>
int main()
{
constexpr boost::decimal::decimal64_t val1 {314, -2};
constexpr boost::decimal::decimal32_t val2 {3141, -3};
std::print("{:10.3e}\n", val1);
std::print("{:10.3e}\n", val2);
return 0;
}