Overview

Description

Boost.Decimal is an implementation of IEEE 754 and ISO/IEC DTR 24733 Decimal Floating Point numbers. The library is header-only, has no dependencies, and requires C++14.

Motivation

Current C++ floating point types store the significand (often incorrectly referred to as the mantissa) as binary digits. Famously this leads to representation errors: https://0.30000000000000004.com. Decimal floating point numbers avoid this issue by storing the significand in base-10 (decimal). The other major difference between binary and decimal floating point types is that the latter allows for multiple representations of the same number. For example 1e5 could also be stored as 0.1e6, 0.01e7, so on and so forth. These are referred to as cohorts which binary does not have as there is only one way to represent each number in binary floating point.

Use Cases

The use case for Decimal Floating Point numbers is where rounding errors are significantly impactful such as finance. In applications where integer or fixed-point arithmetic are used to combat this issue Decimal Floating Point numbers can provide a significantly greater range of values. For example, while a fixed-point representation that allocates 8 decimal digits and 2 decimal places can represent the numbers 123456.78, 8765.43, 123.00, and so on, a floating-point representation with 8 decimal digits could also represent 1.2345678, 1234567.8, 0.000012345678, 12345678000000000, and so on.

Supported Compilers

Boost.Decimal is tested on Ubuntu (x86_64, s390x, and aarch64), macOS (x86_64, and Apple Silicon), and Windows with the following compilers:

  • GCC 7 and later

  • Clang 6 and later

  • Visual Studio 2017 and later

Tested on Github Actions and Drone. Coverage can be found on Codecov.

Basic Usage

#include <boost/decimal.hpp>
#include <iostream>
#include <iomanip>

int main()
{
    using namespace boost::decimal;

    // Outputs 0.30000000000000004
    std::cout << std::setprecision(17) << 0.1 + 0.2;

    // Construct the two decimal values
    constexpr decimal64 a {1, -1}; // 1e-1 or 0.1
    constexpr decimal64 b {2, -1}; // 2e-1 or 0.2

    // Outputs 0.30000000000000000
    std::cout << a + b << std::endl;

    return 0;
}

Decimal Types

Synopsis of header <boost/decimal.hpp>

// Paragraph numbers are from ISO/IEC DTR 24733

namespace boost {
namespace decimal {

// 3.2.2 class decimal32:
class decimal32;

// 3.2.3 class decimal64:
class decimal64;

// 3.2.4 class decimal128:
class decimal128;

// 3.2.7 unary arithmetic operators:
constexpr decimal32  operator+(decimal32 rhs) noexcept;
constexpr decimal64  operator+(decimal64 rhs) noexcept;
constexpr decimal128 operator+(decimal128 rhs) noexcept;

constexpr decimal32  operator-(decimal32 rhs) noexcept;
constexpr decimal64  operator-(decimal64 rhs) noexcept;
constexpr decimal128 operator-(decimal128 rhs) noexcept;

// 3.2.8 binary arithmetic operators:
// LHS and RHS can be any integer or decimal type

constexpr /* see 3.2.8 */ operator+(LHS lhs, decimal32 rhs) noexcept;
constexpr /* see 3.2.8 */ operator+(LHS lhs, decimal64 rhs) noexcept;
constexpr /* see 3.2.8 */ operator+(LHS lhs, decimal128 rhs) noexcept;
constexpr /* see 3.2.8 */ operator+(decimal32 lhs, RHS rhs) noexcept;
constexpr /* see 3.2.8 */ operator+(decimal64 lhs, RHS rhs) noexcept;
constexpr /* see 3.2.8 */ operator+(decimal128 lhs, RHS rhs) noexcept;

constexpr /* see 3.2.8 */ operator-(LHS lhs, decimal32 rhs) noexcept;
constexpr /* see 3.2.8 */ operator-(LHS lhs, decimal64 rhs) noexcept;
constexpr /* see 3.2.8 */ operator-(LHS lhs, decimal128 rhs) noexcept;
constexpr /* see 3.2.8 */ operator-(decimal32 lhs, RHS rhs) noexcept;
constexpr /* see 3.2.8 */ operator-(decimal64 lhs, RHS rhs) noexcept;
constexpr /* see 3.2.8 */ operator-(decimal128 lhs, RHS rhs) noexcept;

constexpr /* see 3.2.8 */ operator*(LHS lhs, decimal32 rhs) noexcept;
constexpr /* see 3.2.8 */ operator*(LHS lhs, decimal64 rhs) noexcept;
constexpr /* see 3.2.8 */ operator*(LHS lhs, decimal128 rhs) noexcept;
constexpr /* see 3.2.8 */ operator*(decimal32 lhs, RHS rhs) noexcept;
constexpr /* see 3.2.8 */ operator*(decimal64 lhs, RHS rhs) noexcept;
constexpr /* see 3.2.8 */ operator*(decimal128 lhs, RHS rhs) noexcept;

constexpr /* see 3.2.8 */ operator/(LHS lhs, decimal32 rhs) noexcept;
constexpr /* see 3.2.8 */ operator/(LHS lhs, decimal64 rhs) noexcept;
constexpr /* see 3.2.8 */ operator/(LHS lhs, decimal128 rhs) noexcept;
constexpr /* see 3.2.8 */ operator/(decimal32 lhs, RHS rhs) noexcept;
constexpr /* see 3.2.8 */ operator/(decimal64 lhs, RHS rhs) noexcept;
constexpr /* see 3.2.8 */ operator/(decimal128 lhs, RHS rhs) noexcept;

// 3.2.9 comparison operators:
// LHS and RHS can be any integer or decimal type

constexpr bool operator==(LHS lhs, decimal32 rhs) noexcept;
constexpr bool operator==(LHS lhs, decimal64 rhs) noexcept;
constexpr bool operator==(LHS lhs, decimal128 rhs) noexcept;
constexpr bool operator==(decimal32 lhs, RHS rhs) noexcept;
constexpr bool operator==(decimal64 lhs, RHS rhs) noexcept;
constexpr bool operator==(decimal128 lhs, RHS rhs) noexcept;
constexpr bool operator!=(LHS lhs, decimal32 rhs) noexcept;
constexpr bool operator!=(LHS lhs, decimal64 rhs) noexcept;
constexpr bool operator!=(LHS lhs, decimal128 rhs) noexcept;
constexpr bool operator!=(decimal32 lhs, RHS rhs) noexcept;
constexpr bool operator!=(decimal64 lhs, RHS rhs) noexcept;
constexpr bool operator!=(decimal128 lhs, RHS rhs) noexcept;
constexpr bool operator<(LHS lhs, decimal32 rhs) noexcept;
constexpr bool operator<(LHS lhs, decimal64 rhs) noexcept;
constexpr bool operator<(LHS lhs, decimal128 rhs) noexcept;
constexpr bool operator<(decimal32 lhs, RHS rhs) noexcept;
constexpr bool operator<(decimal64 lhs, RHS rhs) noexcept;
constexpr bool operator<(decimal128 lhs, RHS rhs) noexcept;
constexpr bool operator<=(LHS lhs, decimal32 rhs) noexcept;
constexpr bool operator<=(LHS lhs, decimal64 rhs) noexcept;
constexpr bool operator<=(LHS lhs, decimal128 rhs) noexcept;
constexpr bool operator<=(decimal32 lhs, RHS rhs) noexcept;
constexpr bool operator<=(decimal64 lhs, RHS rhs) noexcept;
constexpr bool operator<=(decimal128 lhs, RHS rhs) noexcept;
constexpr bool operator>(LHS lhs, decimal32 rhs) noexcept;
constexpr bool operator>(LHS lhs, decimal64 rhs) noexcept;
constexpr bool operator>(LHS lhs, decimal128 rhs) noexcept;
constexpr bool operator>(decimal32 lhs, RHS rhs) noexcept;
constexpr bool operator>(decimal64 lhs, RHS rhs) noexcept;
constexpr bool operator>(decimal128 lhs, RHS rhs) noexcept;
constexpr bool operator>=(LHS lhs, decimal32 rhs) noexcept;
constexpr bool operator>=(LHS lhs, decimal64 rhs) noexcept;
constexpr bool operator>=(LHS lhs, decimal128 rhs) noexcept;
constexpr bool operator>=(decimal32 lhs, RHS rhs) noexcept;
constexpr bool operator>=(decimal64 lhs, RHS rhs) noexcept;
constexpr bool operator>=(decimal128 lhs, RHS rhs) noexcept;

// If C++20 is available
// LHS and RHS can be any integer or decimal type

constexpr std::partial_ordering operator<=>(decimal32 lhs, RHS rhs) noexcept;
constexpr std::partial_ordering operator<=>(decimal64 lhs, RHS rhs) noexcept;
constexpr std::partial_ordering operator<=>(decimal128 lhs, RHS rhs) noexcept;
constexpr std::partial_ordering operator<=>(LHS lhs, decimal32 rhs) noexcept;
constexpr std::partial_ordering operator<=>(LHS lhs, decimal64 rhs) noexcept;
constexpr std::partial_ordering operator<=>(LHS lhs, decimal128 rhs) noexcept;

// 3.2.10 Formatted input:
// These functions are locale dependent
template <typename charT, typename traits>
std::basic_istream<charT, traits>&
operator>>(std::basic_istream<charT, traits>& is,
decimal32& d);

template <typename charT, typename traits>
std::basic_istream<charT, traits>&
operator>>(std::basic_istream<charT, traits> & is,
decimal64& d);

template <typename charT, typename traits>
std::basic_istream<charT, traits>&
operator>>(std::basic_istream<charT, traits> & is,
decimal128& d);

// 3.2.11 Formatted output:
// These functions are locale dependent
template <typename charT, typename traits>
std::basic_ostream<charT, traits>&
operator<<(std::basic_ostream<charT, traits>& os,
decimal32 d);

template <typename charT, typename traits>
std::basic_ostream<charT, traits>&
operator<<(std::basic_ostream<charT, traits>& os,
decimal64 d);

template <typename charT, typename traits>
std::basic_ostream<charT, traits>&
operator<<(std::basic_ostream<charT, traits>& os,
decimal128 d);

} //namespace decimal
} //namespace boost

