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
Types
IEEE 754 Compliant Types
Non-IEEE 754 Compliant Types
Structures
Enums
Constants
Macros
See: Configuration Macros
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.
Nearest integer floating point operations
Floating point manipulation functions
Classification and comparison
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:
-
Downward
-
To nearest
-
To nearest from zero
-
Toward zero
-
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 ofdecimal64
internally, and returned asdecimal64
-
2: all
decimal32
anddecimal64
operations are evaluated with the precision and range ofdecimal128
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 toconstexpr
when compiling with C++20 or greater, otherwise it expands to nothing. -
BOOST_DECIMAL_CONSTEXPR
: This is defined toconstexpr
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 |
|
34,814 |
0.604 |
|
57,644 |
1.000 |
|
2,163,595 |
37.534 |
|
2,633,923 |
45.693 |
|
6,064,630 |
105.208 |
|
613,626 |
10.645 |
|
693,390 |
12.029 |
|
628,596 |
10.905 |
GCC |
893,375 |
15.498 |
GCC |
496,127 |
8.607 |
GCC |
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 |
|
182,707 |
0.943 |
|
193,737 |
1.000 |
|
3,097,942 |
15.990 |
|
4,697,948 |
24.249 |
|
17,267,609 |
89.129 |
|
809,847 |
4.180 |
|
1,043,657 |
5.387 |
|
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 |
|
131,803 |
2.060 |
|
63,981 |
1.000 |
|
2,052,770 |
32.084 |
|
2,701,290 |
42.220 |
|
5,545,490 |
86.674 |
|
728,146 |
11.381 |
|
611,866 |
9.563 |
|
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 |
|
55,811 |
1.062 |
|
52,531 |
1.000 |
|
2,653,456 |
50.512 |
|
3,254,833 |
61.960 |
|
10,479,050 |
199.483 |
|
1,371,022 |
26.100 |
|
1,370,192 |
26.083 |
|
7,197,718 |
137.018 |
GCC |
2,997,658 |
57.065 |
GCC |
2,129,898 |
40.546 |
GCC |
3,056,979 |
58.194 |
Subtraction
Type |
Runtime (us) |
Ratio to |
|
53,362 |
1.083 |
|
49,242 |
1.000 |
|
2,054,535 |
41.723 |
|
2,507,709 |
50.926 |
|
5,554,139 |
112.793 |
|
1,050,225 |
21.328 |
|
1,048,560 |
21.294 |
|
2,073,580 |
42.110 |
GCC |
2,006,964 |
40.757 |
GCC |
1,324,796 |
26.904 |
GCC |
2,783,553 |
56.528 |
Multiplication
Type |
Runtime (us) |
Ratio to |
|
53,469 |
1.093 |
|
48,903 |
1.000 |
|
1,993,989 |
40.774 |
|
2,766,602 |
56.573 |
|
4,796,346 |
98.079 |
|
1,117,727 |
22.856 |
|
1,369,834 |
28.011 |
|
8,139,518 |
166.442 |
GCC |
2,507,998 |
51.285 |
GCC |
2,414,864 |
49.381 |
GCC |
6,248,956 |
127.783 |
Division
Type |
Runtime (us) |
Ratio to |
|
59,003 |
0.756 |
|
78,078 |
1.000 |
|
2,250,186 |
28.820 |
|
2,816,014 |
36.067 |
|
18,320,634 |
234.645 |
|
1,123,428 |
14.389 |
|
1,258,004 |
16.112 |
|
1,243,024 |
15.920 |
GCC |
5,002,197 |
64.067 |
GCC |
2,961,731 |
37.933 |
GCC |
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 |
|
67,019 |
0.974 |
|
68,820 |
1.000 |
|
2,994,405 |
43.511 |
|
4,531,755 |
65.849 |
|
25,209,554 |
366.311 |
|
2,066,728 |
30.031 |
|
3,667,169 |
53.286 |
|
11,213,280 |
162.936 |
Subtraction
Type |
Runtime (us) |
Ratio to |
|
60,912 |
0.976 |
|
62,409 |
1.000 |
|
3,132,613 |
50.194 |
|
3,864,498 |
61.992 |
|
17,210,173 |
275.764 |
|
2,028,429 |
32.502 |
|
3,017,419 |
48.349 |
|
5,557,846 |
89.055 |
Multiplication
Type |
Runtime (us) |
Ratio to |
|
60,742 |
0.969 |
|
62,658 |
1.000 |
|
2,029,689 |
32.393 |
|
8,805,524 |
140.533 |
|
15,519,053 |
247.689 |
|
1,573,280 |
25.109 |
|
7,650,156 |
122.094 |
|
16,874,890 |
269.317 |
Division
Type |
Runtime (us) |
Ratio to |
|
75,437 |
0.936 |
|
80,559 |
1.000 |
|
2,832,016 |
45.198 |
|
11,640,789 |
185.783 |
|
32,470,044 |
518.211 |
|
1,660,332 |
26.498 |
|
11,266,972 |
179.817 |
|
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 |
|
43,056 |
1.295 |
|
33,238 |
1.000 |
|
3,146,032 |
94.652 |
|
2,963,788 |
89.169 |
|
10,125,221 |
304.628 |
|
1,685,360 |
50.706 |
|
1,886,022 |
56.743 |
|
6,893,049 |
207.385 |
Subtraction
Type |
Runtime (us) |
Ratio to |
|
43,013 |
1.295 |
|
33,204 |
1.000 |
|
2,385,896 |
71.586 |
|
2,759,536 |
83.108 |
|
5,560,295 |
167.459 |
|
1,228,630 |
37.002 |
|
1,312,815 |
39.538 |
|
2,869,005 |
86.405 |
Multiplication
Type |
Runtime (us) |
Ratio to |
|
42,634 |
1.293 |
|
32,970 |
1.000 |
|
2,826,351 |
85.725 |
|
3,268,243 |
99.128 |
|
4,654,643 |
141.178 |
|
1,614,365 |
48.965 |
|
2,417,646 |
73.329 |
|
8,017,934 |
243.189 |
Division
Type |
Runtime (us) |
Ratio to |
|
46,030 |
1.351 |
|
34,078 |
1.000 |
|
2,649,922 |
77.760 |
|
3,721,028 |
109.192 |
|
19,559,739 |
573.970 |
|
1,436,099 |
42.142 |
|
2,593,573 |
76.107 |
|
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 |
|
10,308,818 |
0.551 |
|
18,692,513 |
1.000 |
|
3,301,003 |
0.177 |
|
4,580,001 |
0.245 |
|
3,321,788 |
0.178 |
|
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 |
|
8,577,201 |
0.410 |
|
20,903,459 |
1.000 |
|
4,602,771 |
0.220 |
|
5,332,730 |
0.255 |
|
3,932,622 |
0.188 |
|
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 |
|
2,556,533 |
0.965 |
|
2,648,485 |
1.000 |
|
3,201,545 |
1.209 |
|
4,775,487 |
1.803 |
|
3,196,724 |
1.207 |
|
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 |
|
10,363,219 |
0.554 |
|
18,677,179 |
1.000 |
|
3,296,877 |
0.177 |
|
4,500,127 |
0.241 |
|
3,381,651 |
0.181 |
|
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 |
|
8,170,079 |
0.439 |
|
18,626,905 |
1.000 |
|
3,927,882 |
0.211 |
|
5,668,246 |
0.304 |
|
3,904,457 |
0.210 |
|
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 |
|
2,651,707 |
0.986 |
|
2,690,166 |
1.000 |
|
3,153,821 |
1.172 |
|
4,726,009 |
1.926 |
|
4,726,009 |
1.757 |
|
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 |
|
2,839,146 |
0.841 |
|
3,374,946 |
1.000 |
|
4,253,304 |
1.260 |
|
6,885,679 |
2.040 |
|
4,453,957 |
1.320 |
|
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 |
|
3,108,053 |
0.823 |
|
3,774,811 |
1.000 |
|
6,127,529 |
1.623 |
|
8,582,256 |
2.273 |
|
7,639,470 |
2.024 |
|
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 |
|
2,917,920 |
0.849 |
|
3,435,671 |
1.000 |
|
4,636,747 |
1.350 |
|
5,680,800 |
1.653 |
|
4,675,951 |
1.361 |
|
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 |
|
5,226,353 |
0.957 |
|
5,458,987 |
1.000 |
|
3,782,692 |
0.693 |
|
5,368,162 |
0.983 |
|
3,611,498 |
0.662 |
|
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 |
|
5,873,775 |
0.929 |
|
6,322,448 |
1.000 |
|
5,493,981 |
0.869 |
|
7,849,419 |
1.215 |
|
6,516,633 |
1.031 |
|
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 |
|
6,320,719 |
0.962 |
|
6,572,846 |
1.000 |
|
4,133,466 |
0.629 |
|
6,106,989 |
0.929 |
|
3,458,534 |
0.526 |
|
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 |
|
2,835,528 |
0.849 |
|
3,338,216 |
1.000 |
|
2,887,451 |
0.865 |
|
5,218,195 |
1.563 |
|
3,033,115 |
0.909 |
|
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 |
|
3,047,827 |
0.814 |
|
3,742,344 |
1.000 |
|
4,103,661 |
1.097 |
|
6,721,570 |
1.796 |
|
4,542,470 |
1.214 |
|
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 |
|
2,814,527 |
0.817 |
|
3,442,930 |
1.000 |
|
3,048,663 |
0.885 |
|
3,786,216 |
1.010 |
|
2,813,360 |
0.817 |
|
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 |
|
4,686,460 |
0.938 |
|
4,993,886 |
1.000 |
|
2,919,727 |
0.585 |
|
4,157,802 |
0.833 |
|
3,052,228 |
0.611 |
|
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 |
|
4,734,517 |
0.970 |
|
4,880,384 |
1.000 |
|
3,879,496 |
0.795 |
|
5,614,452 |
1.150 |
|
4,445,619 |
0.911 |
|
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 |
|
5,636,010 |
0.952 |
|
5,922,301 |
1.000 |
|
3,048,058 |
0.515 |
|
5,140,604 |
0.868 |
|
2,821,707 |
0.476 |
|
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
Copyright and License
This documentation is copyright 2023 Matt Borland and is distributed under the Boost Software License, Version 1.0.