diff --git a/doc/modules/ROOT/nav.adoc b/doc/modules/ROOT/nav.adoc index 59a8ffa36..7018ed80a 100644 --- a/doc/modules/ROOT/nav.adoc +++ b/doc/modules/ROOT/nav.adoc @@ -3,6 +3,8 @@ * xref:examples.adoc[] ** xref:examples.adoc#examples_construction[Basic Construction] ** xref:examples.adoc#examples_basic_math[Basic Arithmetic] +** xref:examples.adoc#examples_conversions[Conversions] +*** xref:examples.adoc#examples_binary_floating_conversions[Binary Floating Point] ** xref:examples.adoc#examples_promotion[Promotion and Mixed Decimal Arithmetic] ** xref:examples.adoc#examples_charconv[``] ** xref:examples.adoc#examples_generic_programming[Generic Programming] diff --git a/doc/modules/ROOT/pages/examples.adoc b/doc/modules/ROOT/pages/examples.adoc index 7357301c9..6376be694 100644 --- a/doc/modules/ROOT/pages/examples.adoc +++ b/doc/modules/ROOT/pages/examples.adoc @@ -69,6 +69,30 @@ Decimal Result: 100 .... ==== +[#examples_conversions] +== Conversions + +[#examples_binary_floating_conversions] +=== Binary Floating Point Conversions + +.This https://github.com/cppalliance/decimal/blob/develop/examples/binary_float_conversions.cpp[example] shows show to construct a binary floating point value from decimal type and vice versa +==== +[source,c++] +---- +include::example$binary_float_conversions.cpp[] +---- + +.Expected Output: +.... +Decimal QNAN converts to double QNAN +Decimal INFINITY converts to double INFINITY +decimal64_t pi: 3.141592653589793 + double pi: 3.141592653589793 + Converted pi: 3.141592653589793 +decimal32_t pi: 3.141593 +.... +==== + [#examples_promotion] == Promotion and Mixed Decimal Arithmetic diff --git a/examples/binary_float_conversions.cpp b/examples/binary_float_conversions.cpp new file mode 100644 index 000000000..3b1a5f3a6 --- /dev/null +++ b/examples/binary_float_conversions.cpp @@ -0,0 +1,79 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// +// This file demonstrates how to convert various types to decimal types and back, +// along with edge case handling + +#include // For type decimal32_t +#include // For type decimal64_t +#include // For decimal support of cmath functions +#include // For decimal support of and +#include // For decimal support of +#include +#include +#include + +int main() +{ + using boost::decimal::decimal32_t; // Type decimal32_t + using boost::decimal::decimal64_t; // Type decimal64_t + + // Non-finite values construct the equivalent non-finite value in binary floating point + constexpr decimal64_t decimal_qnan {std::numeric_limits::quiet_NaN()}; + const double double_from_qnan {static_cast(decimal_qnan)}; + + // Note here that we must use boost::decimal::isnan for decimal types, + // as it is illegal to overload std::isnan + if (boost::decimal::isnan(decimal_qnan) && std::isnan(double_from_qnan)) + { + std::cout << "Decimal QNAN converts to double QNAN\n"; + } + + constexpr decimal64_t decimal_inf {std::numeric_limits::infinity()}; + const double double_from_inf {static_cast(decimal_inf)}; + + // Same as the above but with INF instead of NAN + if (boost::decimal::isinf(decimal_inf) && std::isinf(double_from_inf)) + { + std::cout << "Decimal INFINITY converts to double INFINITY\n"; + } + + // For finite values we make a best effort approach to covert to double + // We are able to decompose the decimal floating point value into a sign, significand, and exponent. + // From there we use the methods outline in Daniel Lemire's "Number Parsing at a Gigabyte a Second", + // to construct the binary floating point value. + // See: https://arxiv.org/pdf/2101.11408 + + // Construct the decimal64_t version of pi using our pre-computed constants from + constexpr decimal64_t decimal_pi {boost::decimal::numbers::pi_v}; + const double double_from_pi {static_cast(decimal_pi)}; + + std::cout << std::setprecision(std::numeric_limits::digits10) + << "decimal64_t pi: " << decimal_pi << '\n' + << " double pi: " << double_from_pi << '\n'; + + // To construct a decimal64_t from double we use the methods described in "Ryu: fast float-to-string conversion" + // See: https://dl.acm.org/doi/10.1145/3192366.3192369 + // This paper shows how to decompose a double into it's sign, significand, and exponent + // Once we have those components we can use the normal constructors of the decimal types to construct + // Since we are using the normal constructors here, + // any construction from this conversion is subject to the current rounding mode + // Such as with a lossy conversion like shown (double -> decimal32_t) + + const decimal64_t decimal_from_double {static_cast(double_from_pi)}; + const decimal32_t lossy_decimal_from_double {static_cast(double_from_pi)}; + + std::cout << " Converted pi: " << decimal_from_double << '\n' + << "decimal32_t pi: " << lossy_decimal_from_double << '\n'; + + + // Other than what has already been shown, + // there are no other ways in the library to convert between decimal types and binary floating point types + // The reason for this is to discourage their use. + // + // You can use intermediate representations like strings if you want to make these conversions, + // and want to be sure about what the resulting value will be + + return 0; +} diff --git a/test/Jamfile b/test/Jamfile index 320551eff..254b9bd15 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -215,6 +215,7 @@ run ../examples/basic_arithmetic.cpp ; run ../examples/to_from_file.cpp ; run ../examples/addition.cpp ; run ../examples/debugger.cpp ; +run ../examples/binary_float_conversions.cpp ; # Test compilation of separate headers compile compile_tests/bid_conversion.cpp ;