diff --git a/README.md b/README.md index 2c0882963..38f5c5f3c 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,14 @@ Boost Multiprecision Library | | Master | Develop | |------------------|----------|-------------| -| Drone | [![Build Status](https://drone.cpp.al/api/badges/boostorg/multiprecision/status.svg?ref=refs/heads/master)](https://drone.cpp.al/boostorg/multiprecision) | [![Build Status](https://drone.cpp.al/api/badges/boostorg/multiprecision/status.svg)](https://drone.cpp.al/boostorg/multiprecision) | -| Github Actions | [![Build Status](https://github.com/boostorg/multiprecision/workflows/multiprecision/badge.svg?branch=master)](https://github.com/boostorg/multiprecision/actions) | [![Build Status](https://github.com/boostorg/multiprecision/workflows/multiprecision/badge.svg?branch=develop)](https://github.com/boostorg/multiprecision/actions) | -| Codecov | [![codecov](https://codecov.io/gh/boostorg/multiprecision/branch/master/graph/badge.svg)](https://codecov.io/gh/boostorg/multiprecision/branch/master) | [![codecov](https://codecov.io/gh/boostorg/multiprecision/branch/develop/graph/badge.svg)](https://codecov.io/gh/boostorg/multiprecision/branch/develop) | +| Drone | [![Build Status](https://drone.cpp.al/api/badges/boostorg/multiprecision/status.svg?ref=refs/heads/master)](https://drone.cpp.al/boostorg/multiprecision) | [![Build Status](https://drone.cpp.al/api/badges/boostorg/multiprecision/status.svg)](https://drone.cpp.al/boostorg/multiprecision) | +| Github Actions | [![Build Status](https://github.com/boostorg/multiprecision/actions/workflows/multiprecision.yml/badge.svg?branch=master)](https://github.com/boostorg/multiprecision/actions) | [![Build Status](https://github.com/boostorg/multiprecision/actions/workflows/multiprecision.yml/badge.svg?branch=develop)](https://github.com/boostorg/multiprecision/actions) | +| Codecov | [![codecov](https://codecov.io/gh/boostorg/multiprecision/branch/master/graph/badge.svg)](https://codecov.io/gh/boostorg/multiprecision/branch/master) | [![codecov](https://codecov.io/gh/boostorg/multiprecision/branch/develop/graph/badge.svg)](https://codecov.io/gh/boostorg/multiprecision/branch/develop) | -`Boost.Multiprecision` is a C++ library that provides integer, rational, floating-point, complex and interval number types -having more range and precision than the language's ordinary built-in types. +`Boost.Multiprecision` is a C++ library that provides integer, rational, floating-point, +complex and interval number types having more range and precision than the language's +ordinary built-in types. Language adherence: - `Boost.Multiprecision` requires a compliant C++14 compiler. @@ -21,7 +22,7 @@ also interoperate with the built-in types in C++ using clearly defined conversio used for all kinds of mathematical calculations involving integer, rational and floating-point types requiring extended range and precision. Multiprecision consists of a generic interface to the mathematics of large numbers as well as a selection of big number back ends, with -support for integer, rational and floating-point types. `Boost.Multiprecision` provides a selection of back ends provided off-the-rack in +support for integer, rational and floating-point types. `Boost.Multiprecision` provides a selection of back ends provided off-the-rack including interfaces to GMP, MPFR, MPIR, TomMath as well as its own collection of Boost-licensed, header-only back ends for integers, rationals, floats and complex. In addition, user-defined back ends can be created and used with the interface of Multiprecision, provided the class implementation adheres to the necessary concepts. @@ -29,40 +30,42 @@ provided the class implementation adheres to the necessary concepts. Depending upon the number type, precision may be arbitrarily large (limited only by available memory), fixed at compile time (for example $50$ or $100$ decimal digits), or a variable controlled at run-time by member functions. The types are expression-template-enabled by default. This usually provides better performance than naive user-defined types. -If not needed, expression templates can be disabled when configuring the `number` type with its backend. +If not needed, expression templates can be disabled when configuring the `number`-type with its backend. The full documentation is available on [boost.org](http://www.boost.org/doc/libs/release/libs/multiprecision/index.html). -## Using Multiprecision ## +## Using Multiprecision

- +

In the following example, we use Multiprecision's Boost-licensed binary floating-point backend type `cpp_bin_float` to compute ${\sim}100$ decimal digits of -$$\sqrt{\pi} = \Gamma \left( \frac{1}{2} \right)~{\approx}~1.772453850905516027298{\ldots}\text{,}$$ +$$ +\sqrt{\pi} = \Gamma \left( \frac{1}{2} \right)~{\approx}~1.772453850905516027298{\ldots}\text{,} +$$ where we also observe that Multiprecision can seemlesly interoperate with [Boost.Math](https://github.com/boostorg/math). ```cpp +#include +#include + #include #include #include -#include -#include - auto main() -> int { using big_float_type = boost::multiprecision::cpp_bin_float_100; const big_float_type sqrt_pi { sqrt(boost::math::constants::pi()) }; - const big_float_type half { big_float_type(1) / 2 }; + const big_float_type half { big_float_type { 1 } / 2 }; const big_float_type gamma_half { boost::math::tgamma(half) }; @@ -75,7 +78,7 @@ auto main() -> int } ``` -## Standalone ## +## Standalone Defining `BOOST_MP_STANDALONE` allows `Boost.Multiprecision` to be used with the only dependency being [Boost.Config](https://github.com/boostorg/config). @@ -83,11 +86,11 @@ to be used with the only dependency being [Boost.Config](https://github.com/boos Our [package on this page](https://github.com/boostorg/multiprecision/releases) already includes a copy of Boost.Config so no other downloads are required. Some functionality is reduced in this mode. -A static_assert message will alert you if a particular feature has been disabled by standalone mode. +A `static_assert` message will alert you if a particular feature has been disabled by standalone mode. [Boost.Math](https://github.com/boostorg/math) standalone mode is compatiable, -and recommended if special functions are required for the floating point types. +and recommended if special functions are required for the floating-point types. -## Support, bugs and feature requests ## +## Support, bugs and feature requests Bugs and feature requests can be reported through the [Gitub issue tracker](https://github.com/boostorg/multiprecision/issues) (see [open issues](https://github.com/boostorg/multiprecision/issues) and @@ -100,7 +103,7 @@ although you can use the general-purpose Boost [mailing-list](http://lists.boost using the tag [multiprecision]. -## Development ## +## Development Clone the whole boost project, which includes the individual Boost projects as submodules ([see boost+git doc](https://github.com/boostorg/boost/wiki/Getting-Started)): @@ -113,7 +116,8 @@ Clone the whole boost project, which includes the individual Boost projects as s The Boost Multiprecision Library is located in `libs/multiprecision/`. -### Running tests ### +### Running tests + First, build the `b2` engine by running `bootstrap.sh` in the root of the boost directory. This will generate `b2` configuration in `project-config.jam`. ```sh diff --git a/doc/multiprecision.qbk b/doc/multiprecision.qbk index 6a5072272..a8e9c2873 100644 --- a/doc/multiprecision.qbk +++ b/doc/multiprecision.qbk @@ -1,8 +1,8 @@ [/ - Copyright 2011 - 2024 John Maddock. + Copyright 2011 - 2025 John Maddock. Copyright 2013 - 2019 Paul A. Bristow. - Copyright 2013 - 2024 Christopher Kormanyos. - Copyright 2021 - 2024 Fahad Syed. + Copyright 2013 - 2025 Christopher Kormanyos. + Copyright 2021 - 2025 Fahad Syed. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at @@ -11,7 +11,7 @@ [library Boost.Multiprecision [quickbook 1.7] - [copyright 2002-2020 John Maddock and Christopher Kormanyos] + [copyright 2002-2025 John Maddock and Christopher Kormanyos] [purpose Multiprecision Number library] [license Distributed under the Boost Software License, Version 1.0. diff --git a/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail.hpp b/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail.hpp index 67f8657a8..337f7f51e 100644 --- a/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail.hpp +++ b/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail.hpp @@ -1,6 +1,6 @@ /////////////////////////////////////////////////////////////////////////////// // Copyright 2021 Fahad Syed. -// Copyright 2021 - 2024 Christopher Kormanyos. +// Copyright 2021 - 2025 Christopher Kormanyos. // Copyright 2021 Janek Kozicki. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at diff --git a/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath.hpp b/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath.hpp index 74f893a1f..e5e05e56c 100644 --- a/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath.hpp +++ b/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath.hpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////// -// Copyright Christopher Kormanyos 2023. +// Copyright Christopher Kormanyos 2023 - 2025. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) diff --git a/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_fabs.hpp b/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_fabs.hpp index 8785efa16..83dcedb8f 100644 --- a/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_fabs.hpp +++ b/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_fabs.hpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////// -// Copyright Christopher Kormanyos 2023 - 2024. +// Copyright Christopher Kormanyos 2023 - 2025. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) diff --git a/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_floor.hpp b/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_floor.hpp index ae8bfbc8d..4df2f46a3 100644 --- a/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_floor.hpp +++ b/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_floor.hpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////// -// Copyright Christopher Kormanyos 2024. +// Copyright Christopher Kormanyos 2024 - 2025. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) diff --git a/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_fma.hpp b/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_fma.hpp index c9100f9f8..75bee6004 100644 --- a/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_fma.hpp +++ b/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_fma.hpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////// -// Copyright Christopher Kormanyos 2024. +// Copyright Christopher Kormanyos 2024 - 2025. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) diff --git a/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_fpclassify.hpp b/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_fpclassify.hpp index 3de62126c..7824e99ad 100644 --- a/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_fpclassify.hpp +++ b/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_fpclassify.hpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////// -// Copyright Christopher Kormanyos 2024. +// Copyright Christopher Kormanyos 2024 - 2025. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) diff --git a/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_frexp.hpp b/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_frexp.hpp index 3a5f89c8e..2ca53f32c 100644 --- a/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_frexp.hpp +++ b/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_frexp.hpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////// -// Copyright Christopher Kormanyos 2023 - 2024. +// Copyright Christopher Kormanyos 2023 - 2025. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) diff --git a/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_isinf.hpp b/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_isinf.hpp index 680de5397..7362c7f06 100644 --- a/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_isinf.hpp +++ b/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_isinf.hpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////// -// Copyright Christopher Kormanyos 2023 - 2024. +// Copyright Christopher Kormanyos 2023 - 2025. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) diff --git a/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_isnan.hpp b/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_isnan.hpp index 63fc3b468..468a97b97 100644 --- a/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_isnan.hpp +++ b/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_isnan.hpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////// -// Copyright Christopher Kormanyos 2023 - 2024. +// Copyright Christopher Kormanyos 2023 - 2025. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) diff --git a/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_ldexp.hpp b/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_ldexp.hpp index f9535d647..da2510969 100644 --- a/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_ldexp.hpp +++ b/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_ldexp.hpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////// -// Copyright Christopher Kormanyos 2023 - 2024. +// Copyright Christopher Kormanyos 2023 - 2025. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) diff --git a/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_limits.hpp b/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_limits.hpp index 9b3e2d272..7d8b01c1f 100644 --- a/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_limits.hpp +++ b/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_limits.hpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////// -// Copyright Christopher Kormanyos 2023 - 2024. +// Copyright Christopher Kormanyos 2023 - 2025. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) diff --git a/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_log.hpp b/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_log.hpp index b9a8f42d9..64cd301ed 100644 --- a/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_log.hpp +++ b/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_log.hpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////// -// Copyright Christopher Kormanyos 2024. +// Copyright Christopher Kormanyos 2024 - 2025. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) diff --git a/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_sqrt.hpp b/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_sqrt.hpp index ba985d790..381570b91 100644 --- a/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_sqrt.hpp +++ b/include/boost/multiprecision/cpp_df_qf/cpp_df_qf_detail_ccmath_sqrt.hpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////// -// Copyright Christopher Kormanyos 2023 - 2024. +// Copyright Christopher Kormanyos 2023 - 2025. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) diff --git a/include/boost/multiprecision/cpp_double_fp.hpp b/include/boost/multiprecision/cpp_double_fp.hpp index 4b94b9e42..704953050 100644 --- a/include/boost/multiprecision/cpp_double_fp.hpp +++ b/include/boost/multiprecision/cpp_double_fp.hpp @@ -1,6 +1,6 @@ /////////////////////////////////////////////////////////////////////////////// // Copyright 2021 Fahad Syed. -// Copyright 2021 - 2024 Christopher Kormanyos. +// Copyright 2021 - 2025 Christopher Kormanyos. // Copyright 2021 Janek Kozicki. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at @@ -122,12 +122,6 @@ template constexpr typename ::std::enable_if::value>::type eval_convert_to(OtherFloatingPointType* result, const cpp_double_fp_backend& backend); -template -::std::basic_ostream& operator<<(std::basic_ostream& os, - const cpp_double_fp_backend& f); - template ::std::size_t hash_value(const cpp_double_fp_backend& a); @@ -356,20 +350,36 @@ class cpp_double_fp_backend std::size_t hash() const { - // Here we first convert to scientific string, then - // hash the charactgers in the scientific string. + // Hash the raw values of the data field with direct-memory access. + // Use 16-bit (2 byte) chunks as the data size when hashing. + + static_assert( ( sizeof(data.first) == sizeof(data.second)) + && ( sizeof(float_type) >= sizeof(std::uint16_t)) + && ((sizeof(float_type) % sizeof(std::uint16_t)) == std::size_t { UINT8_C(0) }), + "Error: float_type size is inappropriate for hashing routine"); + + auto hash_one + { + [](std::size_t& res, const float_type& val) + { + const std::uint16_t* first { reinterpret_cast(&val) }; + const std::uint16_t* last { first + std::size_t { sizeof(float_type) / sizeof(std::uint16_t) } }; + + while (first != last) + { + boost::multiprecision::detail::hash_combine(res, *first); - // TBD: Is there a faster or more simple hash method? - // TBD: Is there any constexpr support for rudimentary hashing? + ++first; + } - const std::string str_to_hash { str(cpp_double_fp_backend::my_digits10, std::ios::scientific) }; + return res; + } + }; std::size_t result { UINT8_C(0) }; - for (std::size_t i = std::size_t { UINT8_C(0) }; i < str_to_hash.length(); ++i) - { - boost::multiprecision::detail::hash_combine(result, str_to_hash.at(i)); - } + static_cast(hash_one(result, data.first)); + static_cast(hash_one(result, data.second)); return result; } @@ -572,13 +582,7 @@ class cpp_double_fp_backend if (iszero_u || iszero_v) { - const bool b_neg { (isneg_unchecked() != v.isneg_unchecked()) }; - - operator=(cpp_double_fp_backend(0)); - - if (b_neg) { negate(); } - - return *this; + return operator=(cpp_double_fp_backend(0)); } } @@ -678,14 +682,6 @@ class cpp_double_fp_backend const bool iszero_u { (fpc_u == FP_ZERO) }; const bool iszero_v { (fpc_v == FP_ZERO) }; - if (this == &v) - { - data.first = float_type { 1.0F }; - data.second = float_type { 0.0F }; - - return *this; - } - if (iszero_u) { if (iszero_v) @@ -736,6 +732,14 @@ class cpp_double_fp_backend } } + if (this == &v) + { + data.first = float_type { 1.0F }; + data.second = float_type { 0.0F }; + + return *this; + } + // The division algorithm has been taken from Victor Shoup, // package WinNTL-5_3_2. It might originally be related to the // K. Briggs work. The algorithm has been significantly simplified @@ -840,56 +844,6 @@ class cpp_double_fp_backend return v; } - // Helper functions. - constexpr static cpp_double_fp_backend pown(const cpp_double_fp_backend& x, int p) - { - using local_float_type = cpp_double_fp_backend; - - local_float_type result { }; - - if (p < 0) - result = pown(local_float_type(1U) / x, -p); - else if (p == 0) - result = local_float_type(1U); - else if (p == 1) - result = x; - else if (p == 2) - result = local_float_type(x * x); - else if (p == 3) - result = local_float_type((x * x) * x); - else if (p == 4) - { const local_float_type x2 { x * x }; result = x2 * x2; } - else - { - result = local_float_type(1U); - - local_float_type y(x); - - auto p_local = static_cast(p); - - for (;;) - { - if (static_cast(p_local & static_cast(UINT8_C(1))) != static_cast(UINT8_C(0))) - { - result *= y; - } - - p_local >>= 1U; - - if (p_local == static_cast(UINT8_C(0))) - { - break; - } - else - { - y *= y; - } - } - } - - return result; - } - constexpr void swap(cpp_double_fp_backend& other) { if (this != &other) @@ -953,56 +907,37 @@ class cpp_double_fp_backend return e2; } - constexpr int order10() const { return static_cast(static_cast(order02()) * 0.301F); } - - constexpr bool small_arg() const { return (order10() < static_cast(-my_digits10 / 6)); } - - constexpr bool near_one() const - { - cpp_double_fp_backend delta_one { }; - - eval_subtract(delta_one, cpp_double_fp_backend(1U), *this); - - if (delta_one().isneg_unchecked()) - { - delta_one.negate(); - } - - return delta_one.small_arg(); - } - static constexpr cpp_double_fp_backend my_value_max() noexcept { // Use the non-normalized sum of two maximum values, where the lower // value is "shifted" right in the sense of floating-point ldexp. - // TBD: This value _still_ needs to be independently verified. + constexpr float_type + hi_part + { + (cpp_df_qf_detail::ccmath::numeric_limits::max)() + * ( + static_cast(1.0F) + - static_cast(1.5F) * cpp_df_qf_detail::ccmath::unsafe::sqrt(cpp_df_qf_detail::ccmath::numeric_limits::epsilon()) + ) + }; - constexpr cpp_double_fp_backend - my_value_max_constexpr + constexpr float_type + lo_part { - arithmetic::two_hilo_sum + cpp_df_qf_detail::ccmath::unsafe::ldexp ( - static_cast - ( - (cpp_df_qf_detail::ccmath::numeric_limits::max)() - * ( - static_cast(1.0F) - - static_cast(1.5F) * cpp_df_qf_detail::ccmath::unsafe::sqrt(cpp_df_qf_detail::ccmath::numeric_limits::epsilon()) - ) - ), - cpp_df_qf_detail::ccmath::unsafe::ldexp - ( - (cpp_df_qf_detail::ccmath::numeric_limits::max)(), - -(cpp_df_qf_detail::ccmath::numeric_limits::digits + 1) - ) + (cpp_df_qf_detail::ccmath::numeric_limits::max)(), + -cpp_df_qf_detail::ccmath::numeric_limits::digits ) }; + constexpr cpp_double_fp_backend my_value_max_constexpr { arithmetic::two_hilo_sum(hi_part, lo_part) }; + static_assert ( - eval_gt(my_value_max_constexpr, cpp_double_fp_backend { (cpp_df_qf_detail::ccmath::numeric_limits::max)() / 2 }), - "Error: maximum value is too small and must exceed (1/2 * max) of its constituent type" + eval_gt(my_value_max_constexpr, cpp_double_fp_backend { hi_part }), + "Error: maximum value is too small in relation to the maximum of its constituent type" ); return my_value_max_constexpr; @@ -1019,7 +954,7 @@ class cpp_double_fp_backend cpp_df_qf_detail::ccmath::unsafe::ldexp ( (cpp_df_qf_detail::ccmath::numeric_limits::min)(), - cpp_df_qf_detail::ccmath::numeric_limits::digits + cpp_df_qf_detail::ccmath::numeric_limits::digits ) }; @@ -1270,29 +1205,6 @@ constexpr cpp_double_fp_backend operator*(const cpp_double_fp template constexpr cpp_double_fp_backend operator/(const cpp_double_fp_backend& a, const cpp_double_fp_backend& b) { return cpp_double_fp_backend(a) /= b; } -// Input/Output Streaming -template -::std::basic_ostream& -operator<<(::std::basic_ostream& os, const cpp_double_fp_backend& f) -{ - const auto str_result = f.str(os.precision(), os.flags()); - - return (os << str_result); -} - -template -::std::basic_istream& -operator>>(::std::basic_istream& is, cpp_double_fp_backend& f) -{ - std::string input_str; - - is >> input_str; - - f = input_str.c_str(); - - return is; -} - template constexpr void eval_add(cpp_double_fp_backend& result, const cpp_double_fp_backend& x) { result += x; } template @@ -1488,8 +1400,6 @@ constexpr void eval_exp(cpp_double_fp_backend& result, const { const int fpc { eval_fpclassify(x) }; - const bool x_is_zero { fpc == FP_ZERO }; - using double_float_type = cpp_double_fp_backend; if (fpc == FP_ZERO) @@ -1543,11 +1453,7 @@ constexpr void eval_exp(cpp_double_fp_backend& result, const }() }; - if (x_is_zero) - { - result = double_float_type(1U); - } - else if (eval_lt(x, min_exp_input)) + if (eval_lt(x, min_exp_input)) { result = double_float_type(0U); } @@ -1642,8 +1548,6 @@ constexpr void eval_exp(cpp_double_fp_backend& result, const { const int fpc { eval_fpclassify(x) }; - const bool x_is_zero { fpc == FP_ZERO }; - using double_float_type = cpp_double_fp_backend; if (fpc == FP_ZERO) @@ -1697,11 +1601,7 @@ constexpr void eval_exp(cpp_double_fp_backend& result, const }() }; - if (x_is_zero) - { - result = double_float_type(1U); - } - else if (eval_lt(x, min_exp_input)) + if (eval_lt(x, min_exp_input)) { result = double_float_type(0U); } @@ -1796,8 +1696,6 @@ constexpr void eval_exp(cpp_double_fp_backend& result, const { const int fpc { eval_fpclassify(x) }; - const bool x_is_zero { fpc == FP_ZERO }; - using double_float_type = cpp_double_fp_backend; if (fpc == FP_ZERO) @@ -1851,11 +1749,7 @@ constexpr void eval_exp(cpp_double_fp_backend& result, const }() }; - if (x_is_zero) - { - result = double_float_type(1U); - } - else if (eval_lt(x, min_exp_input)) + if (eval_lt(x, min_exp_input)) { result = double_float_type(0U); } @@ -2022,19 +1916,23 @@ constexpr void eval_convert_to(signed long long* result, const cpp_double_fp_bac return; } - constexpr signed long long my_max_val = (std::numeric_limits::max)(); - constexpr signed long long my_min_val = (std::numeric_limits::min)(); + constexpr signed long long my_max_val { (std::numeric_limits::max)() }; + constexpr signed long long my_min_val { (std::numeric_limits::min)() }; using c_type = typename std::common_type::type; - constexpr c_type my_max = static_cast(my_max_val); - const c_type ct = cpp_df_qf_detail::ccmath::fabs(backend.crep().first); + constexpr c_type my_max { static_cast(my_max_val) }; + constexpr c_type my_min { static_cast(my_min_val) }; + + const c_type ct { static_cast(backend.crep().first) }; if (ct > my_max) { - *result = backend.crep().first >= typename cpp_double_fp_backend::float_type(0U) - ? my_max_val - : my_min_val; + *result = my_max_val; + } + else if (ct < my_min) + { + *result = my_min_val; } else { @@ -2075,12 +1973,13 @@ constexpr void eval_convert_to(unsigned long long* result, const cpp_double_fp_b return; } - constexpr unsigned long long my_max_val = (std::numeric_limits::max)(); + constexpr unsigned long long my_max_val { (std::numeric_limits::max)() }; using c_type = typename std::common_type::type; - constexpr c_type my_max = static_cast(my_max_val); - const c_type ct = cpp_df_qf_detail::ccmath::fabs(backend.crep().first); + constexpr c_type my_max { static_cast(my_max_val) }; + + const c_type ct { static_cast(backend.crep().first) }; if (ct > my_max) { @@ -2088,112 +1987,73 @@ constexpr void eval_convert_to(unsigned long long* result, const cpp_double_fp_b } else { - BOOST_IF_CONSTEXPR(std::numeric_limits::digits >= cpp_df_qf_detail::ccmath::numeric_limits::digits) - { - *result = static_cast(backend.crep().first); - *result += static_cast(backend.crep().second); - } - else - { - cpp_double_fp_backend source = backend; - - *result = 0; - - for(auto digit_count = 0; - digit_count < cpp_double_fp_backend::my_digits; - digit_count += std::numeric_limits::digits) - { - const auto next = static_cast(source.crep().first); - - *result += next; - - eval_subtract(source, cpp_double_fp_backend(next)); - } - } + *result = static_cast(backend.crep().first); + *result += static_cast(backend.crep().second); } } #ifdef BOOST_HAS_INT128 template -constexpr void eval_convert_to(int128_type* result, const cpp_double_fp_backend& backend) +constexpr void eval_convert_to(boost::int128_type* result, const cpp_double_fp_backend& backend) { const auto fpc = eval_fpclassify(backend); if (fpc != FP_NORMAL) { - *result = static_cast(backend.crep().first); + *result = static_cast(backend.crep().first); return; } - constexpr int128_type my_max_val = (((static_cast(1) << (sizeof(int128_type) * CHAR_BIT - 2)) - 1) << 1) + 1; - constexpr int128_type my_min_val = static_cast(-my_max_val - 1); + constexpr boost::int128_type my_max_val = (((static_cast(1) << (sizeof(boost::int128_type) * CHAR_BIT - 2)) - 1) << 1) + 1; + constexpr boost::int128_type my_min_val = static_cast(-my_max_val - 1); - using c_type = typename std::common_type::type; + using c_type = typename std::common_type::type; - constexpr c_type my_max = static_cast(my_max_val); - const c_type ct = cpp_df_qf_detail::ccmath::fabs(backend.crep().first); + constexpr c_type my_max { static_cast(my_max_val) }; + constexpr c_type my_min { static_cast(my_min_val) }; + + const c_type ct { static_cast(backend.crep().first) }; if (ct > my_max) { - *result = backend.crep().first >= typename cpp_double_fp_backend::float_type(0U) - ? my_max_val - : my_min_val; + *result = my_max_val; + } + if (ct < my_min) + { + *result = my_min_val; } else { - BOOST_IF_CONSTEXPR(static_cast(static_cast(sizeof(int128_type)) * CHAR_BIT) >= cpp_df_qf_detail::ccmath::numeric_limits::digits) - { - *result = static_cast(backend.crep().first); - *result += static_cast(backend.crep().second); - } - else - { - cpp_double_fp_backend source = backend; - - *result = 0; - - for(auto digit_count = static_cast(0); - digit_count < cpp_double_fp_backend::my_digits; - digit_count += static_cast(static_cast(sizeof(int128_type)) * CHAR_BIT)) - { - const auto next = static_cast(source.crep().first); - - *result += next; - - eval_subtract(source, cpp_double_fp_backend(next)); - } - } + *result = static_cast(backend.crep().first); + *result += static_cast(backend.crep().second); } } template -constexpr void eval_convert_to(uint128_type* result, const cpp_double_fp_backend& backend) +constexpr void eval_convert_to(boost::uint128_type* result, const cpp_double_fp_backend& backend) { const auto fpc = eval_fpclassify(backend); if (fpc != FP_NORMAL) { - *result = static_cast(backend.crep().first); + *result = static_cast(backend.crep().first); return; } - uint128_type my_max_val { }; - - BOOST_IF_CONSTEXPR(std::is_same::value && (std::numeric_limits::digits == 24)) + constexpr boost::uint128_type my_max_val { - my_max_val = static_cast(FLT_MAX); - } - else - { - my_max_val = static_cast(~static_cast(0)); - } + (std::is_same::value && (cpp_df_qf_detail::ccmath::numeric_limits::digits == 24)) + ? static_cast(FLT_MAX) + : static_cast(~static_cast(0)) + }; + + using c_type = typename std::common_type::type; - using c_type = typename std::common_type::type; + constexpr c_type my_max { static_cast(my_max_val) }; - const c_type my_max = static_cast(my_max_val); - const c_type ct = cpp_df_qf_detail::ccmath::fabs(backend.crep().first); + const c_type ct { static_cast(backend.crep().first) }; if (ct > my_max) { @@ -2201,28 +2061,10 @@ constexpr void eval_convert_to(uint128_type* result, const cpp_double_fp_backend } else { - BOOST_IF_CONSTEXPR(static_cast(static_cast(sizeof(uint128_type)) * CHAR_BIT) >= cpp_df_qf_detail::ccmath::numeric_limits::digits) - { - *result = static_cast(backend.crep().first); - *result += static_cast(backend.crep().second); - } - else - { - cpp_double_fp_backend source = backend; - - *result = 0; + *result = static_cast(backend.crep().first); + *result += static_cast(backend.crep().second); - for(auto digit_count = static_cast(0); - digit_count < cpp_double_fp_backend::my_digits; - digit_count += static_cast(static_cast(sizeof(uint128_type)) * CHAR_BIT)) - { - const auto next = static_cast(source.crep().first); - - *result += next; - - eval_subtract(source, cpp_double_fp_backend(next)); - } - } + *result = static_cast(*result); } } #endif @@ -2233,7 +2075,7 @@ constexpr typename ::std::enable_ifquadmath ] ] [ run test_cpp_dec_float_round.cpp no_eh_support ] + [ run test_various_edges.cpp no_eh_support ] [ run test_arithmetic_logged_1.cpp no_eh_support : : : msvc:-bigobj [ check-target-builds ../config//has_float128 : quadmath ] ] [ run test_arithmetic_logged_2.cpp no_eh_support : : : msvc:-bigobj [ check-target-builds ../config//has_float128 : quadmath ] ] diff --git a/test/test_arithmetic.hpp b/test/test_arithmetic.hpp index 6bc741aa9..6b25b4cf6 100644 --- a/test/test_arithmetic.hpp +++ b/test/test_arithmetic.hpp @@ -1736,6 +1736,25 @@ void test_float_ops(const std::integral_constant::is_specialized && std::numeric_limits::has_infinity && std::numeric_limits::max_exponent10 > 18 && std::numeric_limits::min_exponent10 < -18) + { + Real a = (std::numeric_limits::max)(); + + a /= 1000000; + a /= 1000000; + a /= 1000000; + + BOOST_CHECK((boost::math::isfinite)(a)); + + a = (std::numeric_limits::min)(); + + a *= 1000000; + a *= 1000000; + a *= 1000000; + + BOOST_CHECK((boost::math::isfinite)(a)); + } + test_float_funcs(std::integral_constant::is_specialized>()); } diff --git a/test/test_exp.cpp b/test/test_exp.cpp index 11328b319..ea6394859 100644 --- a/test/test_exp.cpp +++ b/test/test_exp.cpp @@ -15,9 +15,12 @@ #endif #include -#include #include "test.hpp" +#include +#include +#include + #if !defined(TEST_MPF_50) && !defined(TEST_MPF) && !defined(TEST_BACKEND) && !defined(TEST_CPP_DEC_FLOAT) && !defined(TEST_MPFR) && !defined(TEST_MPFR_50) && !defined(TEST_MPFI_50) && !defined(TEST_FLOAT128) && !defined(TEST_CPP_BIN_FLOAT) && !defined(TEST_CPP_DOUBLE_FLOAT) #define TEST_MPF_50 //# define TEST_MPF @@ -210,7 +213,6 @@ void test() } } - #if defined(TEST_CPP_DOUBLE_FLOAT) // Handle uneven/asymmetric exponents on min/max of cpp_double_fp_backend bug_case = log(1 / (std::numeric_limits::min)()) / -1.0005; diff --git a/test/test_various_edges.cpp b/test/test_various_edges.cpp new file mode 100644 index 000000000..60cf1d659 --- /dev/null +++ b/test/test_various_edges.cpp @@ -0,0 +1,895 @@ +// Copyright 2023 - 2024 Matt Borland +// Copyright 2023 - 2025 Christopher Kormanyos +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// Some parts of this test file have been taken from the Boost.Decimal project. + +#if !defined(TEST_CPP_DOUBLE_FLOAT) +#define TEST_CPP_DOUBLE_FLOAT +#endif + +#include +#include +#if defined(TEST_CPP_DOUBLE_FLOAT) +#include +#endif + +#include +#include +#include +#include +#include + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wfloat-equal" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + +#include + +template auto my_zero() noexcept -> FloatType&; +template auto my_one () noexcept -> FloatType&; +template auto my_inf () noexcept -> FloatType&; +template auto my_nan () noexcept -> FloatType&; +template auto my_exp1() noexcept -> FloatType&; + +namespace local +{ + template + auto time_point() noexcept -> IntegralTimePointType + { + using local_integral_time_point_type = IntegralTimePointType; + using local_clock_type = ClockType; + + const auto current_now = + static_cast + ( + std::chrono::duration_cast + ( + local_clock_type::now().time_since_epoch() + ).count() + ); + + return static_cast(current_now); + } + + template + auto is_close_fraction(const NumericType& a, + const NumericType& b, + const NumericType& tol) noexcept -> bool + { + using std::fabs; + + auto result_is_ok = bool { }; + + NumericType delta { }; + + if(b == static_cast(0)) + { + delta = fabs(a - b); // LCOV_EXCL_LINE + + result_is_ok = (delta < tol); // LCOV_EXCL_LINE + } + else + { + delta = fabs(1 - (a / b)); + + result_is_ok = (delta < tol); + } + + return result_is_ok; + } + + template + bool test_edges() + { + using float_type = FloatType; + using ctrl_type = boost::multiprecision::number, boost::multiprecision::et_off>; + + std::mt19937_64 gen { time_point() }; + + auto dis = + std::uniform_real_distribution + { + static_cast(1.01F), + static_cast(1.04F) + }; + + bool result_is_ok { true }; + + { + float_type flt_val { (std::numeric_limits::max)() }; + ctrl_type ctl_val { flt_val }; + + for(auto i = static_cast(UINT8_C(0)); i < static_cast(UINT8_C(16)); ++i) + { + static_cast(i); + + float_type flt_denom { 2345.6F * dis(gen) }; + + flt_val /= flt_denom; + ctl_val /= ctrl_type { flt_denom }; + + const bool result_div_is_ok { local::is_close_fraction(flt_val, float_type { ctl_val }, std::numeric_limits::epsilon() * 32) }; + + BOOST_TEST(result_div_is_ok); + + result_is_ok = (result_div_is_ok && result_is_ok); + } + } + + { + float_type flt_val { (std::numeric_limits::min)() }; + ctrl_type ctl_val { flt_val }; + + for(auto i = static_cast(UINT8_C(0)); i < static_cast(UINT8_C(16)); ++i) + { + static_cast(i); + + float_type flt_factor { 2345.6F * dis(gen) }; + + flt_val *= flt_factor; + ctl_val *= ctrl_type { flt_factor }; + + const bool result_mul_is_ok { local::is_close_fraction(flt_val, float_type { ctl_val }, std::numeric_limits::epsilon() * 32) }; + + BOOST_TEST(result_mul_is_ok); + + result_is_ok = (result_mul_is_ok && result_is_ok); + } + } + + { + float_type flt_val { (std::numeric_limits::max)() }; + ctrl_type ctl_val { flt_val }; + + for(auto i = static_cast(UINT8_C(0)); i < static_cast(UINT8_C(16)); ++i) + { + static_cast(i); + + float_type flt_factor { 1234.56e-12F * dis(gen) }; + + flt_val *= flt_factor; + ctl_val *= ctrl_type { flt_factor }; + + const bool result_mul_is_ok { local::is_close_fraction(flt_val, float_type { ctl_val }, std::numeric_limits::epsilon() * 32) }; + + BOOST_TEST(result_mul_is_ok); + + result_is_ok = (result_mul_is_ok && result_is_ok); + } + } + + { + float_type flt_val { (std::numeric_limits::max)() }; + ctrl_type ctl_val { flt_val }; + + for(auto i = static_cast(UINT8_C(0)); i < static_cast(UINT8_C(16)); ++i) + { + static_cast(i); + + float_type flt_factor { 1234.56e-12F * dis(gen) }; + + flt_val = flt_factor * flt_val; + ctl_val *= ctrl_type { flt_factor }; + + const bool result_mul_is_ok { local::is_close_fraction(flt_val, float_type { ctl_val }, std::numeric_limits::epsilon() * 32) }; + + BOOST_TEST(result_mul_is_ok); + + result_is_ok = (result_mul_is_ok && result_is_ok); + } + } + + { + float_type flt_val { }; + ctrl_type ctl_val { }; + + for(auto i = static_cast(UINT8_C(0)); i < static_cast(UINT8_C(16)); ++i) + { + static_cast(i); + + flt_val = sqrt((std::numeric_limits::max)()); + ctl_val = ctrl_type { flt_val }; + + float_type flt_factor { 123.456F * dis(gen) }; + + unsigned j { static_cast(UINT8_C(0)) }; + + for(; j < static_cast(UINT16_C(8192)); ++j) + { + if(unsigned { j % unsigned { UINT8_C(3) } } == unsigned { UINT8_C(0)}) + { + flt_val = -flt_val; + ctl_val = -ctl_val; + } + + if(unsigned { j % unsigned { UINT8_C(2) } } == unsigned { UINT8_C(0)}) + { + flt_factor = -flt_factor; + } + + flt_val *= flt_factor; + ctl_val *= ctrl_type { flt_factor }; + + if(isinf(flt_val)) { break; } + + const bool result_finite_mul_is_ok + { + local::is_close_fraction(flt_val, float_type { ctl_val }, std::numeric_limits::epsilon() * 32) + && (signbit(flt_val) == signbit(ctl_val)) + }; + + BOOST_TEST(result_finite_mul_is_ok); + } + + const bool result_mul_is_ok + { + isinf(flt_val) + && (j < unsigned { UINT16_C(8192) }) + && (signbit(flt_val) == signbit(ctl_val)) + }; + + BOOST_TEST(result_mul_is_ok); + + result_is_ok = (result_mul_is_ok && result_is_ok); + } + } + + { + const float_type inf_pos_01 { "1e100001" }; + const float_type inf_pos_02 { "1e100002" }; + const float_type inf_pos_03 { "1e100003" }; + const float_type inf_neg_01 { "-1e100001" }; + const float_type inf_neg_02 { "-1e100002" }; + const float_type inf_neg_03 { "-1e100003" }; + + const float_type tiny_01 { "1e-100001" }; + const float_type tiny_02 { "1e-100002" }; + const float_type tiny_03 { "1e-100003" }; + + BOOST_TEST(result_is_ok = (isinf(inf_pos_01) && result_is_ok)); + BOOST_TEST(result_is_ok = (isinf(inf_pos_02) && result_is_ok)); + BOOST_TEST(result_is_ok = (isinf(inf_pos_03) && result_is_ok)); + + BOOST_TEST(result_is_ok = (isinf(inf_neg_01) && signbit(inf_neg_01) && result_is_ok)); + BOOST_TEST(result_is_ok = (isinf(inf_neg_02) && signbit(inf_neg_02) && result_is_ok)); + BOOST_TEST(result_is_ok = (isinf(inf_neg_03) && signbit(inf_neg_03) && result_is_ok)); + + BOOST_TEST(result_is_ok = ((fpclassify(tiny_01) == FP_ZERO) && result_is_ok)); + BOOST_TEST(result_is_ok = ((fpclassify(tiny_02) == FP_ZERO) && result_is_ok)); + BOOST_TEST(result_is_ok = ((fpclassify(tiny_03) == FP_ZERO) && result_is_ok)); + } + + for(auto i = static_cast(UINT8_C(0)); i < static_cast(UINT8_C(8)); ++i) + { + static_cast(i); + + float_type flt_x { 2345.6F + static_cast(i) * dis(gen) }; + + const float_type quotient_one = flt_x /= flt_x; + + BOOST_TEST(result_is_ok = ((quotient_one == 1) && result_is_ok)); + } + + for(auto i = static_cast(UINT8_C(0)); i < static_cast(UINT8_C(8)); ++i) + { + static_cast(i); + + float_type flt_x { 1000.0F + static_cast(i) * dis(gen) }; + + const float_type sub_result_zero = flt_x -= flt_x; + + BOOST_TEST(result_is_ok = ((fpclassify(sub_result_zero) == FP_ZERO) && result_is_ok)); + } + + for(auto i = static_cast(UINT8_C(0)); i < static_cast(UINT8_C(8)); ++i) + { + static_cast(i); + + float_type flt_numpos { ::my_one() * dis(gen) }; + float_type flt_numneg { ::my_one() * -dis(gen) }; + float_type flt_denom { ::my_zero() * dis(gen) }; + + const float_type div_result_zero_pos { flt_numpos / flt_denom }; + const float_type div_result_zero_neg { flt_numneg / flt_denom }; + + BOOST_TEST(result_is_ok = ((fpclassify(div_result_zero_pos) == FP_INFINITE) && result_is_ok)); + BOOST_TEST(result_is_ok = ((fpclassify(div_result_zero_neg) == FP_INFINITE) && signbit(div_result_zero_neg) && result_is_ok)); + } + + for(auto i = static_cast(UINT8_C(0)); i < static_cast(UINT8_C(8)); ++i) + { + static_cast(i); + + float_type flt_x { float_type { (std::numeric_limits::max)() } * static_cast((static_cast(i) + 1) * 2) * dis(gen) }; + + const signed long long conversion_result_max { static_cast(flt_x) }; + + BOOST_TEST(result_is_ok = ((conversion_result_max == (std::numeric_limits::max)()) && result_is_ok)); + } + + for(auto i = static_cast(UINT8_C(0)); i < static_cast(UINT8_C(8)); ++i) + { + static_cast(i); + + float_type flt_x { float_type { (std::numeric_limits::min)() } * static_cast((static_cast(i) + 1) * 2) * dis(gen) }; + + const signed long long conversion_result_min { static_cast(flt_x) }; + + BOOST_TEST(result_is_ok = ((conversion_result_min == (std::numeric_limits::min)()) && result_is_ok)); + } + + { + for(auto i = static_cast(UINT8_C(0)); i < static_cast(UINT8_C(16)); ++i) + { + static_cast(i); + + const float_type flt_factor_inf_pos { std::numeric_limits::infinity() * dis(gen) }; + const float_type flt_factor_inf_neg { std::numeric_limits::infinity() * -dis(gen) }; + + { + const float_type val_inf_pos_neg_add { flt_factor_inf_pos + flt_factor_inf_neg }; + + const bool result_inf_pos_neg_add_is_ok { isnan(val_inf_pos_neg_add) }; + + BOOST_TEST(result_inf_pos_neg_add_is_ok); + + result_is_ok = (result_inf_pos_neg_add_is_ok && result_is_ok); + } + + { + const float_type val_inf_pos_pos_add { flt_factor_inf_pos + -flt_factor_inf_neg }; + + const bool result_inf_pos_pos_add_is_ok { isinf(val_inf_pos_pos_add) }; + + BOOST_TEST(result_inf_pos_pos_add_is_ok); + + result_is_ok = (result_inf_pos_pos_add_is_ok && result_is_ok); + } + + { + const float_type val_inf_neg_neg_add { -flt_factor_inf_pos + (flt_factor_inf_neg) }; + + const bool result_inf_neg_neg_add_is_ok { isinf(val_inf_neg_neg_add) && signbit(val_inf_neg_neg_add) }; + + BOOST_TEST(result_inf_neg_neg_add_is_ok); + + result_is_ok = (result_inf_neg_neg_add_is_ok && result_is_ok); + } + + { + const float_type val_inf_pos_neg_sub { flt_factor_inf_pos - flt_factor_inf_neg }; + + const bool result_inf_pos_neg_sub_is_ok { isinf(val_inf_pos_neg_sub) }; + + BOOST_TEST(result_inf_pos_neg_sub_is_ok); + + result_is_ok = (result_inf_pos_neg_sub_is_ok && result_is_ok); + } + + { + const float_type val_inf_pos_pos_sub { flt_factor_inf_pos - (-flt_factor_inf_neg) }; + + const bool result_inf_pos_pos_sub_is_ok { isnan(val_inf_pos_pos_sub) }; + + BOOST_TEST(result_inf_pos_pos_sub_is_ok); + + result_is_ok = (result_inf_pos_pos_sub_is_ok && result_is_ok); + } + + { + const float_type val_inf_neg_neg_sub { -flt_factor_inf_pos - (flt_factor_inf_neg) }; + + const bool result_inf_neg_neg_sub_is_ok { isnan(val_inf_neg_neg_sub) }; + + BOOST_TEST(result_inf_neg_neg_sub_is_ok); + + result_is_ok = (result_inf_neg_neg_sub_is_ok && result_is_ok); + } + + } + } + + return result_is_ok; + } + + template + bool test_edges_extra() + { + using float_type = FloatType; + + std::mt19937_64 gen { time_point() }; + + auto dis = + std::uniform_real_distribution + { + static_cast(1.01F), + static_cast(1.04F) + }; + + bool result_is_ok { true }; + + for(auto i = static_cast(UINT8_C(0)); i < static_cast(UINT8_C(8)); ++i) + { + static_cast(i); + + float_type flt_x { float_type { (std::numeric_limits::max)() } * static_cast((static_cast(i) + 1) * 2) * dis(gen) }; + + const unsigned long long conversion_result_max { static_cast(flt_x) }; + + BOOST_TEST(result_is_ok = ((conversion_result_max == (std::numeric_limits::max)()) && result_is_ok)); + } + + return result_is_ok; + } + + template + auto test_sqrt_edge() -> bool + { + using float_type = FloatType; + + std::mt19937_64 gen { time_point() }; + + std::uniform_real_distribution + dist + ( + static_cast(1.01L), + static_cast(1.04L) + ); + + auto result_is_ok = true; + + for(auto i = static_cast(UINT8_C(0)); i < static_cast(UINT8_C(4)); ++i) + { + static_cast(i); + + const auto val_nan = sqrt(-std::numeric_limits::quiet_NaN() * static_cast(dist(gen))); + + const auto result_val_nan_is_ok = isnan(val_nan); + + BOOST_TEST(result_val_nan_is_ok); + + result_is_ok = (result_val_nan_is_ok && result_is_ok); + } + + for(auto i = static_cast(UINT8_C(0)); i < static_cast(UINT8_C(4)); ++i) + { + static_cast(i); + + const auto val_inf_pos = sqrt(std::numeric_limits::infinity() * static_cast(dist(gen))); + + const auto result_val_inf_pos_is_ok = (isinf(val_inf_pos) && (!signbit(val_inf_pos))); + + BOOST_TEST(result_val_inf_pos_is_ok); + + result_is_ok = (result_val_inf_pos_is_ok && result_is_ok); + } + + for(auto i = static_cast(UINT8_C(0)); i < static_cast(UINT8_C(4)); ++i) + { + static_cast(i); + + const auto val_one = sqrt(::my_one()); + + const auto result_val_one_is_ok = (val_one == ::my_one()); + + BOOST_TEST(result_val_one_is_ok); + + result_is_ok = (result_val_one_is_ok && result_is_ok); + } + + for(auto i = static_cast(UINT8_C(0)); i < static_cast(UINT8_C(4)); ++i) + { + static_cast(i); + + const auto val_zero = sqrt(::my_zero()); + + const auto result_val_zero_is_ok = ((val_zero == ::my_zero()) && (!signbit(val_zero))); + + BOOST_TEST(result_val_zero_is_ok); + + result_is_ok = (result_val_zero_is_ok && result_is_ok); + } + + return result_is_ok; + } + + template + auto test_exp_edge() -> bool + { + using float_type = FloatType; + + std::mt19937_64 gen { time_point() }; + + std::uniform_real_distribution + dist + ( + static_cast(0.95L), + static_cast(1.05L) + ); + + auto result_is_ok = true; + + for(auto i = static_cast(UINT8_C(0)); i < static_cast(UINT8_C(16)); ++i) + { + static_cast(i); + + const float_type val_nan { exp(std::numeric_limits::quiet_NaN() * static_cast(dist(gen))) }; + + const bool result_val_nan_is_ok { isnan(val_nan) }; + + BOOST_TEST(result_val_nan_is_ok); + + result_is_ok = (result_val_nan_is_ok && result_is_ok); + } + + for(auto i = static_cast(UINT8_C(0)); i < static_cast(UINT8_C(16)); ++i) + { + static_cast(i); + + const float_type arg_inf { std::numeric_limits::infinity() * static_cast(dist(gen)) }; + + const float_type val_inf_pos { exp(arg_inf) }; + + const bool result_val_inf_pos_is_ok { (fpclassify(val_inf_pos) == FP_INFINITE) }; + + BOOST_TEST(result_val_inf_pos_is_ok); + + result_is_ok = (result_val_inf_pos_is_ok && result_is_ok); + } + + for(auto i = static_cast(UINT8_C(0)); i < static_cast(UINT8_C(16)); ++i) + { + static_cast(i); + + const float_type val_inf_neg { exp(-std::numeric_limits::infinity() * static_cast(dist(gen))) }; + + const bool result_val_inf_neg_is_ok { (val_inf_neg == ::my_zero()) }; + + BOOST_TEST(result_val_inf_neg_is_ok); + + result_is_ok = (result_val_inf_neg_is_ok && result_is_ok); + } + + for(auto i = static_cast(UINT8_C(0)); i < static_cast(UINT8_C(16)); ++i) + { + static_cast(i); + + const float_type val_zero { exp(::my_zero() * static_cast(dist(gen))) }; + + const bool result_val_zero_is_ok { (val_zero == ::my_one()) }; + + BOOST_TEST(result_val_zero_is_ok); + + result_is_ok = (result_val_zero_is_ok && result_is_ok); + } + + for(auto i = static_cast(UINT8_C(0)); i < static_cast(UINT8_C(16)); ++i) + { + static_cast(i); + + const float_type arg_large { ldexp(float_type { 3.14F } * static_cast(dist(gen)), static_cast(static_cast(std::numeric_limits::max_exponent) * 0.8)) }; + + const float_type result_exp_large { exp(arg_large) }; + + const bool result_exp_large_is_ok { isinf(result_exp_large) }; + + BOOST_TEST(result_exp_large_is_ok); + + result_is_ok = (result_exp_large_is_ok && result_is_ok); + } + + for(auto i = static_cast(UINT8_C(0)); i < static_cast(UINT8_C(16)); ++i) + { + static_cast(i); + + const float_type arg_small { ldexp(float_type { -3.14F } * static_cast(dist(gen)), static_cast(static_cast(std::numeric_limits::max_exponent) * 0.8)) }; + + const float_type result_exp_small { exp(arg_small) }; + + const bool result_exp_small_is_ok { (fpclassify(result_exp_small) == FP_ZERO) }; + + BOOST_TEST(result_exp_small_is_ok); + + result_is_ok = (result_exp_small_is_ok && result_is_ok); + } + + for(auto i = static_cast(UINT8_C(0)); i < static_cast(UINT8_C(16)); ++i) + { + static_cast(i); + + const float_type arg_small_scale { float_type {::my_one() / 24 } * static_cast(dist(gen)) }; + + const float_type result_exp_small_scale { exp(arg_small_scale) }; + + using ctrl_type = boost::multiprecision::number, boost::multiprecision::et_off>; + + const ctrl_type result_ctrl { exp(ctrl_type { arg_small_scale }) }; + + bool result_exp_small_scale_is_ok + { + local::is_close_fraction + ( + result_exp_small_scale, + float_type { result_ctrl }, + std::numeric_limits::epsilon() * 512 + ) + }; + + BOOST_TEST(result_exp_small_scale_is_ok); + + result_is_ok = (result_exp_small_scale_is_ok && result_is_ok); + } + + for(auto i = static_cast(UINT8_C(0)); i < static_cast(UINT8_C(32)); ++i) + { + static_cast(i); + + // Create a number with "fuzz", but which will essentially always round to integer-value 1. + const float_type one { 1 }; + + const float_type + arg_near_one + { + ( + one + * static_cast(dist(gen)) + * static_cast(dist(gen)) + * static_cast(dist(gen)) + ) + + float_type { 0.25F } * static_cast(dist(gen)) + }; + + using ctrl_type = boost::multiprecision::number, boost::multiprecision::et_off>; + + const float_type arg_near_one_to_use { static_cast(arg_near_one) }; + + const int arg_n { static_cast(arg_near_one_to_use) }; + + const float_type result_exp_n_pos { exp(float_type { arg_n }) }; + const ctrl_type result_ctrl_pos { exp(ctrl_type { arg_n }) }; + + const float_type result_exp_n_neg { exp(float_type { -arg_n }) }; + const ctrl_type result_ctrl_neg { exp(ctrl_type { -arg_n }) }; + + bool result_exp_n_pos_is_ok + { + local::is_close_fraction + ( + result_exp_n_pos, + float_type { result_ctrl_pos }, + std::numeric_limits::epsilon() * 512 + ) + }; + + bool result_exp_n_neg_is_ok + { + local::is_close_fraction + ( + result_exp_n_neg, + float_type { result_ctrl_neg }, + std::numeric_limits::epsilon() * 512 + ) + }; + + const bool result_exp_n_is_ok = (result_exp_n_pos_is_ok && result_exp_n_neg_is_ok); + + BOOST_TEST(result_exp_n_is_ok); + + result_is_ok = (result_exp_n_is_ok && result_is_ok); + } + + for(auto i = static_cast(UINT8_C(0)); i < static_cast(UINT8_C(16)); ++i) + { + static_cast(i); + + const float_type arg_huge { sqrt((std::numeric_limits::max)()) * static_cast(dist(gen)) }; + + const float_type result_exp_huge { exp(arg_huge) }; + + const bool result_exp_huge_is_ok { (fpclassify(result_exp_huge) == FP_INFINITE) }; + + BOOST_TEST(result_exp_huge_is_ok); + + result_is_ok = (result_exp_huge_is_ok && result_is_ok); + } + + for(auto i = static_cast(UINT8_C(0)); i < static_cast(UINT8_C(16)); ++i) + { + static_cast(i); + + const float_type arg_tiny { -sqrt((std::numeric_limits::max)()) * static_cast(dist(gen)) }; + + const float_type result_exp_tiny { exp(arg_tiny) }; + + const bool result_exp_tiny_is_ok { (fpclassify(result_exp_tiny) == FP_ZERO) }; + + BOOST_TEST(result_exp_tiny_is_ok); + + result_is_ok = (result_exp_tiny_is_ok && result_is_ok); + } + + return result_is_ok; + } + + template + auto test_log_edge() -> bool + { + using float_type = FloatType; + + std::mt19937_64 gen { time_point() }; + + std::uniform_real_distribution + dist + ( + static_cast(1.01L), + static_cast(1.04L) + ); + + auto result_is_ok = true; + + for(auto index = static_cast(UINT8_C(0)); index < static_cast(UINT8_C(8)); ++index) + { + static_cast(index); + + const float_type arg_zero = ::my_zero() * static_cast(dist(gen)); + + const auto log_zero = log(arg_zero); + + const volatile bool result_log_zero_is_ok + { + (fpclassify(arg_zero) == FP_ZERO) + && isinf(log_zero) + && signbit(log_zero) + }; + + BOOST_TEST(result_log_zero_is_ok); + + result_is_ok = (result_log_zero_is_ok && result_is_ok); + } + + for(auto index = static_cast(UINT8_C(0)); index < static_cast(UINT8_C(8)); ++index) + { + static_cast(index); + + float_type arg_one = + static_cast + ( + static_cast(::my_one() * static_cast(dist(gen))) + ); + + const auto log_one = log(arg_one); + + const volatile auto result_log_one_is_ok = ((arg_one == ::my_one()) && (log_one == ::my_zero())); + + BOOST_TEST(result_log_one_is_ok); + + result_is_ok = (result_log_one_is_ok && result_is_ok); + } + + for(auto index = static_cast(UINT8_C(0)); index < static_cast(UINT8_C(8)); ++index) + { + static_cast(index); + + float_type arg_one_minus // LCOV_EXCL_LINE + { + static_cast + ( + -static_cast(::my_one() * static_cast(dist(gen))) + ) + }; + + const auto log_one_minus = log(arg_one_minus); + + const volatile auto result_log_one_minus_is_ok = ((-arg_one_minus == ::my_one()) && isnan(log_one_minus)); + + BOOST_TEST(result_log_one_minus_is_ok); + + result_is_ok = (result_log_one_minus_is_ok && result_is_ok); + } + + for(auto index = static_cast(UINT8_C(0)); index < static_cast(UINT8_C(8)); ++index) + { + static_cast(index); + + const auto log_inf = log(::my_inf() * static_cast(dist(gen))); + + const volatile auto result_log_inf_is_ok = isinf(log_inf); + + BOOST_TEST(result_log_inf_is_ok); + + result_is_ok = (result_log_inf_is_ok && result_is_ok); + } + + for(auto index = static_cast(UINT8_C(0)); index < static_cast(UINT8_C(8)); ++index) + { + static_cast(index); + + const auto log_inf_minus = log(-::my_inf() * static_cast(dist(gen))); + + const volatile auto result_log_inf_minus_is_ok = isnan(log_inf_minus); + + BOOST_TEST(result_log_inf_minus_is_ok); + + result_is_ok = (result_log_inf_minus_is_ok && result_is_ok); + } + + for(auto index = static_cast(UINT8_C(0)); index < static_cast(UINT8_C(8)); ++index) + { + static_cast(index); + + const float_type arg_nan = ::my_nan() * static_cast(dist(gen)); + + const float_type log_nan = log(arg_nan); + + const volatile auto result_log_nan_is_ok = (isnan(arg_nan) && isnan(log_nan)); + + BOOST_TEST(result_log_nan_is_ok); + + result_is_ok = (result_log_nan_is_ok && result_is_ok); + } + + return result_is_ok; + } + +} // namespace local + +auto main() -> int +{ + #if defined(TEST_CPP_DOUBLE_FLOAT) + { + using float_type = boost::multiprecision::cpp_double_float; + + static_cast(local::test_edges()); + static_cast(local::test_edges_extra()); + local::test_sqrt_edge(); + local::test_exp_edge(); + local::test_log_edge(); + } + + { + using float_type = boost::multiprecision::cpp_double_double; + + static_cast(local::test_edges()); + static_cast(local::test_edges_extra()); + local::test_sqrt_edge(); + local::test_exp_edge(); + local::test_log_edge(); + } + + { + using float_type = boost::multiprecision::cpp_double_long_double; + + static_cast(local::test_edges()); + static_cast(local::test_edges_extra()); + local::test_sqrt_edge(); + local::test_exp_edge(); + local::test_log_edge(); + } + #endif + + { + using float_backend_type = boost::multiprecision::cpp_bin_float<106, boost::multiprecision::digit_base_2, void, int, -1021, +1024>; + + using float_type = boost::multiprecision::number; + + static_cast(local::test_edges()); + local::test_sqrt_edge(); + local::test_exp_edge(); + local::test_log_edge(); + } + + return boost::report_errors(); +} + +template auto my_zero() noexcept -> FloatType& { using float_type = FloatType; static float_type val_zero { 0 }; return val_zero; } +template auto my_one () noexcept -> FloatType& { using float_type = FloatType; static float_type val_one { 1 }; return val_one; } +template auto my_inf () noexcept -> FloatType& { using float_type = FloatType; static float_type val_inf { std::numeric_limits::infinity() }; return val_inf; } +template auto my_nan () noexcept -> FloatType& { using float_type = FloatType; static float_type val_nan { std::numeric_limits::quiet_NaN() }; return val_nan; } +template auto my_exp1() noexcept -> FloatType& { using float_type = FloatType; static float_type val_exp1 { "2.718281828459045235360287471352662497757247093699959574966967627724076630353547594571382178525166427427466391932003059921817413596629043572900334295260595630738132328627943490763233829880753195251019011573834187930702154089149934884167509244761460668082265" }; return val_exp1; }