// Copyright (c) 2026 Progmasoft. // SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1 #pragma once #include #include #include #include #include #include #include #include namespace Progmasoft::Catch3 { /// The range of Gen::Floats, and what it may produce besides finite /// values inside the range. struct FloatOptions final { std::optional Minimum = std::nullopt; std::optional Maximum = std::nullopt; bool AllowNaN = false; bool AllowInfinity = false; bool AllowNegativeZero = true; }; } // namespace Progmasoft::Catch3 namespace Progmasoft::Catch3::Gen { /// Integers in [minimum, maximum]. They shrink towards the value of the /// range closest to zero; generation favours small magnitudes, zero and /// the bounds, scaled by the trial size. template = 0> [[nodiscard]] Strategy Integers(IntegerType minimum = std::numeric_limits::lowest(), IntegerType maximum = std::numeric_limits::max()) { static_assert(sizeof(IntegerType) <= sizeof(std::uint64_t)); if (minimum > maximum) { Detail::RejectInvalidArgument( "Integers requires minimum <= maximum"); } return Strategy( [minimum, maximum]( ChoiceSource &source) -> std::optional { // Magnitudes are computed in 64 bits; the casts are exact // because every magnitude lies inside the requested range. if (minimum >= 0) { const auto width = static_cast(maximum) - static_cast(minimum); return static_cast( static_cast(minimum) + source.DrawBiased(width)); } if (maximum <= 0) { // Two's complement arithmetic in 64 bits: maximum minus // minimum may not fit a signed 64-bit integer. const auto width = static_cast( static_cast(maximum)) - static_cast( static_cast(minimum)); const auto value = static_cast( static_cast(maximum)) - source.DrawBiased(width); return static_cast( static_cast(value)); } // The range crosses zero: a sign, then a magnitude. Choice 0 // is non-negative, so values shrink to 0, never to minimum. if (source.Draw(1) == 0) { return static_cast(source.DrawBiased( static_cast(maximum))); } // -(minimum + 1) cannot overflow, and the magnitude of -1 is // choice 0. const auto negativeWidth = static_cast( -(static_cast(minimum) + 1)); const auto magnitude = source.DrawBiased(negativeWidth); return static_cast( -1 - static_cast(magnitude)); }); } /// true or false; shrinks towards false. [[nodiscard]] inline Strategy Booleans() { return Strategy( [](ChoiceSource &source) -> std::optional { return source.Draw(1) == 1; }); } /// Floating-point values. They shrink towards zero, or the bound /// closest to it, then towards integral values and short fractions. /// Generation mixes integral values, short binary fractions, uniform /// values of the range, arbitrary bit patterns, and edge values such as /// the smallest subnormal and the largest finite value. template, int> = 0> [[nodiscard]] Strategy Floats(FloatOptions options = {}) { constexpr double kLargest = std::numeric_limits::max(); const double minimum = options.Minimum.value_or(-kLargest); const double maximum = options.Maximum.value_or(kLargest); if (!(minimum <= maximum) || std::isnan(minimum) || std::isnan(maximum)) { Detail::RejectInvalidArgument( "Floats requires minimum <= maximum, neither NaN"); } return Strategy( [options, minimum, maximum]( ChoiceSource &source) -> std::optional { const auto inRange = [&](double value) { return value >= minimum && value <= maximum; }; const double anchor = inRange(0.0) ? 0.0 : (std::abs(minimum) < std::abs(maximum) ? minimum : maximum); const auto finish = [&](double value) -> FloatType { if (!inRange(value)) { value = anchor; } return static_cast(value); }; // The kind is the first choice: 0 is the anchor itself, and // each later kind is less simple than the one before. const std::uint64_t kinds = 5U + (options.AllowInfinity ? 1U : 0U) + (options.AllowNaN ? 1U : 0U); const std::uint64_t kind = source.Draw(kinds); const auto sign = [&]() { return source.Draw(1) == 0 ? 1.0 : -1.0; }; switch (kind) { case 0: return finish(anchor); case 1: // Integral values near zero. return finish(sign() * static_cast( source.DrawBiased(1ULL << 53U))); case 2: { // Short binary fractions such as 2.75. const double whole = static_cast( source.DrawBiased(1ULL << 20U)); const double fraction = static_cast(source.Draw(255)) / 256.0; return finish(sign() * (whole + fraction)); } case 3: { // Uniform inside the range; finite ranges only. const double unit = static_cast(source.Draw( (1ULL << 53U) - 1U)) * 0x1.0p-53; const double value = minimum * (1.0 - unit) + maximum * unit; return finish(std::isfinite(value) ? value : anchor); } case 4: { static constexpr std::array kEdges = { -0.0, std::numeric_limits::denorm_min(), std::numeric_limits::min(), std::numeric_limits::epsilon(), 1.0, 0.5, static_cast( std::numeric_limits::max()), static_cast( std::numeric_limits::lowest()), static_cast( std::numeric_limits::min()), }; const double edge = kEdges[static_cast( source.Draw(kEdges.size() - 1U))]; const double signedEdge = sign() * edge; if (!options.AllowNegativeZero && signedEdge == 0.0 && std::signbit(signedEdge)) { return finish(0.0); } return finish(signedEdge); } case 5: { // Any bit pattern; non-finite patterns fall back. const auto bits = source.Draw(UINT64_MAX); static_assert(sizeof(double) == sizeof(bits)); double value{}; std::memcpy(&value, &bits, sizeof(value)); return finish(std::isfinite(value) ? value : anchor); } default: break; } const bool wantsInfinity = options.AllowInfinity && kind == 6U; if (wantsInfinity) { return static_cast( sign() * std::numeric_limits::infinity()); } return std::numeric_limits::quiet_NaN(); }); } } // namespace Progmasoft::Catch3::Gen