3.2.8 Note

In the event of binary arithmetic between a non-decimal type and a decimal type the arithmetic will occur between the native types, and the result will be returned as the same type as the decimal operand. (e.g. decimal32 * uint64_t → decimal32)

In the event of binary arithmetic between two decimal types the result will be the higher precision type of the two (e.g. decimal64 + decimal32 → decimal64)

Decimal32

Description

Decimal32 is the 32-bit version of the decimal interchange format, and has the following properties as defined in IEEE 754-2019 table 3.6

  • Storage width - 32 bits

  • Precision - 7 decimal digits (not bits like binary)

  • Max exponent - 96

  • Max Value - 9.999999e96

  • Smallest normalized value - 1.000000e-95

  • Smallest subnormal - 1e-101

#include <boost/decimal/decimal32.hpp>

namespace boost {
namespace decimal {

// Paragraph numbers are from ISO/IEC DTR 24733

// 3.2.2.1 construct/copy/destroy
constexpr decimal32() noexcept = default;

// 3.2.2.2 Conversion form floating-point type
template <typename Float>
explicit BOOST_DECIMAL_CXX20_CONSTEXPR decimal32(Float val) noexcept;

// 3.2.2.3 Conversion from integral type
template <typename Integer>
explicit constexpr decimal32(Integer val) noexcept;

template <typename Integral1, typename Integral2>
constexpr decimal32(Integral1 coeff, Integral2 exp, bool sign = false) noexcept;

template <typename Integral>
constexpr decimal32& operator=(const Integeral& RHS) noexcept;

// 3.2.2.4 Conversion to integral type
explicit constexpr operator int() const noexcept;
explicit constexpr operator unsigned() const noexcept;
explicit constexpr operator long() const noexcept;
explicit constexpr operator unsigned long() const noexcept;
explicit constexpr operator long long() const noexcept;
explicit constexpr operator unsigned long long() const noexcept;
explicit constexpr operator std::int8_t() const noexcept;
explicit constexpr operator std::uint8_t() const noexcept;
explicit constexpr operator std::int16_t() const noexcept;
explicit constexpr operator std::uint16_t() const noexcept;

// 3.2.2.5 increment and decrement operators:
constexpr decimal32& operator++();
constexpr decimal32  operator++(int);
constexpr decimal32& operator--();
constexpr decimal32  operator--(int);

// 3.2.2.6 compound assignment:
constexpr decimal32& operator+=(RHS rhs);
constexpr decimal32& operator-=(RHS rhs);
constexpr decimal32& operator*=(RHS rhs);
constexpr decimal32& operator/=(RHS rhs);

// 3.2.6 Conversion to floating-point type
explicit BOOST_DECIMAL_CXX20_CONSTEXPR operator float() const noexcept;
explicit BOOST_DECIMAL_CXX20_CONSTEXPR operator double() const noexcept;
explicit BOOST_DECIMAL_CXX20_CONSTEXPR operator long double() const noexcept;

// The following are available assuming a C++23 compiler that provides the header <stdfloat>
explicit constexpr operator std::float16_t() const noexcept;
explicit constexpr operator std::float32_t() const noexcept;
explicit constexpr operator std::float64_t() const noexcept;
explicit constexpr operator std::bfloat16_t() const noexcept;

explicit constexpr operator decimal64() const noexcept;
explicit constexpr operator decimal128() const noexcept;

} //namespace decimal
} //namespace boost

Decimal64

Description

Decimal64 is the 64-bit version of the decimal interchange format, and has the following properties as defined in IEEE 754-2019 table 3.6

  • Storage width - 64 bits

  • Precision - 16 decimal digits (not bits like binary)

  • Max exponent - 385

  • Max Value - 9.999999999999999e385

  • Smallest normalized value - 1.000000000000000e-382

  • Smallest subnormal - 1e-398

#include <boost/decimal/decimal64.hpp>

namespace boost {
namespace decimal {

// Paragraph numbers are from ISO/IEC DTR 24733

// 3.2.3.1 construct/copy/destroy
constexpr decimal64() noexcept = default;

// 3.2.2.2 Conversion form floating-point type
template <typename Float>
explicit BOOST_DECIMAL_CXX20_CONSTEXPR decimal64(Float val) noexcept;

// 3.2.3.3 Conversion from integral type
template <typename Integer>
explicit constexpr decimal64(Integer val) noexcept;

template <typename Integral1, typename Integral2>
constexpr decimal64(Integral1 coeff, Integral2 exp, bool sign = false) noexcept;

template <typename Integral>
constexpr decimal64& operator=(const Integeral& RHS) noexcept;

// 3.2.3.4 Conversion to integral type
explicit constexpr operator int() const noexcept;
explicit constexpr operator unsigned() const noexcept;
explicit constexpr operator long() const noexcept;
explicit constexpr operator unsigned long() const noexcept;
explicit constexpr operator long long() const noexcept;
explicit constexpr operator unsigned long long() const noexcept;
explicit constexpr operator std::int8_t() const noexcept;
explicit constexpr operator std::uint8_t() const noexcept;
explicit constexpr operator std::int16_t() const noexcept;
explicit constexpr operator std::uint16_t() const noexcept;

// 3.2.3.5 increment and decrement operators:
constexpr decimal64& operator++();
constexpr decimal64  operator++(int);
constexpr decimal64& operator--();
constexpr decimal64  operator--(int);

// 3.2.3.6 compound assignment:
constexpr decimal64& operator+=(RHS rhs);
constexpr decimal64& operator-=(RHS rhs);
constexpr decimal64& operator*=(RHS rhs);
constexpr decimal64& operator/=(RHS rhs);

// 3.2.6 Conversion to floating-point type
explicit BOOST_DECIMAL_CXX20_CONSTEXPR operator float() const noexcept;
explicit BOOST_DECIMAL_CXX20_CONSTEXPR operator double() const noexcept;
explicit BOOST_DECIMAL_CXX20_CONSTEXPR operator long double() const noexcept;

// The following are available assuming a C++23 compiler that provides the header <stdfloat>
explicit constexpr operator std::float16_t() const noexcept;
explicit constexpr operator std::float32_t() const noexcept;
explicit constexpr operator std::float64_t() const noexcept;
explicit constexpr operator std::bfloat16_t() const noexcept;

explicit constexpr operator decimal32() const noexcept;
explicit constexpr operator decimal128() const noexcept;

} //namespace decimal
} //namespace boost

Decimal128

Description

Decimal128 is the 128-bit version of the decimal interchange format, and has the following properties as defined in IEEE 754-2019 table 3.6

  • Storage width - 128 bits

  • Precision - 34 decimal digits (not bits like binary)

  • Max exponent - 6145

  • Max Value - 9.99999…​e6145

  • Smallest normalized value - 1.0000…​e-6142

  • Smallest subnormal - 1e-6176

#include <boost/decimal/decimal128.hpp>

