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 natively on Ubuntu (x86_64, s390x, and aarch64), macOS (x86_64, and Apple Silicon), and Windows (x32 and x64); as well as emulated PPC64LE and STM32 using QEMU with the following compilers:

  • GCC 7 and later

  • Clang 6 and later

  • Visual Studio 2017 and later

  • Intel OneAPI DPC++

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

API Reference

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

Bit Conversions

IEEE 754 specifies two different encodings for decimal floating point types: Binary Integer Significand Field (BID), and Densely Packed Decimal Significand Field (DPD). Internally this library is implemented in the BID format for the IEEE-754 compliant types. Should the user want to capture the bit format in BID or convert to DPD we offer a family of conversion functions: to_bid, from_bid, to_dpd, and from_dpd that allow conversion to or from the bit strings regardless of encoding.

namespace boost {
namespace decimal {

namespace detail {

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

} // namespace detail

// ----- BID Conversions -----

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

constexpr decimal32 from_bid_d32(std::uint32_t bits) noexcept;

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

constexpr decimal32_fast from_bid_d32f(std::uint32_t bits) noexcept;

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

constexpr decimal64 from_bid_d64(std::uint64_t bits) noexcept;

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

constexpr decimal64_fast from_bid_d64f(std::uint64_t bits) noexcept;

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

constexpr decimal128 from_bid_d128(detail::uint128 bits) noexcept;

// Automatic detection if your platform has built-in unsigned __int128 or not to enable/disable the overload
#ifdef BOOST_DECIMAL_HAS_INT128

constexpr decimal128 from_bid_d128(unsigned __int128 bits) noexcept;

#endif // BOOST_DECIMAL_HAS_INT128

constexpr detail::uint128 to_bid_d128f(decimal128_fast val) noexcept;

constexpr decimal128 from_bid_d128f(detail::uint128 bits) noexcept;

#ifdef BOOST_DECIMAL_HAS_INT128

constexpr decimal128 from_bid_d128f(unsigned __int128 bits) noexcept;

#endif // BOOST_DECIMAL_HAS_INT128

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

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

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

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

// ----- DPD Conversions -----

constexpr std::uint32_t to_dpd_d32(decimal32 val) noexcept;

constexpr std::uint32_t to_dpd_d32f(decimal32_fast val) noexcept;

constexpr std::uint64_t to_dpd_d64(decimal64 val) noexcept;

constexpr std::uint64_t to_dpd_d64f(decimal64_fast val) noexcept;

constexpr detail::uint128 to_dpd_d128(decimal128 val) noexcept;

constexpr detail::uint128 to_dpd_d128f(decimal128_fast val) noexcept;

template <typename T>
constexpr auto to_dpd(T val) noexcept;

template <typename T = decimal32_fast>
constexpr T from_dpd(std::uint32_t bits) noexcept;

template <typename T = decimal64_fast>
constexpr T from_dpd(std::uint64_t bits) noexcept;

template <typename T = decimal128_fast>
constexpr T from_dpd(detail::uint128 bits) noexcept;

#ifdef BOOST_DECIMAL_HAS_INT128
template <typename T = decimal128_fast>
constexpr T from_dpd(unsigned __int128 bits) noexcept;
#endif

} // 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.

chars_format

namespace boost {
namespace decimal {

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

} //namespace decimal
} //namespace boost

from_chars_result

namespace boost {
namespace decimal {

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

} //namespace decimal
} //namespace boost

to_chars_result

namespace boost {
namespace decimal {

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

} //namespace decimal
} //namespace boost

from_chars

namespace boost {
namespace decimal {

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

#ifdef BOOST_DECIMAL_HAS_STD_CHARCONV

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

#endif // BOOST_DECIMAL_HAS_STD_CHARCONV

} //namespace decimal
} //namespace boost
Important
If std::chars_format is used the function will return a std::from_chars_result and if boost::decimal::chars_format is used OR no format is specified then a boost::decimal::from_chars_result will be returned.

to_chars

namespace boost {
namespace decimal {

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;

#ifdef BOOST_DECIMAL_HAS_STD_CHARCONV

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

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

#endif // BOOST_DECIMAL_HAS_STD_CHARCONV

} //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()

Important
Same as from_chars, boost::decimal::to_chars will return a std::to_chars_result if std::chars_format is used to specify the format; otherwise it returns a boost::decimal::to_chars_result.

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

limits

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

