mirror of
				https://github.com/catchorg/Catch2.git
				synced 2025-10-31 20:27:11 +01:00 
			
		
		
		
	Add implementation of helpers for uniform float distribution
Specifically we add * `gamma(a, b)`, which returns the magnitude of largest 1-ULP step in range [a, b]. * `count_equidistant_float(a, b, distance)`, which returns the number of equi-distant floats in range [a, b].
This commit is contained in:
		| @@ -109,6 +109,7 @@ set(IMPL_HEADERS | |||||||
|   ${SOURCES_DIR}/internal/catch_polyfills.hpp |   ${SOURCES_DIR}/internal/catch_polyfills.hpp | ||||||
|   ${SOURCES_DIR}/internal/catch_preprocessor.hpp |   ${SOURCES_DIR}/internal/catch_preprocessor.hpp | ||||||
|   ${SOURCES_DIR}/internal/catch_preprocessor_remove_parens.hpp |   ${SOURCES_DIR}/internal/catch_preprocessor_remove_parens.hpp | ||||||
|  |   ${SOURCES_DIR}/internal/catch_random_floating_point_helpers.hpp | ||||||
|   ${SOURCES_DIR}/internal/catch_random_number_generator.hpp |   ${SOURCES_DIR}/internal/catch_random_number_generator.hpp | ||||||
|   ${SOURCES_DIR}/internal/catch_random_seed_generation.hpp |   ${SOURCES_DIR}/internal/catch_random_seed_generation.hpp | ||||||
|   ${SOURCES_DIR}/internal/catch_reporter_registry.hpp |   ${SOURCES_DIR}/internal/catch_reporter_registry.hpp | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ | |||||||
| #include <catch2/benchmark/detail/catch_stats.hpp> | #include <catch2/benchmark/detail/catch_stats.hpp> | ||||||
|  |  | ||||||
| #include <catch2/internal/catch_compiler_capabilities.hpp> | #include <catch2/internal/catch_compiler_capabilities.hpp> | ||||||
|  | #include <catch2/internal/catch_floating_point_helpers.hpp> | ||||||
|  |  | ||||||
| #include <algorithm> | #include <algorithm> | ||||||
| #include <cassert> | #include <cassert> | ||||||
| @@ -184,20 +185,6 @@ namespace Catch { | |||||||
|                     return std::sqrt( variance ); |                     return std::sqrt( variance ); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
| #if defined( __GNUC__ ) || defined( __clang__ ) |  | ||||||
| #    pragma GCC diagnostic push |  | ||||||
| #    pragma GCC diagnostic ignored "-Wfloat-equal" |  | ||||||
| #endif |  | ||||||
|                 // Used when we know we want == comparison of two doubles |  | ||||||
|                 // to centralize warning suppression |  | ||||||
|                 static bool directCompare( double lhs, double rhs ) { |  | ||||||
|                     return lhs == rhs; |  | ||||||
|                 } |  | ||||||
| #if defined( __GNUC__ ) || defined( __clang__ ) |  | ||||||
| #    pragma GCC diagnostic pop |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
|  |  | ||||||
|                 static sample jackknife( double ( *estimator )( double const*, |                 static sample jackknife( double ( *estimator )( double const*, | ||||||
|                                                                 double const* ), |                                                                 double const* ), | ||||||
|                                          double* first, |                                          double* first, | ||||||
| @@ -234,7 +221,7 @@ namespace Catch { | |||||||
|                 double g = idx - j; |                 double g = idx - j; | ||||||
|                 std::nth_element(first, first + j, last); |                 std::nth_element(first, first + j, last); | ||||||
|                 auto xj = first[j]; |                 auto xj = first[j]; | ||||||
|                 if ( directCompare( g, 0 ) ) { |                 if ( Catch::Detail::directCompare( g, 0 ) ) { | ||||||
|                     return xj; |                     return xj; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
| @@ -338,7 +325,7 @@ namespace Catch { | |||||||
|                                    [point]( double x ) { return x < point; } ) / |                                    [point]( double x ) { return x < point; } ) / | ||||||
|                     static_cast<double>( n ); |                     static_cast<double>( n ); | ||||||
|                 // degenerate case with uniform samples |                 // degenerate case with uniform samples | ||||||
|                 if ( directCompare( prob_n, 0. ) ) { |                 if ( Catch::Detail::directCompare( prob_n, 0. ) ) { | ||||||
|                     return { point, point, point, confidence_level }; |                     return { point, point, point, confidence_level }; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -91,6 +91,7 @@ | |||||||
| #include <catch2/internal/catch_preprocessor.hpp> | #include <catch2/internal/catch_preprocessor.hpp> | ||||||
| #include <catch2/internal/catch_preprocessor_internal_stringify.hpp> | #include <catch2/internal/catch_preprocessor_internal_stringify.hpp> | ||||||
| #include <catch2/internal/catch_preprocessor_remove_parens.hpp> | #include <catch2/internal/catch_preprocessor_remove_parens.hpp> | ||||||
|  | #include <catch2/internal/catch_random_floating_point_helpers.hpp> | ||||||
| #include <catch2/internal/catch_random_number_generator.hpp> | #include <catch2/internal/catch_random_number_generator.hpp> | ||||||
| #include <catch2/internal/catch_random_seed_generation.hpp> | #include <catch2/internal/catch_random_seed_generation.hpp> | ||||||
| #include <catch2/internal/catch_reporter_registry.hpp> | #include <catch2/internal/catch_reporter_registry.hpp> | ||||||
|   | |||||||
| @@ -27,6 +27,17 @@ namespace Catch { | |||||||
|             return i; |             return i; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | #if defined( __GNUC__ ) || defined( __clang__ ) | ||||||
|  | #    pragma GCC diagnostic push | ||||||
|  | #    pragma GCC diagnostic ignored "-Wfloat-equal" | ||||||
|  | #endif | ||||||
|  |         bool directCompare( float lhs, float rhs ) { return lhs == rhs; } | ||||||
|  |         bool directCompare( double lhs, double rhs ) { return lhs == rhs; } | ||||||
|  | #if defined( __GNUC__ ) || defined( __clang__ ) | ||||||
|  | #    pragma GCC diagnostic pop | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |  | ||||||
|     } // end namespace Detail |     } // end namespace Detail | ||||||
| } // end namespace Catch | } // end namespace Catch | ||||||
|  |  | ||||||
|   | |||||||
| @@ -22,6 +22,11 @@ namespace Catch { | |||||||
|         uint32_t convertToBits(float f); |         uint32_t convertToBits(float f); | ||||||
|         uint64_t convertToBits(double d); |         uint64_t convertToBits(double d); | ||||||
|  |  | ||||||
|  |         // Used when we know we want == comparison of two doubles | ||||||
|  |         // to centralize warning suppression | ||||||
|  |         bool directCompare( float lhs, float rhs ); | ||||||
|  |         bool directCompare( double lhs, double rhs ); | ||||||
|  |  | ||||||
|     } // end namespace Detail |     } // end namespace Detail | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										94
									
								
								src/catch2/internal/catch_random_floating_point_helpers.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								src/catch2/internal/catch_random_floating_point_helpers.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | |||||||
|  |  | ||||||
|  | //              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_FLOATING_POINT_HELPERS_HPP_INCLUDED | ||||||
|  | #define CATCH_RANDOM_FLOATING_POINT_HELPERS_HPP_INCLUDED | ||||||
|  |  | ||||||
|  | #include <catch2/internal/catch_polyfills.hpp> | ||||||
|  |  | ||||||
|  | #include <cassert> | ||||||
|  | #include <cmath> | ||||||
|  | #include <cstdint> | ||||||
|  | #include <limits> | ||||||
|  | #include <type_traits> | ||||||
|  |  | ||||||
|  | namespace Catch { | ||||||
|  |  | ||||||
|  |     namespace Detail { | ||||||
|  |         /** | ||||||
|  |          * Returns the largest magnitude of 1-ULP distance inside the [a, b] range. | ||||||
|  |          * | ||||||
|  |          * Assumes `a < b`. | ||||||
|  |          */ | ||||||
|  |         template <typename FloatType> | ||||||
|  |         FloatType gamma(FloatType a, FloatType b) { | ||||||
|  |             static_assert( std::is_floating_point<FloatType>::value, | ||||||
|  |                            "gamma returns the largest ULP magnitude within " | ||||||
|  |                            "floating point range [a, b]. This only makes sense " | ||||||
|  |                            "for floating point types" ); | ||||||
|  |             assert( a < b ); | ||||||
|  |  | ||||||
|  |             const auto gamma_up = Catch::nextafter( a, std::numeric_limits<FloatType>::infinity() ) - a; | ||||||
|  |             const auto gamma_down = b - Catch::nextafter( b, -std::numeric_limits<FloatType>::infinity() ); | ||||||
|  |  | ||||||
|  |             return gamma_up < gamma_down ? gamma_down : gamma_up; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         template <typename FloatingPoint> | ||||||
|  |         struct DistanceTypePicker; | ||||||
|  |         template <> | ||||||
|  |         struct DistanceTypePicker<float> { | ||||||
|  |             using type = std::uint32_t; | ||||||
|  |         }; | ||||||
|  |         template <> | ||||||
|  |         struct DistanceTypePicker<double> { | ||||||
|  |             using type = std::uint64_t; | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         template <typename T> | ||||||
|  |         using DistanceType = typename DistanceTypePicker<T>::type; | ||||||
|  |  | ||||||
|  | #if defined( __GNUC__ ) || defined( __clang__ ) | ||||||
|  | #    pragma GCC diagnostic push | ||||||
|  | #    pragma GCC diagnostic ignored "-Wfloat-equal" | ||||||
|  | #endif | ||||||
|  |         /** | ||||||
|  |          * Computes the number of equi-distant floats in [a, b] | ||||||
|  |          * | ||||||
|  |          * Since not every range can be split into equidistant floats | ||||||
|  |          * exactly, we actually compute ceil(b/distance - a/distance), | ||||||
|  |          * because in those cases we want to overcount. | ||||||
|  |          * | ||||||
|  |          * Uses modified Dekker's FastTwoSum algorithm to handle rounding. | ||||||
|  |          */ | ||||||
|  |         template <typename FloatType> | ||||||
|  |         DistanceType<FloatType> | ||||||
|  |         count_equidistant_floats( FloatType a, FloatType b, FloatType distance ) { | ||||||
|  |             assert( a < b ); | ||||||
|  |             // We get distance as gamma for our uniform float distribution, | ||||||
|  |             // so this will round perfectly. | ||||||
|  |             const auto ag = a / distance; | ||||||
|  |             const auto bg = b / distance; | ||||||
|  |  | ||||||
|  |             const auto s = bg - ag; | ||||||
|  |             const auto err = ( std::fabs( a ) <= std::fabs( b ) ) | ||||||
|  |                                  ? -ag - ( s - bg ) | ||||||
|  |                                  : bg - ( s + ag ); | ||||||
|  |             const auto ceil_s = static_cast<DistanceType<FloatType>>( std::ceil( s ) ); | ||||||
|  |  | ||||||
|  |             return ( ceil_s != s ) ? ceil_s : ceil_s + ( err > 0 ); | ||||||
|  |         } | ||||||
|  | #if defined( __GNUC__ ) || defined( __clang__ ) | ||||||
|  | #    pragma GCC diagnostic pop | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } // end namespace Catch | ||||||
|  |  | ||||||
|  | #endif // CATCH_RANDOM_FLOATING_POINT_HELPERS_HPP_INCLUDED | ||||||
| @@ -115,6 +115,7 @@ internal_headers = [ | |||||||
|   'internal/catch_preprocessor.hpp', |   'internal/catch_preprocessor.hpp', | ||||||
|   'internal/catch_preprocessor_internal_stringify.hpp', |   'internal/catch_preprocessor_internal_stringify.hpp', | ||||||
|   'internal/catch_preprocessor_remove_parens.hpp', |   'internal/catch_preprocessor_remove_parens.hpp', | ||||||
|  |   'internal/catch_random_floating_point_helpers.hpp', | ||||||
|   'internal/catch_random_number_generator.hpp', |   'internal/catch_random_number_generator.hpp', | ||||||
|   'internal/catch_random_seed_generation.hpp', |   'internal/catch_random_seed_generation.hpp', | ||||||
|   'internal/catch_reporter_registry.hpp', |   'internal/catch_reporter_registry.hpp', | ||||||
|   | |||||||
| @@ -9,7 +9,9 @@ | |||||||
| #include <catch2/catch_test_macros.hpp> | #include <catch2/catch_test_macros.hpp> | ||||||
| #include <catch2/catch_template_test_macros.hpp> | #include <catch2/catch_template_test_macros.hpp> | ||||||
| #include <catch2/internal/catch_floating_point_helpers.hpp> | #include <catch2/internal/catch_floating_point_helpers.hpp> | ||||||
|  | #include <catch2/internal/catch_random_floating_point_helpers.hpp> | ||||||
|  |  | ||||||
|  | #include <limits> | ||||||
|  |  | ||||||
| TEST_CASE("convertToBits", "[floating-point][conversion]") { | TEST_CASE("convertToBits", "[floating-point][conversion]") { | ||||||
|     using Catch::Detail::convertToBits; |     using Catch::Detail::convertToBits; | ||||||
| @@ -72,3 +74,60 @@ TEST_CASE("UlpDistance", "[floating-point][ulp][approvals]") { | |||||||
|     CHECK( ulpDistance( 1.f, 2.f ) == 0x80'00'00 ); |     CHECK( ulpDistance( 1.f, 2.f ) == 0x80'00'00 ); | ||||||
|     CHECK( ulpDistance( -2.f, 2.f ) == 0x80'00'00'00 ); |     CHECK( ulpDistance( -2.f, 2.f ) == 0x80'00'00'00 ); | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | TEMPLATE_TEST_CASE("gamma", "[approvals][floating-point][ulp][gamma]", float, double) { | ||||||
|  |     using Catch::Detail::gamma; | ||||||
|  |     using Catch::Detail::directCompare; | ||||||
|  |  | ||||||
|  |     // We need to butcher the equal tests with the directCompare helper, | ||||||
|  |     // because the Wfloat-equal triggers in decomposer rather than here, | ||||||
|  |     // so we cannot locally disable it. Goddamn GCC. | ||||||
|  |     CHECK( directCompare( gamma( TestType( -1. ), TestType( 1. ) ), | ||||||
|  |                           gamma( TestType( 0.2332 ), TestType( 1.0 ) ) ) ); | ||||||
|  |     CHECK( directCompare( gamma( TestType( -2. ), TestType( 0 ) ), | ||||||
|  |                           gamma( TestType( 1. ), TestType( 1.5 ) ) ) ); | ||||||
|  |     CHECK( gamma( TestType( 0. ), TestType( 1.0 ) ) < | ||||||
|  |            gamma( TestType( 1.0 ), TestType( 1.5 ) ) ); | ||||||
|  |     CHECK( gamma( TestType( 0 ), TestType( 1. ) ) < | ||||||
|  |            std::numeric_limits<TestType>::epsilon() ); | ||||||
|  |     CHECK( gamma( TestType( -1. ), TestType( -0. ) ) < | ||||||
|  |            std::numeric_limits<TestType>::epsilon() ); | ||||||
|  |     CHECK( directCompare( gamma( TestType( 1. ), TestType( 2. ) ), | ||||||
|  |                           std::numeric_limits<TestType>::epsilon() ) ); | ||||||
|  |     CHECK( directCompare( gamma( TestType( -2. ), TestType( -1. ) ), | ||||||
|  |                           std::numeric_limits<TestType>::epsilon() ) ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | TEMPLATE_TEST_CASE("count_equidistant_floats", | ||||||
|  |                    "[approvals][floating-point][distance]", | ||||||
|  |                    float, | ||||||
|  |                    double) { | ||||||
|  |     using Catch::Detail::count_equidistant_floats; | ||||||
|  |     auto count_steps = []( TestType a, TestType b ) { | ||||||
|  |         return count_equidistant_floats( a, b, Catch::Detail::gamma( a, b ) ); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     CHECK( count_steps( TestType( -1. ), TestType( 1. ) ) == | ||||||
|  |            2 * count_steps( TestType( 0. ), TestType( 1. ) ) ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | TEST_CASE( "count_equidistant_floats", | ||||||
|  |            "[approvals][floating-point][distance]" ) { | ||||||
|  |     using Catch::Detail::count_equidistant_floats; | ||||||
|  |     auto count_floats_with_scaled_ulp = []( auto a, auto b ) { | ||||||
|  |         return count_equidistant_floats( a, b, Catch::Detail::gamma( a, b ) ); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     CHECK( count_floats_with_scaled_ulp( 1., 1.5 ) == 1ull << 51 ); | ||||||
|  |     CHECK( count_floats_with_scaled_ulp( 1.25, 1.5 ) == 1ull << 50 ); | ||||||
|  |     CHECK( count_floats_with_scaled_ulp( 1.f, 1.5f ) == 1 << 22 ); | ||||||
|  |  | ||||||
|  |     STATIC_REQUIRE( std::is_same<std::uint64_t, | ||||||
|  |                                  decltype( count_floats_with_scaled_ulp( | ||||||
|  |                                      0., 1. ) )>::value ); | ||||||
|  |     STATIC_REQUIRE( std::is_same<std::uint32_t, | ||||||
|  |                                  decltype( count_floats_with_scaled_ulp( | ||||||
|  |                                      0.f, 1.f ) )>::value ); | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Martin Hořeňovský
					Martin Hořeňovský