Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorMartin Hořeňovský2023-11-28 23:26:06 +0100
committerMartin Hořeňovský2023-12-10 19:53:36 +0100
commit04a829b0e13401049707c7c70e95c3cc0c73ba22 (patch)
treed98617b090a90a067164899110b84952282e1f08 /src
parentab1b079e4deb159a6f56c9168031876a697a7d2c (diff)
downloadcatch3-04a829b0e13401049707c7c70e95c3cc0c73ba22.tar.gz
Add helpers for implementing uniform integer distribution
* Utility for extended mult n x n bits -> 2n bits * Utility to adapt output from URBG to target (unsigned) integral type * Utility to reorder signed values into unsigned type while keeping the order.
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/catch2/catch_all.hpp1
-rw-r--r--src/catch2/internal/catch_random_integer_helpers.hpp202
-rw-r--r--src/catch2/meson.build1
4 files changed, 205 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 04727505..9fae759c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -110,6 +110,7 @@ set(IMPL_HEADERS
${SOURCES_DIR}/internal/catch_preprocessor.hpp
${SOURCES_DIR}/internal/catch_preprocessor_remove_parens.hpp
${SOURCES_DIR}/internal/catch_random_floating_point_helpers.hpp
+ ${SOURCES_DIR}/internal/catch_random_integer_helpers.hpp
${SOURCES_DIR}/internal/catch_random_number_generator.hpp
${SOURCES_DIR}/internal/catch_random_seed_generation.hpp
${SOURCES_DIR}/internal/catch_reporter_registry.hpp
diff --git a/src/catch2/catch_all.hpp b/src/catch2/catch_all.hpp
index 98a06f1b..dc81d772 100644
--- a/src/catch2/catch_all.hpp
+++ b/src/catch2/catch_all.hpp
@@ -92,6 +92,7 @@
#include <catch2/internal/catch_preprocessor_internal_stringify.hpp>
#include <catch2/internal/catch_preprocessor_remove_parens.hpp>
#include <catch2/internal/catch_random_floating_point_helpers.hpp>
+#include <catch2/internal/catch_random_integer_helpers.hpp>
#include <catch2/internal/catch_random_number_generator.hpp>
#include <catch2/internal/catch_random_seed_generation.hpp>
#include <catch2/internal/catch_reporter_registry.hpp>
diff --git a/src/catch2/internal/catch_random_integer_helpers.hpp b/src/catch2/internal/catch_random_integer_helpers.hpp
new file mode 100644
index 00000000..1c450f05
--- /dev/null
+++ b/src/catch2/internal/catch_random_integer_helpers.hpp
@@ -0,0 +1,202 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#ifndef CATCH_RANDOM_INTEGER_HELPERS_HPP_INCLUDED
+#define CATCH_RANDOM_INTEGER_HELPERS_HPP_INCLUDED
+
+#include <climits>
+#include <cstddef>
+#include <cstdint>
+#include <type_traits>
+
+namespace Catch {
+ namespace Detail {
+
+ template <std::size_t>
+ struct SizedUnsignedType;
+#define SizedUnsignedTypeHelper( TYPE ) \
+ template <> \
+ struct SizedUnsignedType<sizeof( TYPE )> { \
+ using type = TYPE; \
+ }
+
+ SizedUnsignedTypeHelper( std::uint8_t );
+ SizedUnsignedTypeHelper( std::uint16_t );
+ SizedUnsignedTypeHelper( std::uint32_t );
+ SizedUnsignedTypeHelper( std::uint64_t );
+#undef SizedUnsignedTypeHelper
+
+ template <std::size_t sz>
+ using SizedUnsignedType_t = typename SizedUnsignedType<sz>::type;
+
+ template <typename T>
+ using DoubleWidthUnsignedType_t = SizedUnsignedType_t<2 * sizeof( T )>;
+
+ template <typename T>
+ struct ExtendedMultResult {
+ T upper;
+ T lower;
+ friend bool operator==( ExtendedMultResult const& lhs,
+ ExtendedMultResult const& rhs ) {
+ return lhs.upper == rhs.upper && lhs.lower == rhs.lower;
+ }
+ };
+
+ // Returns 128 bit result of multiplying lhs and rhs
+ constexpr ExtendedMultResult<std::uint64_t>
+ extendedMult( std::uint64_t lhs, std::uint64_t rhs ) {
+ // We use the simple long multiplication approach for
+ // correctness, we can use platform specific builtins
+ // for performance later.
+
+ // Split the lhs and rhs into two 32bit "digits", so that we can
+ // do 64 bit arithmetic to handle carry bits.
+ // 32b 32b 32b 32b
+ // lhs L1 L2
+ // * rhs R1 R2
+ // ------------------------
+ // | R2 * L2 |
+ // | R2 * L1 |
+ // | R1 * L2 |
+ // | R1 * L1 |
+ // -------------------------
+ // | a | b | c | d |
+
+#define CarryBits( x ) ( x >> 32 )
+#define Digits( x ) ( x & 0xFF'FF'FF'FF )
+
+ auto r2l2 = Digits( rhs ) * Digits( lhs );
+ auto r2l1 = Digits( rhs ) * CarryBits( lhs );
+ auto r1l2 = CarryBits( rhs ) * Digits( lhs );
+ auto r1l1 = CarryBits( rhs ) * CarryBits( lhs );
+
+ // Sum to columns first
+ auto d = Digits( r2l2 );
+ auto c = CarryBits( r2l2 ) + Digits( r2l1 ) + Digits( r1l2 );
+ auto b = CarryBits( r2l1 ) + CarryBits( r1l2 ) + Digits( r1l1 );
+ auto a = CarryBits( r1l1 );
+
+ // Propagate carries between columns
+ c += CarryBits( d );
+ b += CarryBits( c );
+ a += CarryBits( b );
+
+ // Remove the used carries
+ c = Digits( c );
+ b = Digits( b );
+ a = Digits( a );
+
+#undef CarryBits
+#undef Digits
+
+ return {
+ a << 32 | b, // upper 64 bits
+ c << 32 | d // lower 64 bits
+ };
+ }
+
+ template <typename UInt>
+ constexpr ExtendedMultResult<UInt> extendedMult( UInt lhs, UInt rhs ) {
+ static_assert( std::is_unsigned<UInt>::value,
+ "extendedMult can only handle unsigned integers" );
+ static_assert( sizeof( UInt ) < sizeof( std::uint64_t ),
+ "Generic extendedMult can only handle types smaller "
+ "than uint64_t" );
+ using WideType = DoubleWidthUnsignedType_t<UInt>;
+
+ auto result = WideType( lhs ) * WideType( rhs );
+ return {
+ static_cast<UInt>( result >> ( CHAR_BIT * sizeof( UInt ) ) ),
+ static_cast<UInt>( result & UInt( -1 ) ) };
+ }
+
+
+ template <typename TargetType,
+ typename Generator>
+ std::enable_if_t<sizeof(typename Generator::result_type) >= sizeof(TargetType),
+ TargetType> fillBitsFrom(Generator& gen) {
+ using gresult_type = typename Generator::result_type;
+ static_assert( std::is_unsigned<TargetType>::value, "Only unsigned integers are supported" );
+ static_assert( Generator::min() == 0 &&
+ Generator::max() == static_cast<gresult_type>( -1 ),
+ "Generator must be able to output all numbers in its result type (effectively it must be a random bit generator)" );
+
+ // We want to return the top bits from a generator, as they are
+ // usually considered higher quality.
+ constexpr auto generated_bits = sizeof( gresult_type ) * CHAR_BIT;
+ constexpr auto return_bits = sizeof( TargetType ) * CHAR_BIT;
+
+ return static_cast<TargetType>( gen() >>
+ ( generated_bits - return_bits) );
+ }
+
+ template <typename TargetType,
+ typename Generator>
+ std::enable_if_t<sizeof(typename Generator::result_type) < sizeof(TargetType),
+ TargetType> fillBitsFrom(Generator& gen) {
+ using gresult_type = typename Generator::result_type;
+ static_assert( std::is_unsigned<TargetType>::value,
+ "Only unsigned integers are supported" );
+ static_assert( Generator::min() == 0 &&
+ Generator::max() == static_cast<gresult_type>( -1 ),
+ "Generator must be able to output all numbers in its result type (effectively it must be a random bit generator)" );
+
+ constexpr auto generated_bits = sizeof( gresult_type ) * CHAR_BIT;
+ constexpr auto return_bits = sizeof( TargetType ) * CHAR_BIT;
+ std::size_t filled_bits = 0;
+ TargetType ret = 0;
+ do {
+ ret <<= generated_bits;
+ ret |= gen();
+ filled_bits += generated_bits;
+ } while ( filled_bits < return_bits );
+
+ return ret;
+ }
+
+ /*
+ * Transposes numbers into unsigned type while keeping their ordering
+ *
+ * This means that signed types are changed so that the ordering is
+ * [INT_MIN, ..., -1, 0, ..., INT_MAX], rather than order we would
+ * get by simple casting ([0, ..., INT_MAX, INT_MIN, ..., -1])
+ */
+ template <typename OriginalType, typename UnsignedType>
+ std::enable_if_t<std::is_signed<OriginalType>::value, UnsignedType>
+ transposeToNaturalOrder( UnsignedType in ) {
+ static_assert(
+ sizeof( OriginalType ) == sizeof( UnsignedType ),
+ "reordering requires the same sized types on both sides" );
+ static_assert( std::is_unsigned<UnsignedType>::value,
+ "Input type must be unsigned" );
+ // Assuming 2s complement (standardized in current C++), the
+ // positive and negative numbers are already internally ordered,
+ // and their difference is in the top bit. Swapping it orders
+ // them the desired way.
+ constexpr auto highest_bit =
+ UnsignedType( 1 ) << ( sizeof( UnsignedType ) * CHAR_BIT - 1 );
+ return static_cast<UnsignedType>( in ^ highest_bit );
+ }
+
+
+
+ template <typename OriginalType,
+ typename UnsignedType>
+ std::enable_if_t<std::is_unsigned<OriginalType>::value, UnsignedType>
+ transposeToNaturalOrder(UnsignedType in) {
+ static_assert(
+ sizeof( OriginalType ) == sizeof( UnsignedType ),
+ "reordering requires the same sized types on both sides" );
+ static_assert( std::is_unsigned<UnsignedType>::value, "Input type must be unsigned" );
+ // No reordering is needed for unsigned -> unsigned
+ return in;
+ }
+ } // namespace Detail
+} // namespace Catch
+
+#endif // CATCH_RANDOM_INTEGER_HELPERS_HPP_INCLUDED
diff --git a/src/catch2/meson.build b/src/catch2/meson.build
index a45e3777..e3ab4af8 100644
--- a/src/catch2/meson.build
+++ b/src/catch2/meson.build
@@ -116,6 +116,7 @@ internal_headers = [
'internal/catch_preprocessor_internal_stringify.hpp',
'internal/catch_preprocessor_remove_parens.hpp',
'internal/catch_random_floating_point_helpers.hpp',
+ 'internal/catch_random_integer_helpers.hpp',
'internal/catch_random_number_generator.hpp',
'internal/catch_random_seed_generation.hpp',
'internal/catch_reporter_registry.hpp',