    static constexpr bool is_specialized = true;
    static constexpr bool is_signed = true;
    static constexpr bool is_integer = false;
    static constexpr bool is_exact = false;
    static constexpr bool has_infinity = true;
    static constexpr bool has_quiet_NaN = true;
    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)))
    static constexpr std::float_denorm_style has_denorm = std::denorm_present;
    static constexpr bool has_denorm_loss = true;
    #endif

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

    // Member functions
    static constexpr boost::decimal::decimal32 min()
    static constexpr boost::decimal::decimal32 max();
    static constexpr boost::decimal::decimal32 lowest();
    static constexpr boost::decimal::decimal32 epsilon();
    static constexpr boost::decimal::decimal32 round_error();
    static constexpr boost::decimal::decimal32 infinity();
    static constexpr boost::decimal::decimal32 quiet_NaN();
    static constexpr boost::decimal::decimal32 signaling_NaN();
    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

    static constexpr bool is_specialized = true;
    static constexpr bool is_signed = true;
    static constexpr bool is_integer = false;
    static constexpr bool is_exact = false;
    static constexpr bool has_infinity = true;
    static constexpr bool has_quiet_NaN = true;
    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)))
    static constexpr std::float_denorm_style has_denorm = std::denorm_present;
    static constexpr bool has_denorm_loss = true;
    #endif

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

    // Member functions
    static constexpr boost::decimal::decimal64 min()
    static constexpr boost::decimal::decimal64 max();
    static constexpr boost::decimal::decimal64 lowest();
    static constexpr boost::decimal::decimal64 epsilon();
    static constexpr boost::decimal::decimal64 round_error();
    static constexpr boost::decimal::decimal64 infinity();
    static constexpr boost::decimal::decimal64 quiet_NaN();
    static constexpr boost::decimal::decimal64 signaling_NaN();
    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

    static constexpr bool is_specialized = true;
    static constexpr bool is_signed = true;
    static constexpr bool is_integer = false;
    static constexpr bool is_exact = false;
    static constexpr bool has_infinity = true;
    static constexpr bool has_quiet_NaN = true;
    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)))
    static constexpr std::float_denorm_style has_denorm = std::denorm_present;
    static constexpr bool has_denorm_loss = true;
    #endif

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

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

} // Namespace std

Configuration Macros

User Configurable 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%.

  • BOOST_DECIMAL_DEC_EVAL_METHOD: See <cfloat> section for explanation

Automatic Configuration Macros

  • BOOST_DECIMAL_CXX20_CONSTEXPR: This is defined to constexpr when compiling with C++20 or greater, otherwise it expands to nothing.

  • BOOST_DECIMAL_CONSTEXPR: This is defined to constexpr when any of the following are met:

    • _MSC_FULL_VER >= 192528326

    • GNUC >= 9

    • Compiler has: __builtin_is_constant_evaluated()

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

  • BOOST_DECIMAL_HAS_STD_CHARCONV: This macro is defined if header <charconv> exists and the language standard used is >= C++17

    • We only need the structs and enums out of the header so we are not concerned with being overly restrictive about the feature test macros.

      • Known compilers that support this lighter requirement are: GCC >= 10, Clang >= 13, and MSVC >= 14.2

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

Output:

 Initial Value: 0.25
Returned Value: 0.25

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

Bit Conversions

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

using namespace boost::decimal;

