mirror of
https://github.com/ETLCPP/etl.git
synced 2026-10-28 18:07:10 +08:00
Support runtime floating-point clamped_value before C++20 (#1595)
This commit is contained in:
parent
2696828f27
commit
ffea27142a
@ -10,26 +10,27 @@ title: "clamped_value"
|
||||
Provides a value constrained between a minimum and maximum value. Values are
|
||||
clamped using `etl::clamp`. The type supports compile-time bounds to minimise
|
||||
storage and runtime bounds when the range must be changed. Integral types are
|
||||
supported in all language modes. Floating-point types are supported in C++20
|
||||
when `ETL_HAS_FLOATING_POINT_CLAMPED_VALUE` is `1`. By default, the macro is
|
||||
enabled when `__cpp_nontype_template_args` is at least `201911L`.
|
||||
supported in all language modes. Floating-point types with runtime bounds are
|
||||
supported in C++11 and later when `ETL_HAS_FLOATING_POINT_CLAMPED_VALUE` is
|
||||
`1`. Floating-point compile-time bounds require C++20 support for
|
||||
floating-point non-type template arguments.
|
||||
|
||||
## Availability
|
||||
|
||||
`ETL_HAS_FLOATING_POINT_CLAMPED_VALUE` is always defined by this header as
|
||||
either `0` or `1`. It defaults to `1` only when C++20 is enabled and the
|
||||
compiler supports floating-point non-type template arguments. A platform
|
||||
profile or build may define the macro before including the header to override
|
||||
automatic detection.
|
||||
either `0` or `1`. It defaults to `1` in C++11 and later. The related
|
||||
`ETL_HAS_COMPILE_TIME_FLOATING_POINT_CLAMPED_VALUE` macro defaults to `1` only
|
||||
when C++20 is enabled and the compiler supports floating-point non-type
|
||||
template arguments. A platform profile or build may define either macro before
|
||||
including the header to override automatic detection.
|
||||
|
||||
When the macro is `0`, `T` must be an integral type. When it is `1`, `float`,
|
||||
`double`, and `long double` may also be used.
|
||||
When `ETL_HAS_FLOATING_POINT_CLAMPED_VALUE` is `0`, `T` must be an integral
|
||||
type. When it is `1`, `float`, `double`, and `long double` may also be used with
|
||||
runtime bounds.
|
||||
|
||||
```cpp
|
||||
template <typename T,
|
||||
T Min = T(),
|
||||
T Max = T(),
|
||||
bool RuntimeSpecialisation = ((Min == T()) && (Max == T()))>
|
||||
// C++11 and later
|
||||
template <typename T, T... Limits>
|
||||
class clamped_value;
|
||||
```
|
||||
|
||||
@ -39,8 +40,11 @@ etl::clamped_value<int, 2, 7> value_ct; // Fixed range [2, 7].
|
||||
etl::clamped_value<int> value_rt(2, 7, 5); // Runtime range and initial value.
|
||||
|
||||
#if ETL_HAS_FLOATING_POINT_CLAMPED_VALUE
|
||||
etl::clamped_value<double> float_rt(-1.5, 2.5, 0.5);
|
||||
#endif
|
||||
|
||||
#if ETL_HAS_COMPILE_TIME_FLOATING_POINT_CLAMPED_VALUE
|
||||
etl::clamped_value<float, -1.5f, 2.5f> float_ct(0.5f);
|
||||
etl::clamped_value<double> float_rt(-1.5, 2.5, 0.5);
|
||||
#endif
|
||||
```
|
||||
|
||||
@ -67,13 +71,10 @@ clamped_value<int> value(min, max, initial);
|
||||
Creates a runtime clamped value. The two-argument form initializes the value
|
||||
to `min`; the three-argument form clamps `initial` to the range.
|
||||
|
||||
`min` must not be greater than `max`. As with `cyclic_value`, zero-initialized
|
||||
`Min` and `Max` select the runtime-bound specialization; use a non-zero
|
||||
compile-time bound when a fixed compile-time range is needed. NaN bounds and
|
||||
initial values are rejected. Infinite bounds and values are permitted.
|
||||
|
||||
Because zero-initialized `Min` and `Max` select runtime bounds, a fixed
|
||||
compile-time range `[0, 0]` cannot be represented by this dispatch scheme.
|
||||
`min` must not be greater than `max`. Omitting the template bounds selects the
|
||||
runtime-bound specialization. Supplying `Min` and `Max` selects fixed
|
||||
compile-time bounds. NaN bounds and initial values are rejected. Infinite
|
||||
bounds and values are permitted.
|
||||
|
||||
## Modifiers
|
||||
|
||||
|
||||
@ -43,21 +43,32 @@ SOFTWARE.
|
||||
|
||||
///\def ETL_HAS_FLOATING_POINT_CLAMPED_VALUE
|
||||
/// Set to `1` when clamped_value supports floating-point types.
|
||||
/// Floating-point support requires C++20 non-type template arguments.
|
||||
/// Runtime floating-point support requires C++11 variadic templates.
|
||||
/// The macro may be defined by the build or platform profile to override
|
||||
/// automatic detection.
|
||||
#if !defined(ETL_HAS_FLOATING_POINT_CLAMPED_VALUE)
|
||||
#if ETL_USING_CPP20 && defined(__cpp_nontype_template_args) && (__cpp_nontype_template_args >= 201911L)
|
||||
#if ETL_USING_CPP11
|
||||
#define ETL_HAS_FLOATING_POINT_CLAMPED_VALUE 1
|
||||
#else
|
||||
#define ETL_HAS_FLOATING_POINT_CLAMPED_VALUE 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
///\def ETL_HAS_COMPILE_TIME_FLOATING_POINT_CLAMPED_VALUE
|
||||
/// Set to `1` when clamped_value supports floating-point compile-time bounds.
|
||||
#if !defined(ETL_HAS_COMPILE_TIME_FLOATING_POINT_CLAMPED_VALUE)
|
||||
#if ETL_HAS_FLOATING_POINT_CLAMPED_VALUE && ETL_USING_CPP20 && defined(__cpp_nontype_template_args) && (__cpp_nontype_template_args >= 201911L)
|
||||
#define ETL_HAS_COMPILE_TIME_FLOATING_POINT_CLAMPED_VALUE 1
|
||||
#else
|
||||
#define ETL_HAS_COMPILE_TIME_FLOATING_POINT_CLAMPED_VALUE 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
///\defgroup clamped_value clamped_value
|
||||
/// Provides a value that is clamped between two limits.
|
||||
/// Integral types are supported in all language modes. Floating-point types
|
||||
/// are supported when ETL_HAS_FLOATING_POINT_CLAMPED_VALUE is `1`.
|
||||
/// with runtime bounds are supported when
|
||||
/// ETL_HAS_FLOATING_POINT_CLAMPED_VALUE is `1`.
|
||||
/// \ingroup utilities
|
||||
|
||||
namespace etl
|
||||
@ -113,18 +124,18 @@ namespace etl
|
||||
return value;
|
||||
}
|
||||
|
||||
const unsigned_type current = etl::to_unsigned(value); //static_cast<unsigned_type>(value);
|
||||
const unsigned_type current = etl::to_unsigned(value); // static_cast<unsigned_type>(value);
|
||||
const unsigned_type step = etl::absolute_unsigned(n);
|
||||
|
||||
if ((n > 0) && (value < max_value))
|
||||
{
|
||||
const unsigned_type distance = etl::to_unsigned(max_value) - current; //static_cast<unsigned_type>(max_value) - current;
|
||||
const unsigned_type distance = etl::to_unsigned(max_value) - current; // static_cast<unsigned_type>(max_value) - current;
|
||||
|
||||
value = (step >= distance) ? max_value : static_cast<T>(current + step);
|
||||
}
|
||||
else if ((n < 0) && (value > min_value))
|
||||
{
|
||||
const unsigned_type distance = current - etl::to_unsigned(min_value); //static_cast<unsigned_type>(min_value);
|
||||
const unsigned_type distance = current - etl::to_unsigned(min_value); // static_cast<unsigned_type>(min_value);
|
||||
|
||||
value = (step >= distance) ? min_value : static_cast<T>(current - step);
|
||||
}
|
||||
@ -236,22 +247,32 @@ namespace etl
|
||||
} // namespace private_clamped_value
|
||||
/// \endcond
|
||||
|
||||
#include "private/diagnostic_float_equal_push.h"
|
||||
#if ETL_USING_CPP11
|
||||
template <typename T, T... Limits>
|
||||
class clamped_value;
|
||||
#else // ETL_NOT_USING_CPP11
|
||||
#include "private/diagnostic_float_equal_push.h"
|
||||
template <typename T, T Min = T(), T Max = T(), bool RuntimeSpecialisation = ((Min == T()) && (Max == T()))>
|
||||
class clamped_value;
|
||||
#include "private/diagnostic_pop.h"
|
||||
#include "private/diagnostic_pop.h"
|
||||
#endif
|
||||
|
||||
//***************************************************************************
|
||||
/// Provides a value that is clamped between two compile-time limits.
|
||||
/// Supports incrementing, decrementing and arbitrary advance.
|
||||
///@tparam T An integral type, or a floating-point type when
|
||||
/// ETL_HAS_FLOATING_POINT_CLAMPED_VALUE is `1`.
|
||||
/// ETL_HAS_COMPILE_TIME_FLOATING_POINT_CLAMPED_VALUE is `1`.
|
||||
///@tparam Min The minimum value of the range.
|
||||
///@tparam Max The maximum value of the range.
|
||||
///\ingroup clamped_value
|
||||
//***************************************************************************
|
||||
#if ETL_USING_CPP11
|
||||
template <typename T, T Min, T Max>
|
||||
class clamped_value<T, Min, Max>
|
||||
#else // ETL_NOT_USING_CPP11
|
||||
template <typename T, T Min, T Max>
|
||||
class clamped_value<T, Min, Max, false>
|
||||
#endif
|
||||
{
|
||||
public:
|
||||
|
||||
@ -583,8 +604,13 @@ namespace etl
|
||||
///@tparam Max Ignored for this specialisation.
|
||||
///\ingroup clamped_value
|
||||
//***************************************************************************
|
||||
#if ETL_USING_CPP11
|
||||
template <typename T>
|
||||
class clamped_value<T>
|
||||
#else // ETL_NOT_USING_CPP11
|
||||
template <typename T, T Min, T Max>
|
||||
class clamped_value<T, Min, Max, true>
|
||||
#endif
|
||||
{
|
||||
public:
|
||||
|
||||
|
||||
@ -31,6 +31,9 @@ SOFTWARE.
|
||||
#include <etl/clamped_value.h>
|
||||
|
||||
#if ETL_HAS_FLOATING_POINT_CLAMPED_VALUE
|
||||
etl::clamped_value<float, -1.0f, 1.0f> clamped_float_compile_time;
|
||||
etl::clamped_value<double> clamped_double_run_time;
|
||||
etl::clamped_value<double> clamped_double_run_time;
|
||||
#endif
|
||||
|
||||
#if ETL_HAS_COMPILE_TIME_FLOATING_POINT_CLAMPED_VALUE
|
||||
etl::clamped_value<float, -1.0f, 1.0f> clamped_float_compile_time;
|
||||
#endif
|
||||
|
||||
@ -61,6 +61,17 @@ namespace
|
||||
CHECK_EQUAL(7, value.max());
|
||||
}
|
||||
|
||||
#if ETL_USING_CPP11
|
||||
//*************************************************************************
|
||||
TEST(test_compile_time_zero_range)
|
||||
{
|
||||
etl::clamped_value<int, 0, 0> value(1);
|
||||
CHECK_EQUAL(0, value.get());
|
||||
CHECK_EQUAL(0, value.min());
|
||||
CHECK_EQUAL(0, value.max());
|
||||
}
|
||||
#endif
|
||||
|
||||
//*************************************************************************
|
||||
TEST(test_run_time_initialisation)
|
||||
{
|
||||
@ -364,7 +375,7 @@ namespace
|
||||
CHECK_THROW(value.set(7, 2), etl::exception);
|
||||
}
|
||||
|
||||
#if ETL_HAS_FLOATING_POINT_CLAMPED_VALUE
|
||||
#if ETL_HAS_COMPILE_TIME_FLOATING_POINT_CLAMPED_VALUE
|
||||
//*************************************************************************
|
||||
TEST(test_compile_time_floating_clamps_and_advances)
|
||||
{
|
||||
@ -381,7 +392,9 @@ namespace
|
||||
value.advance(10.0f);
|
||||
CHECK_CLOSE(2.5f, value.get(), 0.0001f);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ETL_HAS_FLOATING_POINT_CLAMPED_VALUE
|
||||
//*************************************************************************
|
||||
TEST(test_runtime_floating_clamps_and_advances)
|
||||
{
|
||||
@ -396,45 +409,52 @@ namespace
|
||||
value.advance(-10.0);
|
||||
CHECK_CLOSE(-1.5, value.get(), 0.0001);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ETL_HAS_COMPILE_TIME_FLOATING_POINT_CLAMPED_VALUE
|
||||
//*************************************************************************
|
||||
TEST(test_floating_increment_and_decrement_saturate_at_fractional_bounds)
|
||||
TEST(test_compile_time_floating_increment_saturates_at_fractional_bound)
|
||||
{
|
||||
etl::clamped_value<float, -0.5f, 0.5f> compile_time(0.25f);
|
||||
etl::clamped_value<float> run_time(-0.5f, 0.5f, -0.25f);
|
||||
|
||||
++compile_time;
|
||||
--run_time;
|
||||
|
||||
CHECK_CLOSE(0.5f, compile_time.get(), 0.0001f);
|
||||
CHECK_CLOSE(-0.5f, run_time.get(), 0.0001f);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ETL_HAS_FLOATING_POINT_CLAMPED_VALUE
|
||||
//*************************************************************************
|
||||
TEST(test_runtime_floating_decrement_saturates_at_fractional_bound)
|
||||
{
|
||||
etl::clamped_value<float> value(-0.5f, 0.5f, -0.25f);
|
||||
|
||||
--value;
|
||||
|
||||
CHECK_CLOSE(-0.5f, value.get(), 0.0001f);
|
||||
}
|
||||
|
||||
//*************************************************************************
|
||||
TEST(test_floating_comparison_and_swap)
|
||||
TEST(test_runtime_floating_comparison_and_swap)
|
||||
{
|
||||
etl::clamped_value<double, -2.0, 2.0> compile_time1(-0.5);
|
||||
etl::clamped_value<double, -2.0, 2.0> compile_time2(1.5);
|
||||
etl::clamped_value<double> run_time1(-3.0, 3.0, -1.0);
|
||||
etl::clamped_value<double> run_time2(4.0, 8.0, 6.0);
|
||||
etl::clamped_value<double> value1(-3.0, 3.0, -1.0);
|
||||
etl::clamped_value<double> value2(4.0, 8.0, 6.0);
|
||||
|
||||
CHECK(compile_time1 < compile_time2);
|
||||
CHECK(compile_time1 < 1.0);
|
||||
swap(compile_time1, compile_time2);
|
||||
swap(run_time1, run_time2);
|
||||
CHECK(value1 < value2);
|
||||
CHECK(value1 < 1.0);
|
||||
swap(value1, value2);
|
||||
|
||||
CHECK_CLOSE(1.5, compile_time1.get(), 0.0001);
|
||||
CHECK_CLOSE(6.0, run_time1.get(), 0.0001);
|
||||
CHECK_CLOSE(4.0, run_time1.min(), 0.0001);
|
||||
CHECK_CLOSE(-1.0, run_time2.get(), 0.0001);
|
||||
CHECK_CLOSE(6.0, value1.get(), 0.0001);
|
||||
CHECK_CLOSE(4.0, value1.min(), 0.0001);
|
||||
CHECK_CLOSE(-1.0, value2.get(), 0.0001);
|
||||
}
|
||||
|
||||
//*************************************************************************
|
||||
TEST(test_floating_infinity_saturates)
|
||||
TEST(test_runtime_floating_infinity_saturates)
|
||||
{
|
||||
const double infinity = etl::numeric_limits<double>::infinity();
|
||||
etl::clamped_value<double, -2.0, 2.0> value(0.0);
|
||||
etl::clamped_value<double> infinite_range(-infinity, infinity, 0.0);
|
||||
const double infinity = etl::numeric_limits<double>::infinity();
|
||||
etl::clamped_value<double> value(-2.0, 2.0, 0.0);
|
||||
etl::clamped_value<double> infinite_range(-infinity, infinity, 0.0);
|
||||
|
||||
value += infinity;
|
||||
CHECK_CLOSE(2.0, value.get(), 0.0001);
|
||||
@ -501,7 +521,7 @@ namespace
|
||||
CHECK(true);
|
||||
}
|
||||
|
||||
#if ETL_HAS_FLOATING_POINT_CLAMPED_VALUE && (ETL_USING_MICROSOFT_COMPILER && (_MSC_VER >= 1950))
|
||||
#if ETL_HAS_COMPILE_TIME_FLOATING_POINT_CLAMPED_VALUE && (ETL_USING_MICROSOFT_COMPILER && (_MSC_VER >= 1950))
|
||||
//*************************************************************************
|
||||
TEST(test_floating_clamped_value_constexpr)
|
||||
{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user