namespace boost {
namespace decimal {

// Paragraph numbers are from ISO/IEC DTR 24733

// 3.2.4.1 construct/copy/destroy
constexpr decimal128() noexcept = default;

// 3.2.4.2 Conversion form floating-point type
template <typename Float>
explicit BOOST_DECIMAL_CXX20_CONSTEXPR decimal128(Float val) noexcept;

// 3.2.4.3 Conversion from integral type
template <typename Integer>
explicit constexpr decimal128(Integer val) noexcept;

template <typename Integral1, typename Integral2>
constexpr decimal128(Integral1 coeff, Integral2 exp, bool sign = false) noexcept;

template <typename Integral>
constexpr decimal128& operator=(const Integeral& RHS) noexcept;

// 3.2.4.4 Conversion to integral type
explicit constexpr operator int() const noexcept;
explicit constexpr operator unsigned() const noexcept;
explicit constexpr operator long() const noexcept;
explicit constexpr operator unsigned long() const noexcept;
explicit constexpr operator long long() const noexcept;
explicit constexpr operator unsigned long long() const noexcept;
explicit constexpr operator std::int8_t() const noexcept;
explicit constexpr operator std::uint8_t() const noexcept;
explicit constexpr operator std::int16_t() const noexcept;
explicit constexpr operator std::uint16_t() const noexcept;

// 3.2.4.5 increment and decrement operators:
constexpr decimal128& operator++();
constexpr decimal128  operator++(int);
constexpr decimal128& operator--();
constexpr decimal128  operator--(int);

// 3.2.4.6 compound assignment:
constexpr decimal128& operator+=(RHS rhs);
constexpr decimal128& operator-=(RHS rhs);
constexpr decimal128& operator*=(RHS rhs);
constexpr decimal128& operator/=(RHS rhs);

// 3.2.6 Conversion to floating-point type
explicit BOOST_DECIMAL_CXX20_CONSTEXPR operator float() const noexcept;
explicit BOOST_DECIMAL_CXX20_CONSTEXPR operator double() const noexcept;
explicit BOOST_DECIMAL_CXX20_CONSTEXPR operator long double() const noexcept;

// The following are available assuming a C++23 compiler that provides the header <stdfloat>
explicit constexpr operator std::float16_t() const noexcept;
explicit constexpr operator std::float32_t() const noexcept;
explicit constexpr operator std::float64_t() const noexcept;
explicit constexpr operator std::bfloat16_t() const noexcept;

explicit constexpr operator decimal32() const noexcept;
explicit constexpr operator decimal64() const noexcept;

} //namespace decimal
} //namespace boost

Fast Types

Now that we have seen the three basic types as specified in IEEE-754 there are three additional adjacent types: decimal32_fast, decimal64_fast, and decimal128_fast. These types yield identical computational results, but with ~3x faster performance. These types make the classic tradeoff of space for time as they require more storage width than the IEEE 754 conformant type.

Decimal32_fast

Description

decimal32_fast has the same ranges of values and representations as decimal32 but with greater performance. The performance changes by being non-IEEE 754 compliant so that the value does not have to be decoded from bits, but is instead directly represented internal to the type. As is often the case this trades space for time by having greater storage width requirements.

  • Storage width - At least 48bits (std::uint_fast32_t + std::uint_fast8_t + bool)

  • Precision - 7 decimal digits (not bits like binary)

  • Max exponent - 96

  • Max Value - 9.999999e96

  • Smallest normalized value - 1.000000e-95

  • Smallest subnormal - 1e-101

#include <boost/decimal/decimal32_fast.hpp>

namespace boost {
namespace decimal {

// Paragraph numbers are from ISO/IEC DTR 24733

// 3.2.2.1 construct/copy/destroy
constexpr decimal32_fast() noexcept = default;

// 3.2.2.2 Conversion form floating-point type
template <typename Float>
explicit BOOST_DECIMAL_CXX20_CONSTEXPR decimal32_fast(Float val) noexcept;

// 3.2.2.3 Conversion from integral type
template <typename Integer>
explicit constexpr decimal32_fast(Integer val) noexcept;

template <typename Integral1, typename Integral2>
constexpr decimal32_fast(Integral1 coeff, Integral2 exp, bool sign = false) noexcept;

template <typename Integral>
constexpr decimal32_fast& operator=(const Integeral& RHS) noexcept;

// 3.2.2.4 Conversion to integral type
explicit constexpr operator int() const noexcept;
explicit constexpr operator unsigned() const noexcept;
explicit constexpr operator long() const noexcept;
explicit constexpr operator unsigned long() const noexcept;
explicit constexpr operator long long() const noexcept;
explicit constexpr operator unsigned long long() const noexcept;
explicit constexpr operator std::int8_t() const noexcept;
explicit constexpr operator std::uint8_t() const noexcept;
explicit constexpr operator std::int16_t() const noexcept;
explicit constexpr operator std::uint16_t() const noexcept;

// 3.2.2.5 increment and decrement operators:
constexpr decimal32_fast& operator++();
constexpr decimal32_fast  operator++(int);
constexpr decimal32_fast& operator--();
constexpr decimal32_fast  operator--(int);

// 3.2.2.6 compound assignment:
constexpr decimal32_fast& operator+=(RHS rhs);
constexpr decimal32_fast& operator-=(RHS rhs);
constexpr decimal32_fast& operator*=(RHS rhs);
constexpr decimal32_fast& operator/=(RHS rhs);

// 3.2.6 Conversion to floating-point type
explicit BOOST_DECIMAL_CXX20_CONSTEXPR operator float() const noexcept;
explicit BOOST_DECIMAL_CXX20_CONSTEXPR operator double() const noexcept;
explicit BOOST_DECIMAL_CXX20_CONSTEXPR operator long double() const noexcept;

// The following are available assuming a C++23 compiler that provides the header <stdfloat>
explicit constexpr operator std::float16_t() const noexcept;
explicit constexpr operator std::float32_t() const noexcept;
explicit constexpr operator std::float64_t() const noexcept;
explicit constexpr operator std::bfloat16_t() const noexcept;

explicit constexpr operator decimal64() const noexcept;
explicit constexpr operator decimal128() const noexcept;

} //namespace decimal
} //namespace boost

Decimal64_fast

Description

decimal64_fast has the same ranges of values and representations as decimal64 but with greater performance. The performance changes by being non-IEEE 754 compliant so that the value does not have to be decoded from bits, but is instead directly represented internal to the type. As is often the case this trades space for time by having greater storage width requirements.

  • Storage width - At least 88 bits (std::uint_fast64_t + std::uint_fast_16_t + bool)

  • Precision - 16 decimal digits (not bits like binary)

  • Max exponent - 385

  • Max Value - 9.999999999999999e385

  • Smallest normalized value - 1.000000000000000e-382

  • Smallest subnormal - 1e-398

#include <boost/decimal/decimal64_fast.hpp>

namespace boost {
namespace decimal {

// Paragraph numbers are from ISO/IEC DTR 24733

// 3.2.3.1 construct/copy/destroy
constexpr decimal64_fast() noexcept = default;

// 3.2.2.2 Conversion form floating-point type
template <typename Float>
explicit BOOST_DECIMAL_CXX20_CONSTEXPR decimal64_fast(Float val) noexcept;

// 3.2.3.3 Conversion from integral type
template <typename Integer>
explicit constexpr decimal64_fast(Integer val) noexcept;

template <typename Integral1, typename Integral2>
constexpr decimal64_fast(Integral1 coeff, Integral2 exp, bool sign = false) noexcept;

template <typename Integral>
constexpr decimal64_fast& operator=(const Integeral& RHS) noexcept;

// 3.2.3.4 Conversion to integral type
explicit constexpr operator int() const noexcept;
explicit constexpr operator unsigned() const noexcept;
explicit constexpr operator long() const noexcept;
explicit constexpr operator unsigned long() const noexcept;
explicit constexpr operator long long() const noexcept;
explicit constexpr operator unsigned long long() const noexcept;
explicit constexpr operator std::int8_t() const noexcept;
explicit constexpr operator std::uint8_t() const noexcept;
explicit constexpr operator std::int16_t() const noexcept;
explicit constexpr operator std::uint16_t() const noexcept;

// 3.2.3.5 increment and decrement operators:
constexpr decimal64_fast& operator++();
constexpr decimal64_fast  operator++(int);
constexpr decimal64_fast& operator--();
constexpr decimal64_fast  operator--(int);

// 3.2.3.6 compound assignment:
constexpr decimal64_fast& operator+=(RHS rhs);
constexpr decimal64_fast& operator-=(RHS rhs);
constexpr decimal64_fast& operator*=(RHS rhs);
constexpr decimal64_fast& operator/=(RHS rhs);

// 3.2.6 Conversion to floating-point type
explicit BOOST_DECIMAL_CXX20_CONSTEXPR operator float() const noexcept;
explicit BOOST_DECIMAL_CXX20_CONSTEXPR operator double() const noexcept;
explicit BOOST_DECIMAL_CXX20_CONSTEXPR operator long double() const noexcept;

// The following are available assuming a C++23 compiler that provides the header <stdfloat>
explicit constexpr operator std::float16_t() const noexcept;
explicit constexpr operator std::float32_t() const noexcept;
explicit constexpr operator std::float64_t() const noexcept;
explicit constexpr operator std::bfloat16_t() const noexcept;

explicit constexpr operator decimal32() const noexcept;
explicit constexpr operator decimal128() const noexcept;

} //namespace decimal
} //namespace boost

Decimal128_fast

Description

decimal128_fast has the same ranges of values and representations as decimal128_fast but with greater performance. The performance changes by being non-IEEE 754 compliant so that the value does not have to be decoded from bits, but is instead directly represented internal to the type. As is often the case this trades space for time by having greater storage width requirements.

  • Storage width - at least 168 bits (uint128 + std::uint_fast32_t + bool)

  • Precision - 34 decimal digits (not bits like binary)

  • Max exponent - 6145

  • Max Value - 9.99999…​e6145

  • Smallest normalized value - 1.0000…​e-6142

  • Smallest subnormal - 1e-6176

#include <boost/decimal/decimal128_fast.hpp>