int main()
{
    const decimal32_fast fast_type {5};
    const std::uint32_t BID_bits {to_bid(fast_type)};
    const std::uint32_t DPD_bits {to_dpd(fast_type)};

    std::cout << std::hex
              << "BID format: " << BID_bits << '\n'
              << "DPD format: " << DPD_bits << std::endl;

    const decimal32 bid_decimal {from_bid<decimal32>(BID_bits)};
    const decimal32 dpd_decimal {from_dpd<decimal32>(DPD_bits)};

    return !(bid_decimal == dpd_decimal);
}

Output:

BID format: 31fc4b40
DPD format: 35f00000

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 20,000,000 elements and does operations >, >=, <, <=, ==, and != between vec[i] and vec[i + 1]. This is repeated 5 times to generate stable results.

x64 Linux Results

Run using an Intel i9-11900k chipset running RHEL 9.4 and GCC 11.4.1-3

Type

Runtime (us)

Ratio to double

float

34,814

0.604

double

57,644

1.000

decimal32

2,163,595

37.534

decimal64

2,633,923

45.693

decimal128

6,064,630

105.208

decimal32_fast

613,626

10.645

decimal64_fast

693,390

12.029

decimal128_fast

628,596

10.905

GCC _Decimal32

893,375

15.498

GCC _Decimal64

496,127

8.607

GCC _Decimal128

1,143,636

19.840

x64 Windows Results

Run using an Intel i9-11900k chipset running Windows 11 and Visual Studio 17.11.4

Type

Runtime (us)

Ratio to double

float

182,707

0.943

double

193,737

1.000

decimal32

3,097,942

15.990

decimal64

4,697,948

24.249

decimal128

17,267,609

89.129

decimal32_fast

809,847

4.180

decimal64_fast

1,043,657

5.387

decimal128_fast

888,053

4.584

M1 macOS Results

Run using a Macbook pro with M1 pro chipset running macOS Sonoma 15.0 and homebrew Clang 18.1.8

Type

Runtime (us)

Ratio to double

float

131,803

2.060

double

63,981

1.000

decimal32

2,052,770

32.084

decimal64

2,701,290

42.220

decimal128

5,545,490

86.674

decimal32_fast

728,146

11.381

decimal64_fast

611,866

9.563

decimal128_fast

714,586

11.169

Basic Operations

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

As discussed in the design of the fast types the significand is stored in normalized form so that we do not have to worry about the effects of cohorts. Unfortunately this means that decimal128_fast multiplication is always carried out internally at 256-bit size whereas decimal128 contains heuristics in operator* to avoid 256-bit multiplication when it is not needed (i.e. the resultant significand is less than or equal to 128-bits). This causes multiplication of decimal128_fast to be ~1.72x slower than decimal128, but all other operators leave us with a geometric average runtime under 1.00 for decimal128_fast / decimal128 so we accept this tradeoff.

x64 Linux Results

Run using an Intel i9-11900k chipset running RHEL 9.4 and GCC 11.4.1-3

Addition

Type

Runtime (us)

Ratio to double

float

55,811

1.062

double

52,531

1.000

decimal32

2,653,456

50.512

decimal64

3,254,833

61.960

decimal128

10,479,050

199.483

decimal32_fast

1,371,022

26.100

decimal64_fast

1,370,192

26.083

decimal128_fast

7,197,718

137.018

GCC _Decimal32

2,997,658

57.065

GCC _Decimal64

2,129,898

40.546

GCC _Decimal128

3,056,979

58.194

Subtraction

Type

Runtime (us)

Ratio to double

float

53,362

1.083

double

49,242

1.000

decimal32

2,054,535

41.723

decimal64

2,507,709

50.926

decimal128

5,554,139

112.793

decimal32_fast

1,050,225

21.328

decimal64_fast

1,048,560

21.294

decimal128_fast

2,073,580

42.110

GCC _Decimal32

2,006,964

40.757

GCC _Decimal64

1,324,796

26.904

GCC _Decimal128

2,783,553

56.528

Multiplication

Type

Runtime (us)

Ratio to double

float

53,469

1.093

double

48,903

1.000

decimal32

1,993,989

40.774

