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