namespace boost {
namespace decimal {

// Paragraph numbers are from ISO/IEC DTR 24733

// 3.2.4.1 construct/copy/destroy
constexpr decimal128_fast() noexcept = default;

// 3.2.4.2 Conversion form floating-point type
template <typename Float>
explicit BOOST_DECIMAL_CXX20_CONSTEXPR decimal128_fast(Float val) noexcept;

// 3.2.4.3 Conversion from integral type
template <typename Integer>
explicit constexpr decimal128_fast(Integer val) noexcept;

template <typename Integral1, typename Integral2>
constexpr decimal128_fast(Integral1 coeff, Integral2 exp, bool sign = false) noexcept;

template <typename Integral>
constexpr decimal128_fast& operator=(const Integeral& RHS) noexcept;

// 3.2.4.4 Conversion to integral type
explicit constexpr operator int() const noexcept;
explicit constexpr operator unsigned() const noexcept;
explicit constexpr operator long() const noexcept;
explicit constexpr operator unsigned long() const noexcept;
explicit constexpr operator long long() const noexcept;
explicit constexpr operator unsigned long long() const noexcept;
explicit constexpr operator std::int8_t() const noexcept;
explicit constexpr operator std::uint8_t() const noexcept;
explicit constexpr operator std::int16_t() const noexcept;
explicit constexpr operator std::uint16_t() const noexcept;

// 3.2.4.5 increment and decrement operators:
constexpr decimal128_fast& operator++();
constexpr decimal128_fast  operator++(int);
constexpr decimal128_fast& operator--();
constexpr decimal128_fast  operator--(int);

// 3.2.4.6 compound assignment:
constexpr decimal128_fast& operator+=(RHS rhs);
constexpr decimal128_fast& operator-=(RHS rhs);
constexpr decimal128_fast& operator*=(RHS rhs);
constexpr decimal128_fast& operator/=(RHS rhs);

// 3.2.6 Conversion to floating-point type
explicit BOOST_DECIMAL_CXX20_CONSTEXPR operator float() const noexcept;
explicit BOOST_DECIMAL_CXX20_CONSTEXPR operator double() const noexcept;
explicit BOOST_DECIMAL_CXX20_CONSTEXPR operator long double() const noexcept;

// The following are available assuming a C++23 compiler that provides the header <stdfloat>
explicit constexpr operator std::float16_t() const noexcept;
explicit constexpr operator std::float32_t() const noexcept;
explicit constexpr operator std::float64_t() const noexcept;
explicit constexpr operator std::bfloat16_t() const noexcept;

explicit constexpr operator decimal32() const noexcept;
explicit constexpr operator decimal64() const noexcept;

} //namespace decimal
} //namespace boost

Fast Type Conversions

Since we have non-IEEE 754 compliant types we offer a set of functions that allow their conversion to and from the IEEE 754 compliant BID layout. These functions allow lossless conversion with more compact storage.

namespace boost {
namespace decimal {

namespace detail {

struct uint128
{
    std::uint64_t hi;
    std::uint64_t lo;
};

} // namespace detail

BOOST_DECIMAL_CXX20_CONSTEXPR std::uint32_t to_bid_d32(decimal32 val) noexcept;

BOOST_DECIMAL_CXX20_CONSTEXPR std::uint32_t to_bid_d32f(decimal32_fast val) noexcept;

BOOST_DECIMAL_CXX20_CONSTEXPR std::uint64_t to_bid_d64(decimal64 val) noexcept;

BOOST_DECIMAL_CXX20_CONSTEXPR std::uint64_t to_bid_d64f(decimal64_fast val) noexcept;

BOOST_DECIMAL_CXX20_CONSTEXPR detail::uint128 to_bid_d128(decimal128 val) noexcept;

template <typename T>
BOOST_DECIMAL_CXX20_CONSTEXPR auto to_bid(T val) noexcept;

template <typename T = decimal32_fast>
BOOST_DECIMAL_CXX20_CONSTEXPR T from_bid(std::uint32_t bits) noexcept;

template <typename T = decimal64_fast>
BOOST_DECIMAL_CXX20_CONSTEXPR T from_bid(std::uint64_t bits) noexcept;

template <typename T = decimal128>
BOOST_DECIMAL_CXX20_CONSTEXPR T from_bid(detail::uint128 bits) noexcept;

} // namespace decimal
} // namespace boost

Literals Support

The following literals are offered to construct each of the types:

namespace boost {
namespace decimal {

constexpr auto operator  "" _DF(const char* str) -> decimal32
constexpr auto operator  "" _df(const char* str) -> decimal32

constexpr auto operator  "" _DF(unsigned long long v) -> decimal32
constexpr auto operator  "" _dF(unsigned long long v) -> decimal32

constexpr auto operator  "" _DD(const char* str) -> decimal64
constexpr auto operator  "" _dd(const char* str) -> decimal64

constexpr auto operator  "" _DD(unsigned long long v) -> decimal64
constexpr auto operator  "" _dd(unsigned long long v) -> decimal64

constexpr auto operator  "" _DL(const char* str) -> decimal128
constexpr auto operator  "" _dl(const char* str) -> decimal128

constexpr auto operator  "" _DL(unsigned long long v) -> decimal128
constexpr auto operator  "" _dl(unsigned long long v) -> decimal128

} //namespace decimal
} //namespace boost
Important
0.2_DF is equivalent to calling decimal32{2, -1}. These are different from calling decimal32{0.2} which converts from a floating-point value which may or may not be exactly represented.

Numeric Constants

Overview

Contains all constants provided by C++20’s <numbers> specialized for the decimal types. These do not require C++20.

  • e_v - Euler’s Number

  • log2e_v - log2(e)

  • log10e_v - log10(e)

  • pi_v - pi

  • inv_pi_v - 1/pi

  • inv_sqrtpi_v - 1 / sqrt(pi)

  • ln2_v - ln(2)

  • ln10_v - ln(10)

  • sqrt2_v - sqrt(2)

  • sqrt3_v - sqrt(3)

  • sqrt10_v - sqrt(10)

  • inv_sqrt3_v - 1 / sqrt(3)

  • cbrt2_v - cbrt(2)

  • cbrt10_v - cbrt(10)

  • egamma_v - Euler–Mascheroni constant

  • phi_v - The Golden Ratio

There are also non-template variables that provide the constant as a decimal64 type.

Reference

#include <boost/decimal/numbers.hpp>

namespace boost {
namespace decimal {

template <typename Decimal>
static constexpr Decimal e_v;

template <typename Decimal>
static constexpr Decimal log2e_v;

template <typename Decimal>
static constexpr Decimal log10e_v;

template <typename Decimal>
static constexpr Decimal pi_v;

template <typename Decimal>
static constexpr Decimal inv_pi_v;

template <typename Decimal>
static constexpr Decimal inv_sqrtpi_v;

template <typename Decimal>
static constexpr Decimal ln2_v;

template <typename Decimal>
static constexpr Decimal ln10_v;

template <typename Decimal>
static constexpr Decimal sqrt2_v;

template <typename Decimal>
static constexpr Decimal sqrt3_v;

template <typename Decimal>
static constexpr Decimal sqrt10_v;

template <typename Decimal>
static constexpr Decimal inv_sqrt2_v;

template <typename Decimal>
static constexpr Decimal inv_sqrt3_v;

template <typename Decimal>
static constexpr Decimal cbrt2_v;

template <typename Decimal>
static constexpr Decimal cbrt10_v;

template <typename Decimal>
static constexpr Decimal egamma_v;

template <typename Decimal>
static constexpr Decimal phi_v;

static constexpr auto e {e_v<decimal64>};
static constexpr auto log2e {log2e_v<decimal64>};
static constexpr auto log10e {log10e_v<decimal64>};
static constexpr auto pi {pi_v<decimal64>};
static constexpr auto inv_pi {inv_pi_v<decimal64>};
static constexpr auto inv_sqrtpi {inv_sqrtpi_v<decimal64>};
static constexpr auto ln2 {ln2_v<decimal64>};
static constexpr auto ln10 {ln10_v<decimal64>};
static constexpr auto sqrt2 {sqrt2_v<decimal64>};
static constexpr auto sqrt3 {sqrt3_v<decimal64>};
static constexpr auto sqrt10 {sqrt10_v<decimal64>};
static constexpr auto inv_sqrt2 {inv_sqrt2_v<decimal64>};
static constexpr auto inv_sqrt3 {inv_sqrt3_v<decimal64>};
static constexpr auto cbrt2 {cbrt2_v<decimal64>};
static constexpr auto cbrt10 {cbrt10_v<decimal64>};
static constexpr auto egamma {egamma_v<decimal64>};
static constexpr auto phi {phi_v<decimal64>};

} //namespace decimal
} //namespace boost

cmath support

<cmath>

Decimal contains overloads for all functions from <cmath>, and they have the same handling as built-in floating point types. They are also all constexpr with C++14 unlike the built-in floating point types which require either C++23 or 26.

Exponential Functions

Power Functions

Trigonometric Functions

Hyperbolic Functions

Error and Gamma Functions

Nearest integer floating point operations

Floating point manipulation functions

Summary

