Examples
The following examples will help you get up and running with many of the major parts of the library.
All of these examples can be found in the library examples/ folder as well.
Basic Construction
Example 1. This example demonstrates the basic use of the various constructors offered by the decimal types
// 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
}
Expected Output
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
Basic Arithmetic
Example 2. This example demonstrates the behavior and functionality of arithmetic using a single type
// 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 basic numerical operations with the decimal types
#include <boost/decimal/decimal64_t.hpp> // For type decimal64_t
#include <boost/decimal/cmath.hpp> // For decimal overloads of <cmath> functions
#include <boost/decimal/iostream.hpp> // For decimal support of <iostream> and <iomanip>
#include <iostream>
#include <iomanip>
int main()
{
using boost::decimal::decimal64_t; // Type decimal64_t
constexpr decimal64_t a {"-5.123456891234567"}; // Constructs -5.123456 from string
constexpr decimal64_t b {"3.123456891234567"}; // Constructs 3.123456 from string
constexpr decimal64_t c {a + b};
// Here we can see that the result is exact
constexpr decimal64_t neg_two {-2};
static_assert(c == neg_two, "Result should be exact");
// We can use std::setprecision and std::numeric_limits in their usual ways
std::cout << std::setprecision(std::numeric_limits<decimal64_t>::digits10)
<< "A: " << a << '\n'
<< "B: " << b << '\n'
<< "A + B: " << c << '\n';
// The decimal library provides comprehensive implementations of the <cmath> functions
// that you would expect to have with the builtin floating point types
//
// They are all located in namespace boost::decimal::,
// as overloading namespace std is not allowed
constexpr decimal64_t abs_c {boost::decimal::abs(c)};
std::cout << "abs(A + B): " << abs_c << '\n';
// All cmath functions are constexpr even if their std:: counterparts are not
constexpr decimal64_t sqrt_two {boost::decimal::sqrt(abs_c)};
// Value compute by N[Sqrt[2], 50] using Wolfram Alpha or Mathematica
constexpr decimal64_t wa_sqrt_two {"1.4142135623730950488016887242096980785696718753769"};
std::cout << "sqrt(abs(A + B)): " << sqrt_two << '\n'
<< "Wolfram Alpha sqrt(2): " << wa_sqrt_two << '\n';
}
Expected Output:
A: -5.123456891234567 B: 3.123456891234567 A + B: -2 abs(A + B): 2 sqrt(abs(A + B)): 1.414213562373095 Wolfram Alpha sqrt(2): 1.414213562373095
Example 3. This example shows the differences in arithmetic results between decimal floating point and binary floating point
// Copyright 2025 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
//
// This example shows the difference in the results of repeated addition
#include <boost/decimal/decimal32_t.hpp> // For type decimal32_t and std::numeric_limits support
#include <boost/decimal/iostream.hpp> // For decimal support to <iostream>
#include <iostream>
#include <limits>
int main()
{
using boost::decimal::decimal32_t;
constexpr decimal32_t decimal_one_tenth {"0.1"}; // Construct constant 0.1 from string for lossless conversion
constexpr float float_one_tenth {0.1f}; // Construct floating point constant from literal
decimal32_t decimal_value {}; // Construct decimal 0 to start from
float float_value {}; // Construct float 0 to start from
// We now add 0.1 1000 times which should result exactly in 100
// What we actually find is that the decimal32_t value does result in exactly 100
// With type float the result is not 100 due to inexact representation
for (int i {}; i < 1000; ++i)
{
decimal_value += decimal_one_tenth; // Decimal types support compound arithmetic as expected
float_value += float_one_tenth;
}
// Each of the decimal types has complete support for std::numeric_limits,
// which we leverage here with set precision to show any fractional part of the number (if applicable)
std::cout << std::setprecision(std::numeric_limits<decimal32_t>::digits10)
<< "Decimal Result: " << decimal_value << "\n"
<< std::setprecision(std::numeric_limits<float>::digits10)
<< " Float Result: " << float_value << std::endl;
}
Expected Output:
Decimal Result: 100 Float Result: 99.999
Conversions
Integral Conversions
Example 4. This example shows how to construct an integral value from decimal type and vice versa, as well as the behavior
// Copyright 2025 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
//
// This file demonstrates how to convert various types to decimal types and back,
// along with edge case handling
#include <boost/decimal/decimal32_t.hpp> // For type decimal32_t
#include <boost/decimal/decimal64_t.hpp> // For type decimal64_t
#include <boost/decimal/cmath.hpp> // For decimal support of cmath functions
#include <boost/decimal/iostream.hpp> // For decimal support of <iostream> and <iomanip>
#include <boost/decimal/numbers.hpp> // For decimal support of <numbers>
#include <iostream>
#include <limits>
#include <cstdint>
int main()
{
using boost::decimal::decimal32_t; // Type decimal32_t
using boost::decimal::decimal64_t; // Type decimal64_t
// Non-finite values construct std::numeric_limits<TargetIntegerType>::max()
constexpr decimal64_t decimal_qnan {std::numeric_limits<decimal64_t>::quiet_NaN()};
const std::uint32_t int_from_nan {static_cast<std::uint32_t>(decimal_qnan)};
// Note here that we must use boost::decimal::isnan for decimal types,
// as it is illegal to overload std::isnan
if (boost::decimal::isnan(decimal_qnan) && int_from_nan == std::numeric_limits<std::uint32_t>::max())
{
std::cout << "Decimal QNAN converts to Integer Max\n";
}
// Same thing happens with decimal infinities since integers don't have an infinity
constexpr decimal32_t decimal_inf {std::numeric_limits<decimal32_t>::infinity()};
const std::uint64_t int_from_inf {static_cast<std::uint64_t>(decimal_inf)};
// Same as the above but with INF instead of NAN
if (boost::decimal::isinf(decimal_inf) && int_from_inf == std::numeric_limits<std::uint64_t>::max())
{
std::cout << "Decimal INF converts to Integer Max\n";
}
// For finite values the construction of the resulting integer matches the behavior
// you are familiar with from binary floating point to integer conversions.
// Namely, the result will only have the integer component of the decimal
// Construct the decimal64_t version of pi using our pre-computed constants from <boost/decimal/numbers.hpp>
constexpr decimal64_t decimal_pi {boost::decimal::numbers::pi_v<decimal64_t>};
const std::uint32_t int_from_pi {static_cast<std::uint32_t>(decimal_pi)};
std::cout << std::setprecision(std::numeric_limits<decimal64_t>::digits10)
<< " decimal64_t pi: " << decimal_pi << '\n'
<< "std::uint32_t pi: " << int_from_pi << "\n\n";
// Constructing a decimal value from an integer is lossless until
// the number of digits in the integer exceeds the precision of the decimal type
std::cout << "Conversions will be lossless\n"
<< " decimal64_t digits10: " << std::numeric_limits<decimal64_t>::digits10 << "\n"
<< "std::uint32_t digits10: " << std::numeric_limits<std::uint32_t>::digits10 << "\n";
constexpr decimal64_t decimal_from_u32_max {std::numeric_limits<std::uint32_t>::max()};
std::cout << " std::uint32_t max: " << std::numeric_limits<std::uint32_t>::max() << "\n"
<< "decimal64_t from max: " << decimal_from_u32_max << "\n\n";
// In the construction of lossy values the rounding will be handled according to
// the current global rounding mode.
std::cout << "Conversions will be lossy\n"
<< " decimal32_t digits10: " << std::numeric_limits<decimal32_t>::digits10 << "\n"
<< "std::uint64_t digits10: " << std::numeric_limits<std::uint64_t>::digits10 << "\n";
constexpr decimal32_t decimal_from_u64_max {std::numeric_limits<std::uint64_t>::max()};
std::cout << " std::uint64_t max: " << std::numeric_limits<std::uint64_t>::max() << "\n"
<< "decimal32_t from max: " << decimal_from_u64_max << '\n';
return 0;
}
Expected Output
Decimal QNAN converts to Integer Max Decimal INF converts to Integer Max decimal64_t pi: 3.141592653589793 std::uint32_t pi: 3 Conversions will be lossless decimal64_t digits10: 16 std::uint32_t digits10: 9 std::uint32_t max: 4294967295 decimal64_t from max: 4294967295 Conversions will be lossy decimal32_t digits10: 7 std::uint64_t digits10: 19 std::uint64_t max: 18446744073709551615 decimal32_t from max: 1.844674e+19
Binary Floating Point Conversions
Example 5. This example shows how to construct a binary floating point value from decimal type and vice versa, as well as the behavior
// Copyright 2025 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
//
// This file demonstrates how to convert various types to decimal types and back,
// along with edge case handling
#include <boost/decimal/decimal32_t.hpp> // For type decimal32_t
#include <boost/decimal/decimal64_t.hpp> // For type decimal64_t
#include <boost/decimal/cmath.hpp> // For decimal support of cmath functions
#include <boost/decimal/iostream.hpp> // For decimal support of <iostream> and <iomanip>
#include <boost/decimal/numbers.hpp> // For decimal support of <numbers>
#include <iostream>
#include <cmath>
#include <limits>
int main()
{
using boost::decimal::decimal32_t; // Type decimal32_t
using boost::decimal::decimal64_t; // Type decimal64_t
// Non-finite values construct the equivalent non-finite value in binary floating point
constexpr decimal64_t decimal_qnan {std::numeric_limits<decimal64_t>::quiet_NaN()};
const double double_from_qnan {static_cast<double>(decimal_qnan)};
// Note here that we must use boost::decimal::isnan for decimal types,
// as it is illegal to overload std::isnan
if (boost::decimal::isnan(decimal_qnan) && std::isnan(double_from_qnan))
{
std::cout << "Decimal QNAN converts to double QNAN\n";
}
constexpr decimal64_t decimal_inf {std::numeric_limits<decimal64_t>::infinity()};
const double double_from_inf {static_cast<double>(decimal_inf)};
// Same as the above but with INF instead of NAN
if (boost::decimal::isinf(decimal_inf) && std::isinf(double_from_inf))
{
std::cout << "Decimal INFINITY converts to double INFINITY\n";
}
// For finite values we make a best effort approach to covert to double
// We are able to decompose the decimal floating point value into a sign, significand, and exponent.
// From there we use the methods outline in Daniel Lemire's "Number Parsing at a Gigabyte a Second",
// to construct the binary floating point value.
// See: https://arxiv.org/pdf/2101.11408
// Construct the decimal64_t version of pi using our pre-computed constants from <boost/decimal/numbers.hpp>
constexpr decimal64_t decimal_pi {boost::decimal::numbers::pi_v<decimal64_t>};
const double double_from_pi {static_cast<double>(decimal_pi)};
std::cout << std::setprecision(std::numeric_limits<decimal64_t>::digits10)
<< "decimal64_t pi: " << decimal_pi << '\n'
<< " double pi: " << double_from_pi << '\n';
// To construct a decimal64_t from double we use the methods described in "Ryu: fast float-to-string conversion"
// See: https://dl.acm.org/doi/10.1145/3192366.3192369
// This paper shows how to decompose a double into it's sign, significand, and exponent
// Once we have those components we can use the normal constructors of the decimal types to construct
// Since we are using the normal constructors here,
// any construction from this conversion is subject to the current rounding mode
// Such as with a lossy conversion like shown (double -> decimal32_t)
const decimal64_t decimal_from_double {static_cast<decimal64_t>(double_from_pi)};
const decimal32_t lossy_decimal_from_double {static_cast<decimal32_t>(double_from_pi)};
std::cout << " Converted pi: " << decimal_from_double << '\n'
<< "decimal32_t pi: " << lossy_decimal_from_double << '\n';
// Other than what has already been shown,
// there are no other ways in the library to convert between decimal types and binary floating point types
// The reason for this is to discourage their use.
//
// You can use intermediate representations like strings if you want to make these conversions,
// and want to be sure about what the resulting value will be
return 0;
}
Expected Output:
Decimal QNAN converts to double QNAN
Decimal INFINITY converts to double INFINITY
decimal64_t pi: 3.141592653589793
double pi: 3.141592653589793
Converted pi: 3.141592653589793
decimal32_t pi: 3.141593
Promotion and Mixed Decimal Arithmetic
Example 6. This example demonstrates the behaviors of promotion between types, and mixed decimal type arithmetic
// Copyright 2025 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
//
// 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>
#include <limits>
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';
// Now we can look at similar promotion that occurs when an operation is performed between
// a decimal type and an integer
//
// Similar to the above when we have mixed operations like +, -, *, / we always promote to the decimal type
// Example: decimal64_t * int -> decimal64_t
const auto d {2 * c};
using d_type = std::remove_cv_t<decltype(d)>;
static_assert(std::is_same<d_type, decimal64_t>::value, "decimal64_t * integer is supposed to yield decimal64_t");
std::cout << "The result of 2 * c is a decimal64_t: " << d << '\n';
// The full suite of comparison operators between decimal types and integers
if (d > 5)
{
std::cout << d << " is greater than 5" << '\n';
}
}
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 less than inf The result of a + b is a decimal64_t: 9.1 The result of 2*c is a decimal64_t: 18.2 18.2 is greater than 5
<charconv>
Example 7. This example demonstrates the fundamentals of the
<charconv> like functions provided by the library// Copyright 2024 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
//
// 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)
{
// LCOV_EXCL_START
// 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;
// LCOV_EXCL_STOP
}
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
}
Expected 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
Example 8. This example demonstrates how to write generic code that accepts both built-in floating point types, and decimal floating point values from this library
// Copyright 2024 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
//
// This example shows how we are able to use adl with Boost.Decimal to allow a template function
// to use both built-in binary floating point types, as well as Boost.Decimal types
#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"});
}
Expected Output:
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
Example 9. This example demonstrates how to construct values using literals, and the usage of numerical constants that are provided by the library
// Copyright 2024 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
//
// This examples demonstrates decimal floating point literals,
// as well as numeric constants made available by the library
#include <boost/decimal/decimal32_t.hpp> // For type decimal32_t
#include <boost/decimal/decimal64_t.hpp> // For type decimal64_t
#include <boost/decimal/literals.hpp> // For the decimal (user defined) literals
#include <boost/decimal/numbers.hpp> // For provided numeric constants
#include <boost/decimal/iostream.hpp> // For support to <iostream> and <iomanip>
#include <iostream>
#include <iomanip>
#include <type_traits>
#include <limits>
int main()
{
using namespace boost::decimal::literals; // Much like the std namespace, literals are separate form the lib
using boost::decimal::decimal32_t; // Type decimal32_t
using boost::decimal::decimal64_t; // Type decimal64_t
// Defaulted numeric constants are available with type decimal64_t,
// much like std::numbers::pi defaults to double
constexpr auto default_pi {boost::decimal::numbers::pi};
using default_type = std::remove_cv_t<decltype(default_pi)>;
static_assert(std::is_same<default_type, decimal64_t>::value, "Defaulted value has type decimal64_t");
// You can also specify the type explicitly as these are template constants
constexpr decimal32_t decimal32_pi {boost::decimal::numbers::pi_v<decimal32_t>};
// We can use std::setprecision from <iomanip> to see the real difference between the two values
// numeric_limits is also specialized for each type
std::cout << std::setprecision(std::numeric_limits<decimal32_t>::digits10)
<< "32-bit Pi: " << decimal32_pi << '\n';
std::cout << std::setprecision(std::numeric_limits<decimal64_t>::digits10)
<< "64-bit Pi: " << default_pi << '\n';
// All of our types also offer user defined literals:
// _df or _DF for decimal32_t
// _dd or _DD for decimal64_t
// _dl or _DL for decimal128_t
// For fast types add an f to the end of each (e.g. _dff = decimal_fast32_t or _DLF decimal_fast128_t)
//
// Since we have specified the type using the literal it is safe to use auto for the type
//
// We construct both from the first 40 digits of pi
// The constructor will parse this and then round to the proper precision automatically
constexpr auto literal32_pi {"3.141592653589793238462643383279502884197"_DF};
constexpr auto literal64_pi {"3.141592653589793238462643383279502884197"_DD};
std::cout << std::setprecision(std::numeric_limits<decimal32_t>::digits10)
<< "32-bit UDL Pi: " << literal32_pi << '\n';
// Unlike built-in binary floating point, floating equal is acceptable with decimal floating point
// Float equal will automatically address cohorts as per IEEE 754 if required (not shown in this example)
if (literal32_pi == decimal32_pi)
{
std::cout << "Rounded UDL has the same value as the 32-bit constant" << '\n';
}
std::cout << std::setprecision(std::numeric_limits<decimal64_t>::digits10)
<< "64-bit UDL Pi: " << literal64_pi << '\n';
if (literal64_pi == default_pi)
{
std::cout << "Rounded UDL has the same value as the 64-bit constant" << '\n';
}
}
Expected Output:
32-bit Pi: 3.141593 64-bit Pi: 3.141592653589793 32-bit UDL Pi: 3.141593 Rounded UDL has the same value as the 32-bit constant 64-bit UDL Pi: 3.141592653589793 Rounded UDL has the same value as the 64-bit constant
Formatting
Boost.Decimal allows you to format your output with both <format> and <fmt/format.h> depending on your compiler support.
{fmt} support is available starting with C++14 so long as you have the library available, but <format> requires C++20 and compiler support
<fmt/format.hpp>
Example 10. This example demonstrates the various formatting options provided by the library to support usage of {fmt}
// Copyright 2025 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
//
// This example demonstrates usage and formatting of decimal types with fmt
#include <boost/decimal/decimal32_t.hpp> // For type decimal32_t
#include <boost/decimal/decimal64_t.hpp> // For type decimal64_t
#include <boost/decimal/fmt_format.hpp> // For {fmt} support
#include <iostream>
int main()
{
constexpr boost::decimal::decimal64_t val1 {"3.14"};
constexpr boost::decimal::decimal32_t val2 {"3.141"};
// The easiest is no specification which is general format
// Given these values they will print in fixed format
std::cout << "Default Format:\n";
std::cout << fmt::format("{}", val1) << '\n';
std::cout << fmt::format("{}", val2) << "\n\n";
// Next we can add a type modifier to get scientific formatting
std::cout << "Scientific Format:\n";
std::cout << fmt::format("{:e}", val1) << '\n';
std::cout << fmt::format("{:e}", val2) << "\n\n";
// Next we can add a type modifier to get scientific formatting
// Here this gives one digit of precision rounded according to current rounding mode
std::cout << "Scientific Format with Specified Precision:\n";
std::cout << fmt::format("{:.1e}", val1) << '\n';
std::cout << fmt::format("{:.1e}", val2) << "\n\n";
// This combines the padding modifier (10), precision (3 digits), and a type modifier (e)
std::cout << "Scientific Format with Specified Precision and Padding:\n";
std::cout << fmt::format("{:10.3e}", val1) << '\n';
std::cout << fmt::format("{:10.3e}", val2) << '\n';
return 0;
}
Expected Output:
Default Format: 3.14 3.141 Scientific Format: 3.14e+00 3.141e+00 Scientific Format with Specified Precision: 3.1e+00 3.1e+00 Scientific Format with Specified Precision and Padding: 03.140e+00 03.141e+00
If you are using the convenience header <boost/decimal.hpp> the header <boost/decimal/fmt_format.hpp> is NOT automatically included since it requires an external library.
You must include it yourself.
|
<format>
Taking the above example of {fmt} and replacing all instances of namespace fmt with namespace std gives us another working example.
Example 11. This example demonstrates how to use
<format> with the library in-place or in addition to {fmt}// Copyright 2025 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
//
// This example demonstrates usage and formatting of decimal types with <format>
#include <boost/decimal/decimal32_t.hpp> // For type decimal32_t
#include <boost/decimal/decimal64_t.hpp> // For type decimal64_t
#include <boost/decimal/format.hpp> // For <format> support (when available)
#include <iostream>
// This macro is defined in boost/decimal/format.hpp if the platform has <format> support
#ifdef BOOST_DECIMAL_HAS_FORMAT_SUPPORT
#include <format>
int main()
{
constexpr boost::decimal::decimal64_t val1 {"3.14"};
constexpr boost::decimal::decimal32_t val2 {"3.141"};
// The easiest is no specification which is general format
// Given these values they will print in fixed format
std::cout << "Default Format:\n";
std::cout << std::format("{}", val1) << '\n';
std::cout << std::format("{}", val2) << "\n\n";
// Next we can add a type modifier to get scientific formatting
std::cout << "Scientific Format:\n";
std::cout << std::format("{:e}", val1) << '\n';
std::cout << std::format("{:e}", val2) << "\n\n";
// Next we can add a type modifier to get scientific formatting
// Here this gives one digit of precision rounded according to current rounding mode
std::cout << "Scientific Format with Specified Precision:\n";
std::cout << std::format("{:.1e}", val1) << '\n';
std::cout << std::format("{:.1e}", val2) << "\n\n";
// This combines the padding modifier (10), precision (3 digits), and a type modifier (e)
std::cout << "Scientific Format with Specified Precision and Padding:\n";
std::cout << std::format("{:10.3e}", val1) << '\n';
std::cout << std::format("{:10.3e}", val2) << '\n';
return 0;
}
#else
int main()
{
std::cout << "<format> is unsupported" << std::endl;
return 0;
}
#endif
Expected Output:
Default Format: 3.14 3.141 Scientific Format: 3.14e+00 3.141e+00 Scientific Format with Specified Precision: 3.1e+00 3.1e+00 Scientific Format with Specified Precision and Padding: 03.140e+00 03.141e+00
<print>
Example 12. This example demonstrates how to use
<print> with the library#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;
}
Expected Output:
03.140e+00 03.141e+00
Reading From and Writing To File
Example 13. This example shows how to read and write decimal values efficiently to and from file, rather than using
to_chars and from_chars// Copyright 2025 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
//
// This example shows how to write and read decimal values to file efficiently
#include <boost/decimal/decimal32_t.hpp> // For type decimal32_t
#include <boost/decimal/bid_conversion.hpp> // For to and from BID encoded bits functions
#include <boost/decimal/iostream.hpp> // For decimal type support to <iostream>
#include <fstream>
#include <random>
#include <array>
#include <iostream>
#include <iomanip>
#include <cstdint>
#include <cstdio>
int main()
{
using boost::decimal::decimal32_t; // The type decimal32_t
// 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};
std::uniform_int_distribution<std::int32_t> significand_dist {-9'999'999, 9'999'999};
std::uniform_int_distribution<std::int32_t> exp_dist {-50, 50};
std::array<decimal32_t, 10> values;
for (auto& v : values)
{
v = decimal32_t{significand_dist(rng), exp_dist(rng)};
}
// Now that we have our random decimal32_ts we will write them to file
// using their bitwise representations with the to_bid function
//
// This allows us to losslessly and rapidly recover them from file
// 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");
if (!file.is_open())
{
std::cerr << "Failed to open file for writing" << std::endl;
return 1;
}
for (const auto& value : values)
{
std::uint32_t bid_value {boost::decimal::to_bid(value)};
std::cout << " Current value: " << std::dec << value << '\n'
<< "Value as bytes: " << std::hex << bid_value << "\n\n";
file.write(reinterpret_cast<char*>(&bid_value), sizeof(bid_value));
}
file.close();
// 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);
if (!read_file.is_open())
{
std::cerr << "Failed to open file for reading" << std::endl;
return 1;
}
std::array<decimal32_t, 10> recovered_values;
for (auto& value : recovered_values)
{
std::uint32_t bid_value;
read_file.read(reinterpret_cast<char*>(&bid_value), sizeof(bid_value));
value = boost::decimal::from_bid(bid_value);
}
read_file.close();
if (std::remove("example_values.txt"))
{
std::cerr << "Failed to remove file" << std::endl;
}
// Verify that we recovered the same values
bool success {true};
for (std::size_t i {}; i < values.size(); ++i)
{
if (values[i] != recovered_values[i])
{
success = false;
break;
}
}
if (success)
{
std::cout << "Successfully recovered all values from file" << std::endl;
}
else
{
std::cout << "Warning: Some values did not match after recovery" << std::endl;
}
return 0;
}
Expected Output:
Current value: 0.000506 Value as bytes: 2dcd4c57 Current value: -3.808117e+34 Value as bytes: c0ba1b75 Current value: -1.656579e-12 Value as bytes: a9994703 Current value: 2.040449e+10 Value as bytes: 349f2281 Current value: -5.16665e+43 Value as bytes: c587e239 Current value: -9.25265e+32 Value as bytes: c00e1e51 Current value: -5.766669e-11 Value as bytes: aa57fe0d Current value: -7.641908e+38 Value as bytes: c2f49b34 Current value: 6.31977e+29 Value as bytes: 3e606e9a Current value: 9.210438e-24 Value as bytes: 68ec8a46 Successfully recovered all values from file