decimal64

2,766,602

56.573

decimal128

4,796,346

98.079

decimal32_fast

1,117,727

22.856

decimal64_fast

1,369,834

28.011

decimal128_fast

8,139,518

166.442

GCC _Decimal32

2,507,998

51.285

GCC _Decimal64

2,414,864

49.381

GCC _Decimal128

6,248,956

127.783

Division

Type

Runtime (us)

Ratio to double

float

59,003

0.756

double

78,078

1.000

decimal32

2,250,186

28.820

decimal64

2,816,014

36.067

decimal128

18,320,634

234.645

decimal32_fast

1,123,428

14.389

decimal64_fast

1,258,004

16.112

decimal128_fast

1,243,024

15.920

GCC _Decimal32

5,002,197

64.067

GCC _Decimal64

2,961,731

37.933

GCC _Decimal128

10,095,995

129.307

x64 Windows Results

Run using an Intel i9-11900k chipset running Windows 11 and Visual Studio 17.11.4

Addition

Type

Runtime (us)

Ratio to double

float

67,019

0.974

double

68,820

1.000

decimal32

2,994,405

43.511

decimal64

4,531,755

65.849

decimal128

25,209,554

366.311

decimal32_fast

2,066,728

30.031

decimal64_fast

3,667,169

53.286

decimal128_fast

11,213,280

162.936

Subtraction

Type

Runtime (us)

Ratio to double

float

60,912

0.976

double

62,409

1.000

decimal32

3,132,613

50.194

decimal64

3,864,498

61.992

decimal128

17,210,173

275.764

decimal32_fast

2,028,429

32.502

decimal64_fast

3,017,419

48.349

decimal128_fast

5,557,846

89.055

Multiplication

Type

Runtime (us)

Ratio to double

float

60,742

0.969

double

62,658

1.000

decimal32

2,029,689

32.393

decimal64

8,805,524

140.533

decimal128

15,519,053

247.689

decimal32_fast

1,573,280

25.109

decimal64_fast

7,650,156

122.094

decimal128_fast

16,874,890

269.317

Division

Type

Runtime (us)

Ratio to double

float

75,437

0.936

double

80,559

1.000

decimal32

2,832,016

45.198

decimal64

11,640,789

185.783

decimal128

32,470,044

518.211

decimal32_fast

1,660,332

26.498

decimal64_fast

11,266,972

179.817

decimal128_fast

11,201,820

178.777

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

43,056

1.295

double

33,238

1.000

decimal32

3,146,032

94.652

decimal64

2,963,788

89.169

decimal128

10,125,221

304.628

decimal32_fast

1,685,360

50.706

decimal64_fast

1,886,022

56.743

decimal128_fast

6,893,049

207.385

Subtraction

Type

Runtime (us)

Ratio to double

float

43,013

1.295

double

33,204

1.000

decimal32

2,385,896

71.586

decimal64

2,759,536

83.108

decimal128

5,560,295

167.459

decimal32_fast

1,228,630

37.002

decimal64_fast

1,312,815

39.538

decimal128_fast

2,869,005

86.405

Multiplication

Type

Runtime (us)

Ratio to double

float

42,634

1.293

double

32,970

1.000

decimal32

2,826,351

85.725

decimal64

3,268,243

99.128

decimal128

4,654,643

141.178

decimal32_fast

1,614,365

48.965

decimal64_fast

2,417,646

73.329

decimal128_fast

8,017,934

243.189

Division

Type

Runtime (us)

Ratio to double

float

46,030

1.351

double

34,078

1.000

decimal32

2,649,922

77.760

decimal64

3,721,028

109.192

decimal128

19,559,739

573.970

decimal32_fast

1,436,099

42.142

decimal64_fast

2,593,573

76.107

decimal128_fast

2,594,426

76.132

<charconv>