namespace boost {
namespace decimal {

template <typename DecimalType>
constexpr DecimalType abs(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType fabs(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType abs(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType fmod(DecimalType x, DecimalType y) noexcept;

template <typename DecimalType>
constexpr DecimalType remainder(DecimalType x, DecimalType y) noexcept;

template <typename DecimalType>
constexpr DecimalType remquo(DecimalType x, DecimalType y, int* quo) noexcept;

template <typename DecimalType>
constexpr DecimalType fma(DecimalType x, DecimalType y, DecimalType z) noexcept;

template <typename DecimalType>
constexpr DecimalType fmax(DecimalType x, DecimalType y, int* quo) noexcept;

template <typename DecimalType>
constexpr DecimalType fmin(DecimalType x, DecimalType y, int* quo) noexcept;

template <typename DecimalType>
constexpr DecimalType fdim(DecimalType x, DecimalType y, int* quo) noexcept;

constexpr decimal32 nand32(const char* arg) noexcept;
constexpr decimal64 nand64(const char* arg) noexcept;
constexpr decimal128 nand128(const char* arg) noexcept;

template <typename DecimalType>
constexpr DecimalType exp(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType exp2(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType expm1(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType log(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType log10(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType log2(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType log1p(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType pow(DecimalType x, DecimalType y) noexcept;

template <typename DecimalType>
constexpr DecimalType sqrt(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType cbrt(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType hypot(DecimalType x, DecimalType y) noexcept;

template <typename DecimalType>
constexpr DecimalType hypot(DecimalType x, DecimalType y, DecimalType z) noexcept;

template <typename DecimalType>
constexpr DecimalType sin(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType cos(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType tan(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType asin(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType acos(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType atan(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType atan2(DecimalType x, DecimalType y) noexcept;

template <typename DecimalType>
constexpr DecimalType sinh(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType cosh(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType tanh(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType asinh(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType acosh(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType atanh(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType erf(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType erfc(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType tgamma(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType lgamma(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType ceil(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType floor(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType trunc(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType round(DecimalType x) noexcept;

template <typename DecimalType>
constexpr long lround(DecimalType x) noexcept;

template <typename DecimalType>
constexpr long long llround(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType nearbyint(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType rint(DecimalType x) noexcept;

template <typename DecimalType>
constexpr long lrint(DecimalType x) noexcept;

template <typename DecimalType>
constexpr long long llrint(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType frexp(DecimalType x, int* exp) noexcept;

template <typename DecimalType>
constexpr DecimalType ldexp(DecimalType x, int exp) noexcept;

template <typename DecimalType>
constexpr DecimalType modf(DecimalType x, DecimalType* iptr) noexcept;

template <typename DecimalType>
constexpr DecimalType scalbn(DecimalType x, int exp) noexcept;

template <typename DecimalType>
constexpr DecimalType scalbln(DecimalType x, long exp) noexcept;

template <typename DecimalType>
constexpr int ilogb(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType logb(DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType nextafter(DecimalType from, DecimalType to) noexcept;

template <typename DecimalType>
constexpr DecimalType nexttoward(DecimalType from, long double to) noexcept;

template <typename DecimalType>
constexpr DecimalType copysign(DecimalType mag, DecimalType sgn) noexcept;

template <typename DecimalType>
constexpr int fpclassify(DecimalType x) noexcept;

template <typename DecimalType>
constexpr bool isfinite(DecimalType x) noexcept;

template <typename DecimalType>
constexpr bool isinf(DecimalType x) noexcept;

template <typename DecimalType>
constexpr bool isnan(DecimalType x) noexcept;

template <typename DecimalType>
constexpr bool isnormal(DecimalType x) noexcept;

template <typename DecimalType>
constexpr bool signbit(DecimalType x) noexcept;

template <typename DecimalType>
constexpr bool isgreater(DecimalType x, DecimalType y) noexcept;

template <typename DecimalType>
constexpr bool isgreaterequal(DecimalType x, DecimalType y) noexcept;

template <typename DecimalType>
constexpr bool isless(DecimalType x, DecimalType y) noexcept;

template <typename DecimalType>
constexpr bool islessgreater(DecimalType x, DecimalType y) noexcept;

template <typename DecimalType>
constexpr bool isunordered(DecimalType x, DecimalType y) noexcept;

} //namespace decimal
} //namespace boost

C++17 Mathematical Special Functions

The following functions have been implemented for decimal types:

Summary

namespace boost {
namespace decimal {

template <typename DecimalType>
constexpr DecimalType assoc_laguerre(unsigned int n, unsigned int m, DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType hermite(unsigned int n, DecimalType x) noexcept;

template <typename DecimalType>
constexpr DecimalType laguerre(unsigned int n, DecimalType x) noexcept;

} //namespace decimal
} //namespace boost

Non-standard Functions

The following are convenience functions, or are prescribed in IEEE 754-2019 as required for decimal floating point types.

issignaling

template <typename Decimal>
constexpr bool issignaling(Decimal x) noexcept;

Effects: If x is an sNaN returns true, otherwise returns false.

samequantum

template <typename Decimal>
constexpr bool samequantum(Decimal x, Decimal y) noexcept;

constexpr bool samequantumd32(decimal32 x, decimal32 y) noexcept;
constexpr bool samequantumd64(decimal64 x, decimal64 y) noexcept;
constexpr bool samequantumd128(decimal128 x, decimal128 y) noexcept;

Effects: Determines if the quantum (unbiased) exponents of x and y are the same.

If both x and y are NaN, or infinity, they have the same quantum exponents.

If exactly one operand is infinity or exactly one operand is NaN, they do not have the same quantum exponents.

quantexp

template <typename Decimal>
constexpr int quantexp(Decimal x) noexcept;

constexpr bool quantexp32(decimal32 x) noexcept;
constexpr bool quantexp64(decimal64 x) noexcept;
constexpr bool quantexp128(decimal128 x) noexcept;

Effects: if x is finite, returns its quantum exponent.

Otherwise, a domain error occurs and INT_MIN is returned.

quantized

template <typename Decimal>
constexpr Decimal quantized(Decimal x, Decimal y) noexcept;

constexpr decimal32 quantized32(decimal32 x, decimal32 y) noexcept;
constexpr decimal64 quantized64(decimal64 x, decimal64 y) noexcept;
constexpr decimal128 quantized128(decimal128 x, decimal128 y) noexcept;

Returns: a number that is equal in value (except for any rounding) and sign to x, and which has an exponent set to be equal to the exponent of y.

If the exponent is being increased, the value is correctly rounded according to the current rounding mode;

If the result does not have the same value as x, the "inexact" floating-point exception is raised.

If the exponent is being decreased and the significand of the result has more digits than the type would allow, the "invalid" floating-point exception is raised and the result is sNaN.

If one or both operands are NaN the result is sNaN.

Otherwise, if only one operand is infinity, the "invalid" floating-point exception is raised and the result is sNaN.

If both operands are infinity, the result is infinity, with the same sign as x.

The quantize functions do not signal underflow.

frexp10

template <typename Decimal>
constexpr auto frexp10(Decimal num, int* expptr) noexcept;

constexpr std::uint32_t frexpd32(decimal32 num, int* expptr) noexcept;
constexpr std::uint64_t frexpd64(decimal64 num, int* expptr) noexcept;
constexpr boost::decimal::detail::uint128 frexpd128(decimal128 num, int* expptr) noexcept;

This function is very similar to frexp, but returns the significand and an integral power of 10 since the FLT_RADIX of this type is 10. The significand is normalized to the number of digits of precision the type has (e.g. for decimal32 it is [1'000'000, 9'999'999]).

trunc_to

template <typename Decimal>
constexpr Decimal trunc_to(Decimal val, int precision = 0);

The function returns the decimal type with number of fractional digits equal to the value of precision. trunc_to is similar to trunc, and with the default precision argument of 0 it is identical.

cstdlib support

<cstdlib>

The following functions analogous to those from <cstdlib> are provided:

namespace boost {
namespace decimal {

inline decimal64 strtod(const char* str, char** endptr) noexcept;
inline decimal64 wcstod(const wchar_t* str, wchar_t** endptr) noexcept;

inline decimal32 strtod32(const char* str, char** endptr) noexcept;
inline decimal32 wcstod32(const wchar_t* str, wchar_t** endptr) noexcept;

inline decimal64 strtod64(const char* str, char** endptr) noexcept;
inline decimal64 wcstod64(const wchar_t* str, wchar_t** endptr) noexcept;

inline decimal128 strtod128(const char* str, char** endptr) noexcept;
inline decimal128 wcstod128(const wchar_t* str, wchar_t** endptr) noexcept;

} //namespace decimal
} //namespace boost

charconv support

<charconv>

The following functions analogous to those from <charconv> are provided:

Important
std::from_chars has an open issue with LWG here: https://cplusplus.github.io/LWG/lwg-active.html#3081. The standard for <charconv> does not distinguish between underflow and overflow like strtod does. boost::decimal::from_chars modifies value in order to communicate this to the user in a divergence from the standard. This behavior is the same as that of boost::charconv::from_chars_erange.
namespace boost {
namespace decimal {

enum class chars_format : unsigned
{
    scientific = 1 << 0,
    fixed = 1 << 1,
    hex = 1 << 2,
    general = fixed | scientific
};

struct from_chars_result
{
    const char* ptr;
    std::errc ptr;

    friend constexpr auto operator==(const from_chars_result& lhs, const from_chars_result& rhs) noexcept = default;

    constexpr explicit operator bool() const noexcept { return ec == std::errc{}; }
}

struct to_chars_result
{
    char* ptr;
    std::errc ptr;

    friend constexpr auto operator==(const to_chars_result& lhs, const to_chars_result& rhs) noexcept = default;

    constexpr explicit operator bool() const noexcept { return ec == std::errc{}; }
}

template <typename DecimalType>
constexpr from_chars_result from_chars(const char* first, const char* last, DecimalType& value, chars_format fmt = chars_format::general)

template <typename DecimalType>
BOOST_DECIMAL_CONSTEXPR to_chars_result to_chars(char* first, char* last, DecimalType value) noexcept;

template <typename DecimalType>
BOOST_DECIMAL_CONSTEXPR to_chars_result to_chars(char* first, char* last, DecimalType value, chars_format fmt) noexcept;

template <typename DecimalType>
BOOST_DECIMAL_CONSTEXPR to_chars_result to_chars(char* first, char* last, DecimalType value, chars_format fmt, int precision) noexcept;

} //namespace decimal
} //namespace boost
Note
BOOST_DECIMAL_CONSTEXPR is defined if:
  • _MSC_FULL_VER >= 192528326

  • __GNUC__ >= 9

  • Compiler has: __builtin_is_constant_evaluated()

  • C++20 support with: std::is_constant_evaluated()

The library offers an additional feature for sizing buffers without specified precision and in general format

namespace boost {
namespace decimal {

template <typename T>
struct limits
{
    static constexpr int max_chars;
}

} //namespace decimal
} //namespace boost

The member can then be used to size buffers such as:

#include <boost/decimal.hpp>
#include <iostream>

int main()
{
    using namespace boost::decimal;

    decimal32 val {5, -1};

    char buffer[limits<T>::max_chars];

    auto r_to = to_chars(buffer, buffer + sizeof(buffer), val);
    *r_to.ptr = '\0';

    std::cout << buffer << std::endl;

    return 0;
}

cfenv support

<cfenv>

As opposed to binary floating point types IEEE 754 defined 5 rounding modes instead of 4. They are:

  1. Downward

  2. To nearest

  3. To nearest from zero

  4. Toward zero

  5. Upward

The default rounding mode is to nearest from zero.

Important
The rounding mode can only be changed at runtime. All constexpr calculations will use the default of to nearest from zero.
namespace boost {
namespace decimal {

enum class rounding_mode : unsigned
{
    fe_dec_downward = 1 << 0,
    fe_dec_to_nearest = 1 << 1,
    fe_dec_to_nearest_from_zero = 1 << 2,
    fe_dec_toward_zero = 1 << 3,
    fe_dec_upward = 1 << 4,
    fe_dec_default = fe_dec_to_nearest_from_zero
};

rounding_mode fegetround() noexcept;
rounding_mode fesetround(rounding_mode round) noexcept;

} //namespace decimal
} //namespace boost

cfloat support

<cfloat>

The following macros analogous to those from <cfloat> for the decimal floating point types:

// Number of digits in the coefficient
#define BOOST_DECIMAL_DEC32_MANT_DIG  7
#define BOOST_DECIMAL_DEC64_MANT_DIG  16
#define BOOST_DECIMAL_DEC128_MANT_DIG 34

// Minimum exponent
#define BOOST_DECIMAL_DEC32_MIN_EXP  -94
#define BOOST_DECIMAL_DEC64_MIN_EXP  -382
#define BOOST_DECIMAL_DEC128_MIN_EXP -6142

// Maximum exponent
#define BOOST_DECIMAL_DEC32_MAX_EXP  97
#define BOOST_DECIMAL_DEC64_MAX_EXP  385
#define BOOST_DECIMAL_DEC128_MAX_EXP 6145

// Maximum Finite Value
#define BOOST_DECIMAL_DEC32_MAX  std::numeric_limits<boost::decimal::decimal32>::max()
#define BOOST_DECIMAL_DEC64_MAX  std::numeric_limits<boost::decimal::decimal64>::max()
#define BOOST_DECIMAL_DEC128_MAX std::numeric_limits<boost::decimal::decimal128>::max()

// Minimum positive normal vlaue
#define BOOST_DECIMAL_DEC32_MIN std::numeric_limits<boost::decimal::decimal32>::min()
#define BOOST_DECIMAL_DEC64_MIN   std::numeric_limits<boost::decimal::decimal64>::min()
#define BOOST_DECIMAL_DEC128_MIN std::numeric_limits<boost::decimal::decimal128>::min()

// Minimum positive sub-normal value
#define BOOST_DECIMAL_DEC32_MAX  std::numeric_limits<boost::decimal::decimal32>::denorm_min()
#define BOOST_DECIMAL_DEC64_MAX  std::numeric_limits<boost::decimal::decimal64>::denorm_min()
#define BOOST_DECIMAL_DEC128_MAX std::numeric_limits<boost::decimal::decimal128>::denorm_min()

Additionally BOOST_DECIMAL_DEC_EVAL_METHOD is similar to FLT_EVAL_METHOD: https://en.cppreference.com/w/cpp/types/climits/FLT_EVAL_METHOD

The valid values are:

  • 0: all operations evaluated in the range and precision of the native type

  • 1: all decimal32 operations are evaluated with the precision and range of decimal64 internally, and returned as decimal64

  • 2: all decimal32 and decimal64 operations are evaluated with the precision and range of decimal128 internally, and returned as the original type.

To use the functionallity you must #define BOOST_DECIMAL_DEC_EVAL_METHOD to the value you want before you #include <boost/decimal.hpp>.

cstdio support

<cstdio>

The following functions analogous to those from <cstdio> are provided:

namespace boost {
namespace decimal {

template <typename... Dec>
int snprintf(char* buffer, std::size_t buf_size, const char* format, Dec... value) noexcept;

template <typename... Dec>
int sprintf(char* buffer, const char* format, Dec... value) noexcept;

template <typename... Dec>
int fprintf(std::FILE* buffer, const char* format, Dec... values) noexcept;

template <typename... Dec>
int printf(const char* format, Dec... values) noexcept;

} //namespace decimal
} //namespace boost
Warning
In the interest of safety sprintf simply calls snprintf with buf_size equal to sizeof(buffer).

Type Modifiers

The type modifiers to be used are:

  • "H" for decimal32

  • "D" for decimal64

  • "DD" for decimal128

The remaining specifications of cstdio are the same as usual:

  • "g" or "G" for general format

  • "e" or "E" for scientific format

  • "f" for fixed format

  • "a" or "A" for hex format

Note
The uppercase format will return with all applicable values in uppercase (e.g. 3.14E+02 vs 3.14e+02)

Examples

Here are some example formats:

  • "%Hg" will print a decimal32 in general format

  • "%.3De" will print a decimal64 in scientific format with 3 digits of precision

  • "%.5DDA" will print a decimal128 in hex format with 5 digits of precision and all letters will be capitalized (e.g. 1.F2CP+2 vs 1.f2cp+2)

functional support

<functional>

The following functions from <functional> are overloaded:

namespace std {

template <>
struct hash<boost::decimal::decimal32>;

template <>
struct hash<boost::decimal::decimal64>;

template <>
struct hash<boost::decimal::decimal128>;


} //namespace std

limits support

<limits>

The following from <limits> are overloaded for each type with associated values for reference:

namespace std {

template <>
#ifdef _MSC_VER
class numeric_limits<boost::decimal::decimal32>
#else
struct numeric_limits<boost::decimal::decimal32>
#endif
{

#ifdef _MSC_VER
public:
#endif

    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool is_specialized = true;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool is_signed = true;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool is_integer = false;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool is_exact = false;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool has_infinity = true;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool has_quiet_NaN = true;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool has_signaling_NaN = true;

    // These members were deprecated in C++23
    #if ((!defined(_MSC_VER) && (__cplusplus <= 202002L)) || (defined(_MSC_VER) && (_MSVC_LANG <= 202002L)))
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr std::float_denorm_style has_denorm = std::denorm_present;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool has_denorm_loss = true;
    #endif

    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr std::float_round_style round_style = std::round_indeterminate;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool is_iec559 = true;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool is_bounded = true;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool is_modulo = false;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr int digits = 7;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr int digits10 = digits;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr int max_digits10 = digits;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr int radix = 10;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr int min_exponent = -95;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr int min_exponent10 = min_exponent;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr int max_exponent = 96;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr int max_exponent10 = max_exponent;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool traps = numeric_limits<std::uint32_t>::traps;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool tinyness_before = true;

    // Member functions
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr boost::decimal::decimal32 min()
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr boost::decimal::decimal32 max();
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr boost::decimal::decimal32 lowest();
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr boost::decimal::decimal32 epsilon();
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr boost::decimal::decimal32 round_error();
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr boost::decimal::decimal32 infinity();
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr boost::decimal::decimal32 quiet_NaN();
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr boost::decimal::decimal32 signaling_NaN();
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr boost::decimal::decimal32 denorm_min();
};

template <>
#ifdef _MSC_VER
class numeric_limits<boost::decimal::decimal64>
#else
struct numeric_limits<boost::decimal::decimal64>
#endif
{

#ifdef _MSC_VER
    public:
#endif

    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool is_specialized = true;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool is_signed = true;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool is_integer = false;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool is_exact = false;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool has_infinity = true;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool has_quiet_NaN = true;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool has_signaling_NaN = true;

    // These members were deprecated in C++23
    #if ((!defined(_MSC_VER) && (__cplusplus <= 202002L)) || (defined(_MSC_VER) && (_MSVC_LANG <= 202002L)))
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr std::float_denorm_style has_denorm = std::denorm_present;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool has_denorm_loss = true;
    #endif

    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr std::float_round_style round_style = std::round_indeterminate;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool is_iec559 = true;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool is_bounded = true;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool is_modulo = false;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr int  digits = 16;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr int  digits10 = digits;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr int  max_digits10 = digits;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr int  radix = 10;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr int  min_exponent = -382;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr int  min_exponent10 = min_exponent;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr int  max_exponent = 385;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr int  max_exponent10 = max_exponent;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool traps = numeric_limits<std::uint64_t>::traps;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool tinyness_before = true;

    // Member functions
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr boost::decimal::decimal64 min()
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr boost::decimal::decimal64 max();
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr boost::decimal::decimal64 lowest();
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr boost::decimal::decimal64 epsilon();
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr boost::decimal::decimal64 round_error();
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr boost::decimal::decimal64 infinity();
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr boost::decimal::decimal64 quiet_NaN();
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr boost::decimal::decimal64 signaling_NaN();
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr boost::decimal::decimal64 denorm_min();
};

template<>
#ifdef _MSC_VER
class numeric_limits<boost::decimal::decimal128>
#else
struct numeric_limits<boost::decimal::decimal128>
#endif
{

#ifdef _MSC_VER
    public:
#endif

    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool is_specialized = true;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool is_signed = true;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool is_integer = false;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool is_exact = false;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool has_infinity = true;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool has_quiet_NaN = true;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool has_signaling_NaN = true;

    // These members were deprecated in C++23
    #if ((!defined(_MSC_VER) && (__cplusplus <= 202002L)) || (defined(_MSC_VER) && (_MSVC_LANG <= 202002L)))
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr std::float_denorm_style has_denorm = std::denorm_present;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool has_denorm_loss = true;
    #endif

    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr std::float_round_style round_style = std::round_indeterminate;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool is_iec559 = true;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool is_bounded = true;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool is_modulo = false;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr int  digits = 34;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr int  digits10 = digits;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr int  max_digits10 = digits;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr int  radix = 10;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr int  min_exponent = -6142;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr int  min_exponent10 = min_exponent;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr int  max_exponent = 6145;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr int  max_exponent10 = max_exponent;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool traps = numeric_limits<std::uint64_t>::traps;
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr bool tinyness_before = true;

    // Member functions
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr boost::decimal::decimal128 min()
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr boost::decimal::decimal128 max();
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr boost::decimal::decimal128 lowest();
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr boost::decimal::decimal128 epsilon();
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr boost::decimal::decimal128 round_error();
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr boost::decimal::decimal128 infinity();
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr boost::decimal::decimal128 quiet_NaN();
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr boost::decimal::decimal128 signaling_NaN();
    BOOST_DECIMAL_ATTRIBUTE_UNUSED static constexpr boost::decimal::decimal128 denorm_min();
};

} // Namespace std

Configuration Macros

The following configuration macros are available:

  • BOOST_DECIMAL_DISABLE_CASSERT: Disables the use of <cassert> and all run-time assertions.

  • BOOST_DECIMAL_DISABLE_IOSTREAM: Disables the use of I/O streaming and removes all associated headers (e.g. <iostream>, <iosfwd>, <cwchar>, etc.)

  • BOOST_DECIMAL_DISABLE_CLIB: Defines both of the above macros. In testing this reduces ROM usage by ~50%.

  • BOOST_DECIMAL_ALLOW_IMPLICIT_CONVERSIONS: Allows a binary floating-point type (e.g. double) to be implicitly converted to a decimal floating point type. This option is not recommended, but can be useful if you want to use specific functionality from the standard library with internal conversions such as:

constexpr decimal64 half {5, -1};
std::complex<decimal64> test_val {half, half};
const auto res = std::acos(test_val);
  • BOOST_DECIMAL_FAST_MATH performs optimizations similar to that of the -ffast-math compiler flag such as removing all checks for non-finite values. This flag increases the performance of the basis operations (e.g. add, sub, mul, div, and comparisons) by up to 20%.

type_traits support

<type_traits>

The following type traits from boost.type_traits have been overloaded for the decimal types:

namespace boost {

template <> struct is_arithmetic<boost::decimal::decimal32> : public true_type {};
template <> struct is_arithmetic<boost::decimal::decimal64> : public true_type {};
template <> struct is_arithmetic<boost::decimal::decimal128> : public true_type {};

template <> struct is_fundamental<boost::decimal::decimal32> : public true_type {};
template <> struct is_fundamental<boost::decimal::decimal64> : public true_type {};
template <> struct is_fundamental<boost::decimal::decimal128> : public true_type {};

template <> struct is_scalar<boost::decimal::decimal32> : public true_type {};
template <> struct is_scalar<boost::decimal::decimal64> : public true_type {};
template <> struct is_scalar<boost::decimal::decimal128> : public true_type {};

template <> struct is_class<boost::decimal::decimal32> : public false_type {};
template <> struct is_class<boost::decimal::decimal64> : public false_type {};
template <> struct is_class<boost::decimal::decimal128> : public false_type {};

template <> struct is_pod<boost::decimal::decimal32> : public true_type {};
template <> struct is_pod<boost::decimal::decimal64> : public true_type {};
template <> struct is_pod<boost::decimal::decimal128> : public true_type {};

} // namespace boost
Caution
These overloads only exist if <boost/type_traits.hpp> is installed. In a standalone environment they do not exist.

An additional type_trait has been added is_decimal_floating_point, which is defined as follows:

namespace boost {

template <typename T> struct is_decimal_floating_point : public false_type{};
template <typename T> struct is_decimal_floating_point<const T> : public is_decimal_floating_point<T>{};
template <typename T> struct is_decimal_floating_point<volatile const T> : public is_decimal_floating_point<T>{};
template <typename T> struct is_decimal_floating_point<volatile T> : public is_decimal_floating_point<T>{};

template <> struct is_decimal_floating_point<boost::decimal::decimal32> : public true_type{};
template <> struct is_decimal_floating_point<boost::decimal::decimal64> : public true_type{};
template <> struct is_decimal_floating_point<boost::decimal::decimal128> : public true_type{};

} // namespace boost

Examples

All examples can be found in the library examples/ folder as well.

Construction from an Integer and Exponent

#include <boost/decimal.hpp>
#include <iostream>

int main()
{
    constexpr boost::decimal::decimal32 a {2, -1}; // Constructs the number 0.2
    constexpr boost::decimal::decimal32 b {1, -1}; // Constructs the number 0.1
    boost::decimal::decimal32 sum {a + b};

    std::cout << sum << std::endl; // prints 0.3

    const boost::decimal::decimal32 neg_a {2, -1, true}; // Constructs the number -0.2

    sum += neg_a;

    std::cout << sum << std::endl; // Prints 0.1

    return 0;
}

This is the recommended way of constructing a fractional number as opposed to decimal32 a {0.2}. The representation is exact with integers whereas you may get surprising or unwanted conversion from binary floating point

Promotion

#include <boost/decimal.hpp>
#include <type_traits>
#include <cassert>

int main()
{
    using namespace boost::decimal;

    decimal32 x {1}; // Constructs from an integer
    decimal64 y {2};

    auto sum {x + y};

    assert(std::is_same<decimal64, decltype(sum)>::value);

    return 0;
}

charconv

#include <boost/decimal.hpp>
#include <iostream>
#include <cassert>

int main()
{
    using namespace boost::decimal;

    decimal64 val {0.25}; // Construction from a double (not recommended but explicit construction is allowed)

    char buffer[256];
    auto r_to = to_chars(buffer, buffer + sizeof(buffer) - 1, val);
    assert(r_to); // checks std::errc()
    *r_to.ptr = '\0';

    decimal64 return_value;
    auto r_from = from_chars(buffer, buffer + std::strlen(buffer), return_value);
    assert(r_from);

    assert(val == return_value);

    std::cout << " Initial Value: " << val << '\n'
              << "Returned Value: " << return_value << std::endl;

    return 0;
}

Rounding Mode

#include <boost/decimal.hpp>
#include <cassert>

int main()
{
    auto default_rounding_mode = boost::decimal::fegetround(); // Default is fe_dec_to_nearest_from_zero

    auto new_rounding_mode = boost::decimal::fesetround(boost::decimal::rounding_mode::fe_dec_to_nearest);

    assert(default_rounding_mode != new_rounding_mode);

    return 0;
}

Generic Programming

#include <boost/decimal.hpp>
#include <limits>
#include <cmath>

int error_counter = 0;

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 overloaded for all decimal types
}

template <typename T>
void test(T val)
{
    using std::sin; // ADL allows builtin and decimal types to both be used
    if (!float_equal(sin(val), -sin(-val))) // sin(x) == -sin(-x)
    {
        ++error_counter;
    }
}

int main()
{
    test(-0.5F);
    test(-0.5);
    test(-0.5L);

    test(boost::decimal::decimal32{5, -1, true});
    test(boost::decimal::decimal64{5, -1, true});
    test(boost::decimal::decimal128{5, -1, true});

    return error_counter;
}

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 overloaded for all decimal types
}


int main()
{
    using namespace boost::decimal;

    const auto pi_32 {"3.141592653589793238"_DF};
    const auto pi_64 {"3.141592653589793238"_DD};

    assert(float_equal(pi_32, static_cast<decimal32>(pi_64))); // Explicit conversion between decimal types
    assert(float_equal(pi_32, boost::decimal::numbers::pi_v<decimal32>)); // Constants available in numbers namespace
    assert(float_equal(pi_64, numbers::pi)); // Default constant type is decimal64

    return 0;
}

Benchmarks

This section describes a range of performance benchmarks that have been run comparing this library with the standard library, and how to run your own benchmarks if required.

The values in the ratio column are how many times longer running a specific operation takes in comparison to the same operation with a double.

Important
On nearly all platforms there is hardware support for binary floating point math, so we are comparing hardware to software runtimes; Decimal will be slower

How to run the Benchmarks

To run the benchmarks yourself, navigate to the test folder and define BOOST_DECIMAL_RUN_BENCHMARKS when running the tests. An example on Linux with b2: ../../../b2 cxxstd=20 toolset=gcc-13 define=BOOST_DECIMAL_RUN_BENCHMARKS benchmarks -a release .

Comparisons

The benchmark for comparisons generates a random vector containing 2,000,000 elements and does operations >, >=, <, <=, ==, and != between vec[i] and vec[i + 1]. This is repeated 5 times to generate stable results.

M1 macOS Results

Run using a Macbook pro with M1 pro chipset running macOS Sonoma 14.4.1 and homebrew Clang 18.1.4

Type

Runtime (us)

Ratio to double

float

8587

1.376

double

6240

1.000

decimal32

275,597

44.166

decimal64

296,929

47.587

decimal128

821,847

131.706

decimal32_fast

99,664

15.972

decimal64_fast

102,132

16.367

decimal128_fast

146,302

23.446

Basic Operations

The benchmark for these operations generates a random vector containing 2,000,000 elements and does operations +, -, *, / between vec[i] and vec[i + 1]. This is repeated 5 times to generate stable results.

M1 macOS Results

Run using a Macbook pro with M1 pro chipset running macOS Sonoma 14.4.1 and homebrew Clang 18.1.4

Addition

Type

Runtime (us)

Ratio to double

float

2705

0.859

double

3148

1.000

decimal32

351,505

111.660

decimal64

359,425

114.176

decimal128

1,446,674

459.553

decimal32_fast

146,873

46.656

decimal64_fast

139,294

44.248

decimal128_fast

707,308

224.685

Subtraction

Type

Runtime (us)

Ratio to double

float

3339

2.014

double

1658

1.000

decimal32

267,646

161.427

decimal64

303,589

183.106

decimal128

954,211

575.519

decimal32_fast

147,112

88.729

decimal64_fast

145,606

87.820

decimal128_fast

394,538

2387.960

Multiplication

Type

Runtime (us)

Ratio to double

float

1646

0.957

double

1720

1.000

decimal32

313,219

182.104

decimal64

583,818

339.429

decimal128

1,881,936

1094.149

decimal32_fast

86,093

50.054

decimal64_fast

333,582

193.943

decimal128_fast

1,269,429

738.040

Division

Type

Runtime (us)

Ratio to double

float

2120

0.547

double

3874

1.000

decimal32

307,337

79.333

decimal64

447,910

115.620

decimal128

2,544,798

656.892

decimal32_fast

105,796

27.309

decimal64_fast

291,671

75.289

decimal128_fast

302,003

77.956

Selected Special Functions

The benchmark for these operations generates a random vector containing 2,000,000 elements and does operations +, -, *, / between vec[i] and vec[i + 1]. This is repeated 5 times to generate stable results.

M1 macOS Results

Run using a Macbook pro with M1 pro chipset running macOS Sonoma 14.4.1 and homebrew Clang 18.1.4

SQRT

Type

Runtime (us)

Ratio to double

float

2021

0.626

double

3229

1.000

decimal32

4,826,066

1494.601

decimal64

7,780,637

2409.612

decimal128

100,269,145

31052.693

<charconv>

For all the following the results compare against boost.charconv for 10'000'000 conversions.

from_chars general

M1 macOS Results

Run using a Macbook pro with M1 pro chipset running macOS Sonoma 14.4.1 and homebrew Clang 18.1.4

Type

Runtime (us)

Ratio to double

float

235,816

0.953

double

247,307

1.000

decimal32

366,682

1.483

decimal64

485,965

1.965

Note
decimal128 is currently absent due to results showing it is 2 orders of magnitude faster than the others. This should not be the case so will be investigated.

from_chars scientific

M1 macOS Results

Run using a Macbook pro with M1 pro chipset running macOS Sonoma 14.4.1 and homebrew Clang 18.1.4

Type

Runtime (us)

Ratio to double

float

241,893

0.975

double

247,975

1.000

decimal32

358,189

1.444

decimal64

477,574

1.926

Note
decimal128 is currently absent due to results showing it is 2 orders of magnitude faster than the others. This should not be the case so will be investigated.

to_chars general shortest representation

M1 macOS Results

Run using a Macbook pro with M1 pro chipset running macOS Sonoma 14.4.1 and homebrew Clang 18.1.4

Type

Runtime (us)

Ratio to double

float

316,300

1.040

double

304,272

1.000

decimal32

406,053

1.335

decimal64

678,451

2.230

decimal128

6,309,346

20.736

to_chars general 6-digits of precision

M1 macOS Results

Run using a Macbook pro with M1 pro chipset running macOS Sonoma 14.4.1 and homebrew Clang 18.1.4

Type

Runtime (us)

Ratio to double

float

323,867

0.967

double

334,989

1.000

decimal32

409,608

1.223

decimal64

702,339

2.097

decimal128

6,305,521

18.823

to_chars scientific shortest representation

M1 macOS Results

Run using a Macbook pro with M1 pro chipset running macOS Sonoma 14.4.1 and homebrew Clang 18.1.4

Type

Runtime (us)

Ratio to double

float

286,330

1.011

double

283,287

1.000

decimal32

290,117

1.024

decimal64

499,637

1.764

decimal128

3,096,910

10.932

to_chars scientific 6-digits of precision

M1 macOS Results

Run using a Macbook pro with M1 pro chipset running macOS Sonoma 14.4.1 and homebrew Clang 18.1.4

Type

Runtime (us)

Ratio to double

float

258,710

0.809

double

319,676

1.000

decimal32

292,250

0.914

decimal64

516,399

1.615

decimal128

3,108,380

9.724

Design Decisions

C++14 Requirement

Constexpr Support

Using C++14’s relaxed definition of constexpr nearly all functionality of the library can be constexpr. This allows Boost.Decimal to better emulate the functionality a built-in type would have.

Boost.Math Integration

Boost.Math requires C++14 as the minimum language standard for the library. By meeting this requirement, and Boost.Math’s conceptual requirements for a user-defined type we are able to provide the statistics, interpolation, quadrature, etc. out of the box.

No Binary Floating Point Operations

The library deliberately does not contain any operations between a binary floating point type and the decimal floating point types. The rationale is similar to that of the library in the first place in that you may end up with surprising results. Conversion operators and constructors are available for all decimal types should the user want to explicit cast one side of the operation to conduct the operation.

References

The following books, papers and blog posts serve as the basis for the algorithms used in the library:

  • Michael F. Cowlishaw Decimal Floating-Point: Algorism(sic) for Computers, Proceedings of the 16th IEEE Symposium on Computer Arithmetic, 2003

  • Donald E. Knuth, The Art of Computer Programming Volume 2 Seminumerical Algorithms, 3rd edition, 1998

  • Jean-Michel Muller, Elementary Functions, 3rd edition, 2010

  • Jean-Michel Muller, et. al., Handbook of Floating-Point Arithmetic, 2000

  • John F. Hart, et. al., Computer Approximations, 1968

  • IEEE, IEEE Standard for Floating-Point Arithmetic, 2019

This documentation is copyright 2023 Matt Borland and is distributed under the Boost Software License, Version 1.0.