Parsing and serializing number exactly is one of the key features of decimal floating point types, so we must compare the performance of <charconv>. For all the following the results compare against STL provided <charconv> for 20,000,000 conversions. Since <charconv> is fully implemented in software for each type the performance gap between built-in float and double vs decimal32 and decimal64 is significantly smaller (or the decimal performance is better) than the hardware vs software performance gap seen above for basic operations.

To run these benchmarks yourself you will need a compiler with complete implementation of <charconv> and to run the benchmarks under C++17 or higher. At the time of writing this is limited to:

  • GCC 11 or newer

  • MSVC 19.24 or newer

These benchmarks are automatically disabled if your compiler does not provide feature complete <charconv> or if the language standard is set to C++14.

from_chars

from_chars general
x64 Linux Results

Run using an Intel i9-11900k chipset running RHEL 9.4 and GCC 11.4.1-3

Type

Runtime (us)

Ratio to double

float

10,308,818

0.551

double

18,692,513

1.000

decimal32

3,301,003

0.177

decimal64

4,580,001

0.245

decimal32_fast

3,321,788

0.178

decimal64_fast

4,591,311

0.246

x64 Windows Results

Run using an Intel i9-11900k chipset running Windows 11 and Visual Studio 17.11.4

Type

Runtime (us)

Ratio to double

float

8,577,201

0.410

double

20,903,459

1.000

decimal32

4,602,771

0.220

decimal64

5,332,730

0.255

decimal32_fast

3,932,622

0.188

decimal64_fast

5,614,476

0.269

M1 macOS Results

Run using a Macbook pro with M1 pro chipset running macOS Sonoma 15.0 and homebrew GCC 14.2.0

Type

Runtime (us)

Ratio to double

float

2,556,533

0.965

double

2,648,485

1.000

decimal32

3,201,545

1.209

decimal64

4,775,487

1.803

decimal32_fast

3,196,724

1.207

decimal64_fast

4,762,636

1.798

from_chars scientific
x64 Linux Results

Run using an Intel i9-11900k chipset running RHEL 9.4 and GCC 11.4.1-3

Type

Runtime (us)

Ratio to double

float

10,363,219

0.554

double

18,677,179

1.000

decimal32

3,296,877

0.177

decimal64

4,500,127

0.241

decimal32_fast

3,381,651

0.181

decimal64_fast

4,496,194

0.241

x64 Windows Results

Run using an Intel i9-11900k chipset running Windows 11 and Visual Studio 17.11.4

Type

Runtime (us)

Ratio to double

float

8,170,079

0.439

double

18,626,905

1.000

decimal32

3,927,882

0.211

decimal64

5,668,246

0.304

decimal32_fast

3,904,457

0.210

decimal64_fast

5,302,174

0.285

M1 macOS Results

Run using a Macbook pro with M1 pro chipset running macOS Sonoma 15.0 and homebrew GCC 14.2.0

Type

Runtime (us)

Ratio to double

float

2,651,707

0.986

double

2,690,166

1.000

decimal32

3,153,821

1.172

decimal64

4,726,009

1.926

decimal32_fast

4,726,009

1.757

decimal64_fast

4,693,387

1.747

to_chars

to_chars general shortest representation
x64 Linux Results

Run using an Intel i9-11900k chipset running RHEL 9.4 and GCC 11.4.1-3

Type

Runtime (us)

Ratio to double

float

2,839,146

0.841

double

3,374,946

1.000

decimal32

4,253,304

1.260

decimal64

6,885,679

2.040

decimal32_fast

4,453,957

1.320

decimal64_fast

7,827,910

2.319

x64 Windows Results

Run using an Intel i9-11900k chipset running Windows 11 and Visual Studio 17.11.4

Type

Runtime (us)

Ratio to double

float

3,108,053

0.823

double

3,774,811

1.000

decimal32

6,127,529

1.623

decimal64

8,582,256

2.273

decimal32_fast

7,639,470

2.024

decimal64_fast

11,564,222

3.064

M1 macOS Results

Run using a Macbook pro with M1 pro chipset running macOS Sonoma 15.0 and homebrew GCC 14.2.0

Type

Runtime (us)

Ratio to double

float

2,917,920

0.849

double

3,435,671

1.000

decimal32

4,636,747

1.350

decimal64

5,680,800

1.653

decimal32_fast

4,675,951

1.361

decimal64_fast

5,900,272

1.717

to_chars general 6-digits of precision
x64 Linux Results

Run using an Intel i9-11900k chipset running RHEL 9.4 and GCC 11.4.1-3

Type

Runtime (us)

Ratio to double

float

5,226,353

0.957

double

5,458,987

1.000

decimal32

3,782,692

0.693

decimal64

5,368,162

0.983

decimal32_fast

3,611,498

0.662

decimal64_fast

6,025,340

1.104

x64 Windows Results

Run using an Intel i9-11900k chipset running Windows 11 and Visual Studio 17.11.4

Type

Runtime (us)

Ratio to double

float

5,873,775

0.929

double

6,322,448

1.000

decimal32

5,493,981

0.869

decimal64

7,849,419

1.215

decimal32_fast

6,516,633

1.031

decimal64_fast

8,065,516

1.276

M1 macOS Results

Run using a Macbook pro with M1 pro chipset running macOS Sonoma 15.0 and homebrew GCC 14.2.0

Type

Runtime (us)

Ratio to double

float

6,320,719

0.962

double

6,572,846

1.000

decimal32

4,133,466

0.629

decimal64

6,106,989

0.929

decimal32_fast

3,458,534

0.526

decimal64_fast

5,997,442

0.912

to_chars scientific shortest representation
x64 Linux Results

Run using an Intel i9-11900k chipset running RHEL 9.4 and GCC 11.4.1-3

Type

Runtime (us)

Ratio to double

float

2,835,528

0.849

double

3,338,216

1.000

decimal32

2,887,451

0.865

decimal64

5,218,195

1.563

decimal32_fast

3,033,115

0.909

decimal64_fast

6,103,323

1.828

x64 Windows Results

Run using an Intel i9-11900k chipset running Windows 11 and Visual Studio 17.11.4

Type

Runtime (us)

Ratio to double

float

3,047,827

0.814

double

3,742,344

1.000

decimal32

4,103,661

1.097

decimal64

6,721,570

1.796

decimal32_fast

4,542,470

1.214

decimal64_fast

8,694,813

2.323

M1 macOS Results

Run using a Macbook pro with M1 pro chipset running macOS Sonoma 15.0 and homebrew GCC 14.2.0

Type

Runtime (us)

Ratio to double

float

2,814,527

0.817

double

3,442,930

1.000

decimal32

3,048,663

0.885

decimal64

3,786,216

1.010

decimal32_fast

2,813,360

0.817

decimal64_fast

4,082,146

1.186

to_chars scientific 6-digits of precision
x64 Linux Results

Run using an Intel i9-11900k chipset running RHEL 9.4 and GCC 11.4.1-3

Type

Runtime (us)

Ratio to double

float

4,686,460

0.938

double

4,993,886

1.000

decimal32

2,919,727

0.585

decimal64

4,157,802

0.833

decimal32_fast

3,052,228

0.611

decimal64_fast

5,597,538

1.121

x64 Windows Results

Run using an Intel i9-11900k chipset running Windows 11 and Visual Studio 17.11.4

Type

Runtime (us)

Ratio to double

float

4,734,517

0.970

double

4,880,384

1.000

decimal32

3,879,496

0.795

decimal64

5,614,452

1.150

decimal32_fast

4,445,619

0.911

decimal64_fast

7,375,520

1.511

M1 macOS Results

Run using a Macbook pro with M1 pro chipset running macOS Sonoma 15.0 and homebrew GCC 14.2.0

Type

Runtime (us)

Ratio to double

float

5,636,010

0.952

double

5,922,301

1.000

decimal32

3,048,058

0.515

decimal64

5,140,604

0.868

decimal32_fast

2,821,707

0.476

decimal64_fast

5,525,549

0.933

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.