Implement clamped_value utility (#1565)

* feat: add clamped_value utility

* style: format clamped_value include list

* feat: enhance clamped_value with runtime bounds and comparison operators and DRY up the implementation

* refactor: call set in runtime ctor

instead of duplicated the clamping logic

* Add C++20 floating-point clamped_value support

* Document and stress clamped_value edge cases

* Change ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS) to ETL_NOEXCEPT

* Change ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS) to ETL_NOEXCEPT

* Revert changes

---------

Co-authored-by: John Wellbelove <jwellbelove@users.noreply.github.com>
This commit is contained in:
Drew Rife 2026-09-08 05:21:33 -04:00 committed by GitHub
parent ee87ea90aa
commit 11623e6862
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 1695 additions and 2 deletions

View File

@ -0,0 +1,170 @@
---
title: "clamped_value"
---
{{< callout type="info">}}
Header: `clamped_value.h`
Since: `TBC`
{{< /callout >}}
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`.
## 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.
When the macro is `0`, `T` must be an integral type. When it is `1`, `float`,
`double`, and `long double` may also be used.
```cpp
template <typename T,
T Min = T(),
T Max = T(),
bool RuntimeSpecialisation = ((Min == T()) && (Max == T()))>
class clamped_value;
```
```cpp
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<float, -1.5f, 2.5f> float_ct(0.5f);
etl::clamped_value<double> float_rt(-1.5, 2.5, 0.5);
#endif
```
## Types
`difference_type` is the signed counterpart of `T` for integral values. For
floating-point values, `difference_type` is `T`, allowing fractional steps.
`limits_type` and `difference_limits_type` provide the corresponding
`etl::numeric_limits` specializations.
## Constructors
```cpp
clamped_value<int, 2, 7> value;
```
Creates a compile-time clamped value initialized to `Min`.
```cpp
clamped_value<int> value(min, max);
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.
## Modifiers
```cpp
void set(T value);
void set(T min, T max);
void to_min();
void to_max();
void advance(difference_type n) ETL_NOEXCEPT;
```
`set(T)` clamps the current value. The runtime `set(min, max)` changes the
range and resets the value to the new minimum. `to_min()` and `to_max()` move
the value directly to a bound. For integral values, `advance` uses the signed
counterpart of `T` as its step type. For floating-point values, the step type
is `T` and may be fractional. Steps saturate in constant time at the minimum
or maximum rather than wrapping. NaN steps are rejected.
Positive steps move toward `max`; negative steps move toward `min`. Infinite
steps saturate at a finite bound. Infinite values and bounds remain valid.
```cpp
etl::clamped_value<float, -1.0f, 1.0f> value(0.25f);
value.advance(0.5f); // 0.75f
value.advance(10.0f); // 1.0f
value.advance(-0.5f); // 0.5f
```
Increment and decrement move by one and also saturate, including when a
floating-point bound is less than one unit away:
```cpp
++value; // Remains Max when already at Max.
--value; // Remains Min when already at Min.
```
## Access
```cpp
ETL_NODISCARD T get() const noexcept;
ETL_NODISCARD T min() const noexcept;
ETL_NODISCARD T max() const noexcept;
```
Gets the current value and its bounds. Both compile-time and runtime
specializations provide these accessors as const-qualified member functions.
## Operators
```cpp
ETL_NODISCARD operator T() const noexcept;
clamped_value& operator++() & noexcept;
clamped_value operator++(int) noexcept;
clamped_value& operator--() & noexcept;
clamped_value operator--(int) noexcept;
clamped_value& operator=(T value) & noexcept;
clamped_value& operator+=(difference_type n) & noexcept;
clamped_value& operator-=(difference_type n) & noexcept;
```
Conversion and assignment to the underlying type are supported. Assignment is
clamped to the configured range. The lvalue reference qualification on
assignment prevents assignment through temporary values where supported. For
floating-point values, assignment and compound-assignment reject NaN.
Prefix increment and decrement return a reference to the updated object.
Postfix increment and decrement return a copy of the value before it was
updated. All four operations move by one and saturate at the applicable bound.
## Operations
```cpp
ETL_CONSTEXPR14 void swap(clamped_value& other);
ETL_CONSTEXPR14 void swap(clamped_value& lhs, clamped_value& rhs);
```
Swaps clamped values. Runtime values also swap their bounds.
Equality, inequality, and relational comparisons are provided. Comparisons
against arithmetic values use the implicit conversion to `T`. Equality
compares the current values; runtime bounds do not affect equality. Runtime
ranges must satisfy `min <= max`; invalid ranges trigger an ETL assertion. The
default runtime specialization uses the full representable range of `T`.
Floating-point comparisons use the underlying values directly; no approximate
or tolerance-based comparison is applied.
## Error Handling
Runtime ranges with `min > max` trigger an ETL assertion. NaN values, bounds,
assignments, and steps also trigger an ETL assertion. Depending on the ETL
error-handler configuration, an assertion may throw `etl::exception`, invoke a
configured handler, or terminate. Operations that validate input are only
`noexcept` when exceptions are disabled.

956
include/etl/clamped_value.h Normal file
View File

@ -0,0 +1,956 @@
///\file
/******************************************************************************
The MIT License(MIT)
Embedded Template Library.
https://github.com/ETLCPP/etl
https://www.etlcpp.com
Copyright(c) 2026 John Wellbelove
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files(the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions :
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
******************************************************************************/
#ifndef ETL_CLAMPED_VALUE_INCLUDED
#define ETL_CLAMPED_VALUE_INCLUDED
#include "platform.h"
#include "absolute.h"
#include "algorithm.h"
#include "error_handler.h"
#include "limits.h"
#include "math.h"
#include "static_assert.h"
#include "type_traits.h"
#include "utility.h"
///\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.
/// 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)
#define ETL_HAS_FLOATING_POINT_CLAMPED_VALUE 1
#else
#define ETL_HAS_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`.
/// \ingroup utilities
namespace etl
{
/// \cond INTERNAL
namespace private_clamped_value
{
//*************************************************************************
/// Type policy for clamped_value.
/// Integral values use a signed difference type and unsigned saturation
/// arithmetic. Floating-point values use `T` as the difference type.
//*************************************************************************
template <typename T, bool IsIntegral = etl::is_integral<T>::value>
struct traits;
template <typename T>
struct traits<T, true>
{
typedef typename etl::make_signed<T>::type difference_type;
typedef typename etl::make_unsigned<T>::type unsigned_type;
typedef etl::numeric_limits<T> limits_type;
typedef etl::numeric_limits<difference_type> difference_limits_type;
};
template <typename T>
struct traits<T, false>
{
ETL_STATIC_ASSERT(ETL_HAS_FLOATING_POINT_CLAMPED_VALUE&& etl::is_floating_point<T>::value,
"clamped_value requires an integral type, or a floating-point type when supported");
typedef T difference_type;
typedef etl::numeric_limits<T> limits_type;
typedef etl::numeric_limits<T> difference_limits_type;
};
template <typename T, bool IsIntegral = etl::is_integral<T>::value>
struct arithmetic;
//*************************************************************************
/// Saturating arithmetic policy for integral values.
//*************************************************************************
template <typename T>
struct arithmetic<T, true>
{
typedef typename traits<T>::unsigned_type unsigned_type;
ETL_NODISCARD
static ETL_CONSTEXPR14 T advance(T value, T min_value, T max_value, typename traits<T>::difference_type n) ETL_NOEXCEPT
{
if (n == 0)
{
return value;
}
const unsigned_type current = static_cast<unsigned_type>(value);
const unsigned_type step = etl::absolute_unsigned(n);
if ((n > 0) && (value < max_value))
{
const unsigned_type distance = 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 - static_cast<unsigned_type>(min_value);
value = (step >= distance) ? min_value : static_cast<T>(current - step);
}
return value;
}
ETL_NODISCARD
static ETL_CONSTEXPR14 T subtract(T value, T min_value, T max_value, typename traits<T>::difference_type n) ETL_NOEXCEPT
{
if (n == traits<T>::difference_limits_type::lowest()) ETL_UNLIKELY
{
value = advance(value, min_value, max_value, traits<T>::difference_limits_type::max());
return advance(value, min_value, max_value, 1);
}
return advance(value, min_value, max_value, static_cast<typename traits<T>::difference_type>(-n));
}
};
#if ETL_HAS_FLOATING_POINT_CLAMPED_VALUE
//*************************************************************************
/// Saturating arithmetic policy for floating-point values.
//*************************************************************************
template <typename T>
struct arithmetic<T, false>
{
ETL_NODISCARD
static ETL_CONSTEXPR14 T advance(T value, T min_value, T max_value, T n) ETL_NOEXCEPT
{
if ((n > T(0)) && (value < max_value))
{
const T distance = max_value - value;
value = (n >= distance) ? max_value : static_cast<T>(value + n);
}
else if ((n < T(0)) && (value > min_value))
{
const T distance = value - min_value;
value = (-n >= distance) ? min_value : static_cast<T>(value + n);
}
return value;
}
ETL_NODISCARD
static ETL_CONSTEXPR14 T subtract(T value, T min_value, T max_value, T n) ETL_NOEXCEPT
{
return advance(value, min_value, max_value, -n);
}
};
#endif
//*************************************************************************
/// Advances a value and saturates it at the supplied bounds.
///\param value The current value.
///\param min_value The minimum value.
///\param max_value The maximum value.
///\param n The number of steps.
///\return The advanced value.
//*************************************************************************
template <typename T>
ETL_NODISCARD ETL_CONSTEXPR14 T advance(T value, T min_value, T max_value, typename traits<T>::difference_type n) ETL_NOEXCEPT
{
return arithmetic<T>::advance(value, min_value, max_value, n);
}
//*************************************************************************
/// Subtracts a value and saturates it at the supplied bounds.
/// Handles the lowest integral difference without signed overflow.
///\param value The current value.
///\param min_value The minimum value.
///\param max_value The maximum value.
///\param n The number of steps to subtract.
///\return The value after subtraction.
//*************************************************************************
template <typename T>
ETL_NODISCARD ETL_CONSTEXPR14 T subtract(T value, T min_value, T max_value, typename traits<T>::difference_type n) ETL_NOEXCEPT
{
return arithmetic<T>::subtract(value, min_value, max_value, n);
}
//*************************************************************************
/// Verifies that a clamped value, bound, or difference is not NaN.
/// Integral values always pass validation.
///\param value The value to validate.
//*************************************************************************
template <typename T>
ETL_CONSTEXPR14 void validate(T value) ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS)
{
ETL_ASSERT(!etl::is_nan(value), ETL_ERROR_GENERIC("clamped_value: NaN is not supported"));
}
//*************************************************************************
/// Validates a value and its bounds before clamping.
///\param value The value to clamp.
///\param min_value The minimum permitted value.
///\param max_value The maximum permitted value.
///\return The value clamped to the supplied range.
//*************************************************************************
template <typename T>
ETL_NODISCARD ETL_CONSTEXPR14 T validated_clamp(T value, T min_value, T max_value) ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS)
{
validate(value);
validate(min_value);
validate(max_value);
ETL_ASSERT(min_value <= max_value, ETL_ERROR_GENERIC("clamped_value: invalid range"));
return etl::clamp(value, min_value, max_value);
}
} // namespace private_clamped_value
/// \endcond
#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"
//***************************************************************************
/// 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`.
///@tparam Min The minimum value of the range.
///@tparam Max The maximum value of the range.
///\ingroup clamped_value
//***************************************************************************
template <typename T, T Min, T Max>
class clamped_value<T, Min, Max, false>
{
public:
/// The type used to advance or subtract from the stored value.
typedef typename private_clamped_value::traits<T>::difference_type difference_type;
/// Numeric limits for the stored value type.
typedef typename private_clamped_value::traits<T>::limits_type limits_type;
/// Numeric limits for difference_type.
typedef typename private_clamped_value::traits<T>::difference_limits_type difference_limits_type;
/// \cond INTERNAL
ETL_STATIC_ASSERT(Min <= Max, "clamped_value minimum must not exceed maximum");
/// \endcond
//*************************************************************************
/// Default constructor.
/// The initial value is set to the minimum value.
//*************************************************************************
ETL_CONSTEXPR clamped_value() ETL_NOEXCEPT
: value(Min)
{
}
//*************************************************************************
/// Constructor.
/// Set to an initial value.
/// Clamped to the range.
/// NaN is rejected for floating-point values.
///\param initial The initial value.
//*************************************************************************
ETL_CONSTEXPR14 explicit clamped_value(T initial) ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS)
: value(private_clamped_value::validated_clamp(initial, Min, Max))
{
}
//*************************************************************************
/// Copy constructor.
//*************************************************************************
ETL_CONSTEXPR clamped_value(const clamped_value& other) ETL_NOEXCEPT
: value(other.value)
{
}
//*************************************************************************
/// Assignment operator.
///\param other The value to copy.
///\return A reference to this value.
//*************************************************************************
ETL_CONSTEXPR14 clamped_value& operator=(const clamped_value& other) ETL_LVALUE_REF_QUALIFIER ETL_NOEXCEPT
{
if (this == &other)
{
return *this;
}
set(other.value);
return *this;
}
//*************************************************************************
/// Sets the value.
/// Clamps to the Min/Max range.
/// NaN is rejected for floating-point values.
///\param value_ The value.
//*************************************************************************
ETL_CONSTEXPR14 void set(T value_) ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS)
{
value = private_clamped_value::validated_clamp(value_, Min, Max);
}
//*************************************************************************
/// Resets the value to the minimum in the range.
//*************************************************************************
ETL_CONSTEXPR14 void to_min() ETL_NOEXCEPT
{
value = Min;
}
//*************************************************************************
/// Resets the value to the maximum in the range.
//*************************************************************************
ETL_CONSTEXPR14 void to_max() ETL_NOEXCEPT
{
value = Max;
}
//*************************************************************************
/// Advances the value by a number of steps.
/// Saturates at the Min/Max range.
/// Floating-point steps may be fractional. NaN is rejected.
///\param n The number of steps.
//*************************************************************************
ETL_CONSTEXPR14 void advance(difference_type n) ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS)
{
private_clamped_value::validate(n);
value = private_clamped_value::advance(value, Min, Max, n);
}
//*************************************************************************
/// Conversion operator.
/// \return The value of the underlying type.
//*************************************************************************
ETL_NODISCARD ETL_CONSTEXPR14 operator T() const ETL_NOEXCEPT
{
return value;
}
//*************************************************************************
/// ++ operator.
/// Advances by one.
/// Saturates at Max.
///\return A reference to this value.
//*************************************************************************
ETL_CONSTEXPR14 clamped_value& operator++() ETL_LVALUE_REF_QUALIFIER ETL_NOEXCEPT
{
value = private_clamped_value::advance(value, Min, Max, difference_type(1));
return *this;
}
//*************************************************************************
/// ++ operator.
/// Saturates at Max.
///\return The value before incrementing.
//*************************************************************************
ETL_CONSTEXPR14 clamped_value operator++(int) ETL_NOEXCEPT
{
clamped_value temp(*this);
++(*this);
return temp;
}
//*************************************************************************
/// -- operator.
/// Subtracts one.
/// Saturates at Min.
///\return A reference to this value.
//*************************************************************************
ETL_CONSTEXPR14 clamped_value& operator--() ETL_LVALUE_REF_QUALIFIER ETL_NOEXCEPT
{
value = private_clamped_value::advance(value, Min, Max, difference_type(-1));
return *this;
}
//*************************************************************************
/// -- operator.
/// Saturates at Min.
///\return The value before decrementing.
//*************************************************************************
ETL_CONSTEXPR14 clamped_value operator--(int) ETL_NOEXCEPT
{
clamped_value temp(*this);
--(*this);
return temp;
}
//*************************************************************************
/// = operator.
/// Clamps to the Min/Max range.
///\param value_ The value to assign.
///\return A reference to this value.
//*************************************************************************
ETL_CONSTEXPR14 clamped_value& operator=(T value_) ETL_LVALUE_REF_QUALIFIER ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS)
{
set(value_);
return *this;
}
//*************************************************************************
/// Adds a number of steps and clamps to the range.
/// Floating-point steps may be fractional. NaN is rejected.
///\param n The number of steps.
///\return A reference to this value.
//*************************************************************************
ETL_CONSTEXPR14 clamped_value& operator+=(difference_type n) ETL_LVALUE_REF_QUALIFIER ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS)
{
advance(n);
return *this;
}
//*************************************************************************
/// Subtracts a number of steps and clamps to the range.
/// Floating-point steps may be fractional. NaN is rejected.
///\param n The number of steps.
///\return A reference to this value.
//*************************************************************************
ETL_CONSTEXPR14 clamped_value& operator-=(difference_type n) ETL_LVALUE_REF_QUALIFIER ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS)
{
private_clamped_value::validate(n);
value = private_clamped_value::subtract(value, Min, Max, n);
return *this;
}
//*************************************************************************
/// Gets the value.
///\return The current value.
//*************************************************************************
ETL_NODISCARD ETL_CONSTEXPR T get() const ETL_NOEXCEPT
{
return value;
}
//*************************************************************************
/// Gets the minimum value.
///\return The minimum value.
//*************************************************************************
ETL_NODISCARD ETL_CONSTEXPR T min() const ETL_NOEXCEPT
{
return Min;
}
//*************************************************************************
/// Gets the maximum value.
///\return The maximum value.
//*************************************************************************
ETL_NODISCARD ETL_CONSTEXPR T max() const ETL_NOEXCEPT
{
return Max;
}
//*************************************************************************
/// Swaps the values.
///\param other The value to swap with.
//*************************************************************************
ETL_CONSTEXPR14 void swap(clamped_value& other) ETL_NOEXCEPT
{
using ETL_OR_STD::swap;
swap(value, other.value);
}
//*************************************************************************
/// Swaps the values.
///\param lhs The first value.
///\param rhs The second value.
//*************************************************************************
friend ETL_CONSTEXPR14 void swap(clamped_value& lhs, clamped_value& rhs) ETL_NOEXCEPT
{
lhs.swap(rhs);
}
//*************************************************************************
/// Operator ==.
///\param lhs The left-hand value.
///\param rhs The right-hand value.
///\return `true` if the values are equal.
//*************************************************************************
friend ETL_CONSTEXPR bool operator==(const clamped_value& lhs, const clamped_value& rhs) ETL_NOEXCEPT
{
#include "private/diagnostic_float_equal_push.h"
return lhs.value == rhs.value;
#include "private/diagnostic_pop.h"
}
//*************************************************************************
/// Operator !=.
///\param lhs The left-hand value.
///\param rhs The right-hand value.
///\return `true` if the values are not equal.
//*************************************************************************
friend ETL_CONSTEXPR bool operator!=(const clamped_value& lhs, const clamped_value& rhs) ETL_NOEXCEPT
{
return !(lhs == rhs);
}
//*************************************************************************
/// Operator <.
///\param lhs The left-hand operand.
///\param rhs The right-hand operand.
///\return `true` if lhs is less than rhs.
/// @{
friend ETL_CONSTEXPR bool operator<(const clamped_value& lhs, const clamped_value& rhs) ETL_NOEXCEPT
{
return lhs.value < rhs.value;
}
/// @}
//*************************************************************************
/// Operator <=.
///\param lhs The left-hand operand.
///\param rhs The right-hand operand.
///\return `true` if lhs is less than or equal to rhs.
/// @{
friend ETL_CONSTEXPR bool operator<=(const clamped_value& lhs, const clamped_value& rhs) ETL_NOEXCEPT
{
return !(rhs < lhs);
}
/// @}
//*************************************************************************
/// Operator >.
///\param lhs The left-hand operand.
///\param rhs The right-hand operand.
///\return `true` if lhs is greater than rhs.
/// @{
friend ETL_CONSTEXPR bool operator>(const clamped_value& lhs, const clamped_value& rhs) ETL_NOEXCEPT
{
return rhs < lhs;
}
/// @}
//*************************************************************************
/// Operator >=.
///\param lhs The left-hand operand.
///\param rhs The right-hand operand.
///\return `true` if lhs is greater than or equal to rhs.
/// @{
friend ETL_CONSTEXPR bool operator>=(const clamped_value& lhs, const clamped_value& rhs) ETL_NOEXCEPT
{
return !(lhs < rhs);
}
/// @}
private:
T value;
};
//***************************************************************************
/// Provides a value that is clamped between two runtime 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`.
///@tparam Min Ignored for this specialisation.
///@tparam Max Ignored for this specialisation.
///\ingroup clamped_value
//***************************************************************************
template <typename T, T Min, T Max>
class clamped_value<T, Min, Max, true>
{
public:
/// The type used to advance or subtract from the stored value.
typedef typename private_clamped_value::traits<T>::difference_type difference_type;
/// Numeric limits for the stored value type.
typedef typename private_clamped_value::traits<T>::limits_type limits_type;
/// Numeric limits for difference_type.
typedef typename private_clamped_value::traits<T>::difference_limits_type difference_limits_type;
//*************************************************************************
/// Default constructor.
/// Sets the value to the minimum representable value of `T` and the
/// bounds to the full range of `T`.
//*************************************************************************
ETL_CONSTEXPR clamped_value() ETL_NOEXCEPT
: value(limits_type::lowest())
, min_value(limits_type::lowest())
, max_value(limits_type::max())
{
}
//*************************************************************************
/// Constructor.
/// Sets the value to the minimum of the range.
/// Reversed bounds and NaN bounds are rejected.
///\param min_ The minimum value.
///\param max_ The maximum value.
//*************************************************************************
ETL_CONSTEXPR14 clamped_value(T min_, T max_) ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS)
: value(min_)
, min_value(min_)
, max_value(max_)
{
private_clamped_value::validate(min_);
private_clamped_value::validate(max_);
ETL_ASSERT(min_ <= max_, ETL_ERROR_GENERIC("clamped_value: invalid range"));
}
//*************************************************************************
/// Constructor.
/// Set to an initial value.
/// Clamped to the range.
/// Reversed bounds and NaN values or bounds are rejected.
///\param min_ The minimum value.
///\param max_ The maximum value.
///\param initial The initial value.
//*************************************************************************
ETL_CONSTEXPR14 clamped_value(T min_, T max_, T initial) ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS)
: value(private_clamped_value::validated_clamp(initial, min_, max_))
, min_value(min_)
, max_value(max_)
{
}
//*************************************************************************
/// Copy constructor.
///\param other The value to copy.
//*************************************************************************
ETL_CONSTEXPR clamped_value(const clamped_value& other) ETL_NOEXCEPT
: value(other.value)
, min_value(other.min_value)
, max_value(other.max_value)
{
}
//*************************************************************************
/// Sets the range.
/// Sets the value to the minimum of the range.
/// Reversed bounds and NaN bounds are rejected.
///\param min_ The minimum value.
///\param max_ The maximum value.
//*************************************************************************
ETL_CONSTEXPR14 void set(T min_, T max_) ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS)
{
private_clamped_value::validate(min_);
private_clamped_value::validate(max_);
ETL_ASSERT(min_ <= max_, ETL_ERROR_GENERIC("clamped_value: invalid range"));
min_value = min_;
max_value = max_;
value = min_;
}
//*************************************************************************
/// Sets the value.
/// Clamps to the runtime Min/Max range.
/// NaN is rejected for floating-point values.
///\param value_ The value.
//*************************************************************************
ETL_CONSTEXPR14 void set(T value_) ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS)
{
value = private_clamped_value::validated_clamp(value_, min_value, max_value);
}
//*************************************************************************
/// Resets the value to the minimum in the range.
//*************************************************************************
ETL_CONSTEXPR14 void to_min() ETL_NOEXCEPT
{
value = min_value;
}
//*************************************************************************
/// Resets the value to the maximum in the range.
//*************************************************************************
ETL_CONSTEXPR14 void to_max() ETL_NOEXCEPT
{
value = max_value;
}
//*************************************************************************
/// Advances the value by a number of steps.
/// Saturates at the runtime Min/Max range.
/// Floating-point steps may be fractional. NaN is rejected.
///\param n The number of steps.
//*************************************************************************
ETL_CONSTEXPR14 void advance(difference_type n) ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS)
{
private_clamped_value::validate(n);
value = private_clamped_value::advance(value, min_value, max_value, n);
}
//*************************************************************************
/// Conversion operator.
/// \return The value of the underlying type.
//*************************************************************************
ETL_NODISCARD ETL_CONSTEXPR14 operator T() const ETL_NOEXCEPT
{
return value;
}
//*************************************************************************
/// ++ operator.
/// Advances by one.
/// Saturates at the maximum.
///\return A reference to this value.
//*************************************************************************
ETL_CONSTEXPR14 clamped_value& operator++() ETL_LVALUE_REF_QUALIFIER ETL_NOEXCEPT
{
value = private_clamped_value::advance(value, min_value, max_value, difference_type(1));
return *this;
}
//*************************************************************************
/// ++ operator.
/// Saturates at the maximum.
///\return The value before incrementing.
//*************************************************************************
ETL_CONSTEXPR14 clamped_value operator++(int) ETL_NOEXCEPT
{
clamped_value temp(*this);
++(*this);
return temp;
}
//*************************************************************************
/// -- operator.
/// Subtracts one.
/// Saturates at the minimum.
///\return A reference to this value.
//*************************************************************************
ETL_CONSTEXPR14 clamped_value& operator--() ETL_LVALUE_REF_QUALIFIER ETL_NOEXCEPT
{
value = private_clamped_value::advance(value, min_value, max_value, difference_type(-1));
return *this;
}
//*************************************************************************
/// -- operator.
/// Saturates at the minimum.
///\return The value before decrementing.
//*************************************************************************
ETL_CONSTEXPR14 clamped_value operator--(int) ETL_NOEXCEPT
{
clamped_value temp(*this);
--(*this);
return temp;
}
//*************************************************************************
/// = operator.
/// Clamps to the runtime Min/Max range.
///\param value_ The value to assign.
///\return A reference to this value.
//*************************************************************************
ETL_CONSTEXPR14 clamped_value& operator=(T value_) ETL_LVALUE_REF_QUALIFIER ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS)
{
set(value_);
return *this;
}
//*************************************************************************
/// = operator.
/// Copies the value and runtime bounds.
///\param other The value to copy.
///\return A reference to this value.
//*************************************************************************
ETL_CONSTEXPR14 clamped_value& operator=(const clamped_value& other) ETL_LVALUE_REF_QUALIFIER ETL_NOEXCEPT
{
if (this == &other)
{
return *this;
}
value = other.value;
min_value = other.min_value;
max_value = other.max_value;
return *this;
}
//*************************************************************************
/// Adds a number of steps and clamps to the range.
/// Floating-point steps may be fractional. NaN is rejected.
///\param n The number of steps.
///\return A reference to this value.
//*************************************************************************
ETL_CONSTEXPR14 clamped_value& operator+=(difference_type n) ETL_LVALUE_REF_QUALIFIER ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS)
{
advance(n);
return *this;
}
//*************************************************************************
/// Subtracts a number of steps and clamps to the range.
/// Floating-point steps may be fractional. NaN is rejected.
///\param n The number of steps.
///\return A reference to this value.
//*************************************************************************
ETL_CONSTEXPR14 clamped_value& operator-=(difference_type n) ETL_LVALUE_REF_QUALIFIER ETL_NOEXCEPT_IF(ETL_NOT_USING_EXCEPTIONS)
{
private_clamped_value::validate(n);
value = private_clamped_value::subtract(value, min_value, max_value, n);
return *this;
}
//*************************************************************************
/// Gets the value.
///\return The current value.
//*************************************************************************
ETL_NODISCARD ETL_CONSTEXPR T get() const ETL_NOEXCEPT
{
return value;
}
//*************************************************************************
/// Gets the minimum value.
///\return The minimum value.
//*************************************************************************
ETL_NODISCARD ETL_CONSTEXPR T min() const ETL_NOEXCEPT
{
return min_value;
}
//*************************************************************************
/// Gets the maximum value.
///\return The maximum value.
//*************************************************************************
ETL_NODISCARD ETL_CONSTEXPR T max() const ETL_NOEXCEPT
{
return max_value;
}
//*************************************************************************
/// Swaps the values and runtime bounds.
///\param other The value to swap with.
//*************************************************************************
ETL_CONSTEXPR14 void swap(clamped_value& other) ETL_NOEXCEPT
{
using ETL_OR_STD::swap;
swap(value, other.value);
swap(min_value, other.min_value);
swap(max_value, other.max_value);
}
//*************************************************************************
/// Swaps the values and runtime bounds.
///\param lhs The first value.
///\param rhs The second value.
//*************************************************************************
friend ETL_CONSTEXPR14 void swap(clamped_value& lhs, clamped_value& rhs) ETL_NOEXCEPT
{
lhs.swap(rhs);
}
//*************************************************************************
/// Operator ==.
///\param lhs The left-hand value.
///\param rhs The right-hand value.
///\return `true` if the values are equal.
//*************************************************************************
friend ETL_CONSTEXPR bool operator==(const clamped_value& lhs, const clamped_value& rhs) ETL_NOEXCEPT
{
#include "private/diagnostic_float_equal_push.h"
return lhs.value == rhs.value;
#include "private/diagnostic_pop.h"
}
//*************************************************************************
/// Operator !=.
///\param lhs The left-hand value.
///\param rhs The right-hand value.
///\return `true` if the values differ.
//*************************************************************************
friend ETL_CONSTEXPR bool operator!=(const clamped_value& lhs, const clamped_value& rhs) ETL_NOEXCEPT
{
return !(lhs == rhs);
}
//*************************************************************************
/// Operator <.
///\param lhs The left-hand operand.
///\param rhs The right-hand operand.
///\return `true` if lhs is less than rhs.
/// @{
friend ETL_CONSTEXPR bool operator<(const clamped_value& lhs, const clamped_value& rhs) ETL_NOEXCEPT
{
return lhs.value < rhs.value;
}
/// @}
//*************************************************************************
/// Operator <=.
///\param lhs The left-hand operand.
///\param rhs The right-hand operand.
///\return `true` if lhs is less than or equal to rhs.
/// @{
friend ETL_CONSTEXPR bool operator<=(const clamped_value& lhs, const clamped_value& rhs) ETL_NOEXCEPT
{
return !(rhs < lhs);
}
/// @}
//*************************************************************************
/// Operator >.
///\param lhs The left-hand operand.
///\param rhs The right-hand operand.
///\return `true` if lhs is greater than rhs.
/// @{
friend ETL_CONSTEXPR bool operator>(const clamped_value& lhs, const clamped_value& rhs) ETL_NOEXCEPT
{
return rhs < lhs;
}
/// @}
//*************************************************************************
/// Operator >=.
///\param lhs The left-hand operand.
///\param rhs The right-hand operand.
///\return `true` if lhs is greater than or equal to rhs.
/// @{
friend ETL_CONSTEXPR bool operator>=(const clamped_value& lhs, const clamped_value& rhs) ETL_NOEXCEPT
{
return !(lhs < rhs);
}
/// @}
private:
T value;
T min_value;
T max_value;
};
} // namespace etl
#endif

View File

@ -163,6 +163,7 @@ add_executable(etl_tests
test_crc8_opensafety.cpp
test_crc8_rohc.cpp
test_crc8_wcdma.cpp
test_clamped_value.cpp
test_cyclic_value.cpp
test_debounce.cpp
test_delegate.cpp

View File

@ -46,6 +46,7 @@ SOFTWARE.
#include "etl/callback_timer.h"
#include "etl/char_traits.h"
#include "etl/checksum.h"
#include "etl/clamped_value.h"
#include "etl/combinations.h"
#include "etl/compare.h"
#include "etl/constant.h"

View File

@ -151,6 +151,7 @@ etl_test_sources = files(
'test_crc8_opensafety.cpp',
'test_crc8_rohc.cpp',
'test_crc8_wcdma.cpp',
'test_clamped_value.cpp',
'test_cyclic_value.cpp',
'test_debounce.cpp',
'test_delegate.cpp',

View File

@ -202,6 +202,7 @@ target_sources(tests PRIVATE
crc8_nrsc5.h.t.cpp
crc8_rohc.h.t.cpp
crc8_wcdma.h.t.cpp
clamped_value.h.t.cpp
cyclic_value.h.t.cpp
debounce.h.t.cpp
debug_count.h.t.cpp

View File

@ -0,0 +1,36 @@
///\file
/******************************************************************************
The MIT License(MIT)
Embedded Template Library.
https://github.com/ETLCPP/etl
https://www.etlcpp.com
Copyright(c) 2026 John Wellbelove
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files(the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions :
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
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;
#endif

515
test/test_clamped_value.cpp Normal file
View File

@ -0,0 +1,515 @@
///\file
/******************************************************************************
The MIT License(MIT)
Embedded Template Library.
https://github.com/ETLCPP/etl
https://www.etlcpp.com
Copyright(c) 2026 John Wellbelove
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files(the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions :
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
******************************************************************************/
#include "unit_test_framework.h"
#include "etl/clamped_value.h"
#include <limits.h>
#include <stdint.h>
namespace
{
SUITE(test_clamped_value)
{
//*************************************************************************
TEST(test_compile_time_initialisation)
{
etl::clamped_value<int, 2, 7> value;
const etl::clamped_value<int, 2, 7> const_value(value);
int converted_value = value;
int converted_const_value = const_value;
CHECK_EQUAL(2, value.get());
CHECK_EQUAL(2, value.min());
CHECK_EQUAL(7, value.max());
CHECK_EQUAL(2, converted_value);
CHECK_EQUAL(2, converted_const_value);
}
//*************************************************************************
TEST(test_compile_time_bounds_accessors_are_const)
{
const etl::clamped_value<int, 2, 7> value(4);
CHECK_EQUAL(2, value.min());
CHECK_EQUAL(7, value.max());
}
//*************************************************************************
TEST(test_run_time_initialisation)
{
etl::clamped_value<int> value(2, 7, 5);
etl::clamped_value<int> default_value;
etl::clamped_value<int> range_value(2, 7);
const etl::clamped_value<int> const_value(value);
int converted_value = value;
int converted_const_value = const_value;
CHECK_EQUAL(5, value.get());
CHECK_EQUAL(2, value.min());
CHECK_EQUAL(7, value.max());
CHECK_EQUAL(etl::numeric_limits<int>::lowest(), default_value.min());
CHECK_EQUAL(etl::numeric_limits<int>::max(), default_value.max());
CHECK_EQUAL(etl::numeric_limits<int>::lowest(), default_value.get());
CHECK_EQUAL(2, range_value.get());
CHECK_EQUAL(5, converted_value);
CHECK_EQUAL(5, converted_const_value);
}
//*************************************************************************
TEST(test_set_and_assignment_clamp_values)
{
etl::clamped_value<int, 2, 7> compile_time(1);
etl::clamped_value<int> run_time(2, 7, 8);
CHECK_EQUAL(2, compile_time.get());
CHECK_EQUAL(7, run_time.get());
compile_time = 8;
run_time.set(1);
CHECK_EQUAL(7, compile_time.get());
CHECK_EQUAL(2, run_time.get());
}
//*************************************************************************
TEST(test_increment_and_decrement_saturate)
{
etl::clamped_value<int, 2, 7> compile_time(7);
etl::clamped_value<int> run_time(2, 7, 2);
++compile_time;
--run_time;
CHECK_EQUAL(7, compile_time.get());
CHECK_EQUAL(2, run_time.get());
compile_time.to_min();
run_time.to_max();
--compile_time;
++run_time;
CHECK_EQUAL(2, compile_time.get());
CHECK_EQUAL(7, run_time.get());
}
//*************************************************************************
TEST(test_postfix_operators)
{
etl::clamped_value<int, 2, 7> value(4);
etl::clamped_value<int, 2, 7> previous_increment = value++;
CHECK_EQUAL(4, previous_increment.get());
CHECK_EQUAL(5, value.get());
etl::clamped_value<int, 2, 7> previous_decrement = value--;
CHECK_EQUAL(5, previous_decrement.get());
CHECK_EQUAL(4, value.get());
}
//*************************************************************************
TEST(test_advance_saturates)
{
etl::clamped_value<int, 2, 7> value;
value.advance(100);
CHECK_EQUAL(7, value.get());
value.advance(-100);
CHECK_EQUAL(2, value.get());
}
//*************************************************************************
TEST(test_set_run_time_bounds)
{
etl::clamped_value<int> value(2, 7, 6);
value.set(3, 8);
CHECK_EQUAL(3, value.get());
CHECK_EQUAL(3, value.min());
CHECK_EQUAL(8, value.max());
}
//*************************************************************************
TEST(test_copy_equality_and_swap_compile_time)
{
etl::clamped_value<int, 2, 7> value1(4);
etl::clamped_value<int, 2, 7> value2(value1);
CHECK(value1 == value2);
value2 = 6;
CHECK(value1 != value2);
swap(value1, value2);
CHECK_EQUAL(6, value1.get());
CHECK_EQUAL(4, value2.get());
}
//*************************************************************************
TEST(test_compile_time_instances_with_different_bounds)
{
etl::clamped_value<int, 2, 7> value1(3);
etl::clamped_value<int, 2, 8> value2(3);
CHECK(value1 == value2);
}
//*************************************************************************
TEST(test_runtime_equality_uses_current_value)
{
etl::clamped_value<int> equal_value1(2, 7, 3);
etl::clamped_value<int> equal_value2(2, 8, 3);
etl::clamped_value<int> different_value(2, 8, 4);
CHECK(equal_value1 == equal_value2);
CHECK(!(equal_value1 != equal_value2));
CHECK(equal_value1 != different_value);
}
//*************************************************************************
TEST(test_copy_assignment_and_self_assignment_run_time)
{
etl::clamped_value<int> value1(2, 7, 5);
etl::clamped_value<int> value2(3, 8, 6);
value1 = value2;
CHECK_EQUAL(6, value1.get());
CHECK_EQUAL(3, value1.min());
CHECK_EQUAL(8, value1.max());
etl::clamped_value<int>* value1_pointer = &value1;
value1 = *value1_pointer;
CHECK_EQUAL(6, value1.get());
CHECK_EQUAL(3, value1.min());
CHECK_EQUAL(8, value1.max());
}
//*************************************************************************
TEST(test_unsigned_boundary_values)
{
etl::clamped_value<unsigned, 0U, 2U> value(2U);
++value;
CHECK_EQUAL(2U, value.get());
value.to_min();
--value;
CHECK_EQUAL(0U, value.get());
}
//*************************************************************************
TEST(test_narrow_signed_range_spanning_lowest_value)
{
etl::clamped_value<int8_t, INT8_MIN, -10> value(-50);
value += 10;
CHECK_EQUAL(-40, value.get());
value -= 10;
CHECK_EQUAL(-50, value.get());
value.advance(INT8_MIN);
CHECK_EQUAL(INT8_MIN, value.get());
value.advance(INT8_MAX);
CHECK_EQUAL(-10, value.get());
}
//*************************************************************************
TEST(test_runtime_postfix_and_advance)
{
etl::clamped_value<int> value(2, 7, 4);
etl::clamped_value<int> previous_increment = value++;
CHECK_EQUAL(4, previous_increment.get());
CHECK_EQUAL(5, value.get());
etl::clamped_value<int> previous_decrement = value--;
CHECK_EQUAL(5, previous_decrement.get());
CHECK_EQUAL(4, value.get());
value.advance(0);
CHECK_EQUAL(4, value.get());
value.advance(3);
CHECK_EQUAL(7, value.get());
value.advance(-3);
CHECK_EQUAL(4, value.get());
}
//*************************************************************************
TEST(test_wide_and_negative_ranges)
{
etl::clamped_value<int, -7, -2> negative(-4);
etl::clamped_value<int64_t, -INT64_C(7), INT64_C(7)> wide(0);
etl::clamped_value<unsigned> unsigned_value(2U, 7U, 4U);
negative.advance(100);
wide.advance(INT64_MAX);
unsigned_value.advance(3);
CHECK_EQUAL(-2, negative.get());
CHECK_EQUAL(INT64_C(7), wide.get());
CHECK_EQUAL(7U, unsigned_value.get());
}
//*************************************************************************
TEST(test_compound_and_comparison_operators)
{
etl::clamped_value<int, 2, 7> value(4);
etl::clamped_value<int, 2, 7> other(6);
value += 10;
CHECK_EQUAL(7, value.get());
value -= 10;
CHECK_EQUAL(2, value.get());
CHECK(value < other);
CHECK(value < 6);
CHECK(4 < other);
CHECK(value <= other);
CHECK(value <= 4);
CHECK(4 <= other);
CHECK(other > value);
CHECK(other > 4);
CHECK(6 > value);
CHECK(other >= value);
CHECK(other >= 6);
CHECK(6 >= value);
CHECK(value == 2);
CHECK(2 == value);
CHECK(value != 6);
CHECK(6 != value);
}
//*************************************************************************
TEST(test_mixed_type_comparisons_are_not_ambiguous)
{
etl::clamped_value<uint8_t, 0, 200> narrow(10);
etl::clamped_value<int, 0, 200> value(10);
CHECK(narrow < 20);
CHECK(value < 20L);
CHECK(20L > value);
CHECK(value <= 20L);
CHECK(20L >= value);
}
//*************************************************************************
TEST(test_runtime_comparison_operator_overloads)
{
etl::clamped_value<int> value(2, 7, 4);
etl::clamped_value<int> other(2, 8, 6);
CHECK(value < other);
CHECK(value < 6);
CHECK(4 < other);
CHECK(value <= other);
CHECK(value <= 4);
CHECK(4 <= other);
CHECK(other > value);
CHECK(other > 4);
CHECK(6 > value);
CHECK(other >= value);
CHECK(other >= 6);
CHECK(6 >= value);
}
//*************************************************************************
TEST(test_advance_extreme_values)
{
etl::clamped_value<int, 2, 7> compile_time(4);
etl::clamped_value<int> run_time(2, 7, 4);
compile_time.advance(INT_MAX);
run_time.advance(INT_MIN);
CHECK_EQUAL(7, compile_time.get());
CHECK_EQUAL(2, run_time.get());
}
//*************************************************************************
TEST(test_subtract_lowest_difference_saturates)
{
etl::clamped_value<int, -10, 10> compile_time(0);
etl::clamped_value<int> run_time(-10, 10, 0);
compile_time -= etl::clamped_value<int, -10, 10>::difference_limits_type::lowest();
run_time -= etl::clamped_value<int>::difference_limits_type::lowest();
CHECK_EQUAL(10, compile_time.get());
CHECK_EQUAL(10, run_time.get());
}
//*************************************************************************
TEST(test_runtime_compound_assignment_and_swap)
{
etl::clamped_value<int> value1(-4, 8, 2);
etl::clamped_value<int> value2(10, 20, 15);
value1 += 20;
value2 -= 20;
value1.swap(value2);
CHECK_EQUAL(10, value1.get());
CHECK_EQUAL(10, value1.min());
CHECK_EQUAL(20, value1.max());
CHECK_EQUAL(8, value2.get());
CHECK_EQUAL(-4, value2.min());
CHECK_EQUAL(8, value2.max());
}
//*************************************************************************
TEST(test_runtime_reversed_bounds_are_rejected)
{
CHECK_THROW((etl::clamped_value<int>(7, 2)), etl::exception);
CHECK_THROW((etl::clamped_value<int>(7, 2, 4)), etl::exception);
etl::clamped_value<int> value;
CHECK_THROW(value.set(7, 2), etl::exception);
}
#if ETL_HAS_FLOATING_POINT_CLAMPED_VALUE
//*************************************************************************
TEST(test_compile_time_floating_clamps_and_advances)
{
etl::clamped_value<float, -1.5f, 2.5f> value(4.0f);
CHECK_CLOSE(2.5f, value.get(), 0.0001f);
value = -4.0f;
CHECK_CLOSE(-1.5f, value.get(), 0.0001f);
value += 0.75f;
CHECK_CLOSE(-0.75f, value.get(), 0.0001f);
value -= 0.5f;
CHECK_CLOSE(-1.25f, value.get(), 0.0001f);
value.advance(10.0f);
CHECK_CLOSE(2.5f, value.get(), 0.0001f);
}
//*************************************************************************
TEST(test_runtime_floating_clamps_and_advances)
{
etl::clamped_value<double> value(-1.5, 2.5, 4.0);
CHECK_CLOSE(2.5, value.get(), 0.0001);
value = -4.0;
value += 0.75;
value -= 0.5;
CHECK_CLOSE(-1.25, value.get(), 0.0001);
value.advance(-10.0);
CHECK_CLOSE(-1.5, value.get(), 0.0001);
}
//*************************************************************************
TEST(test_floating_increment_and_decrement_saturate_at_fractional_bounds)
{
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);
}
//*************************************************************************
TEST(test_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);
CHECK(compile_time1 < compile_time2);
CHECK(compile_time1 < 1.0);
swap(compile_time1, compile_time2);
swap(run_time1, run_time2);
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);
}
//*************************************************************************
TEST(test_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);
value += infinity;
CHECK_CLOSE(2.0, value.get(), 0.0001);
value -= infinity;
CHECK_CLOSE(-2.0, value.get(), 0.0001);
infinite_range = infinity;
CHECK(etl::is_infinity(infinite_range.get()));
}
//*************************************************************************
TEST(test_floating_finite_overflow_is_prevented)
{
const double maximum = etl::numeric_limits<double>::max();
etl::clamped_value<double> value(-maximum, maximum, maximum / 2.0);
value += maximum;
CHECK_EQUAL(maximum, value.get());
value = -maximum / 2.0;
value -= maximum;
CHECK_EQUAL(-maximum, value.get());
value = -maximum;
value += maximum;
CHECK_EQUAL(0.0, value.get());
value += maximum;
CHECK_EQUAL(maximum, value.get());
}
//*************************************************************************
TEST(test_floating_nan_is_rejected)
{
const double nan = etl::numeric_limits<double>::quiet_NaN();
CHECK_THROW((etl::clamped_value<double>(nan, 1.0)), etl::exception);
CHECK_THROW((etl::clamped_value<double>(-1.0, nan)), etl::exception);
CHECK_THROW((etl::clamped_value<double>(-1.0, 1.0, nan)), etl::exception);
etl::clamped_value<double> value(-1.0, 1.0, 0.0);
CHECK_THROW(value.set(nan, 1.0), etl::exception);
CHECK_THROW(value.set(-1.0, nan), etl::exception);
CHECK_THROW(value = nan, etl::exception);
CHECK_THROW(value.advance(nan), etl::exception);
CHECK_THROW(value += nan, etl::exception);
CHECK_THROW(value -= nan, etl::exception);
}
//*************************************************************************
TEST(test_default_floating_range)
{
etl::clamped_value<double> value;
CHECK_EQUAL(etl::numeric_limits<double>::lowest(), value.min());
CHECK_EQUAL(etl::numeric_limits<double>::max(), value.max());
CHECK_EQUAL(etl::numeric_limits<double>::lowest(), value.get());
}
#endif
#if ETL_USING_CPP14
//*************************************************************************
TEST(test_clamped_value_constexpr_ctor)
{
constexpr etl::clamped_value<int, 0, 9> value;
static_assert(value.get() == 0, "constexpr clamped_value constructor");
CHECK(true);
}
#if ETL_HAS_FLOATING_POINT_CLAMPED_VALUE
//*************************************************************************
TEST(test_floating_clamped_value_constexpr)
{
constexpr etl::clamped_value<double, -1.0, 1.0> value(0.5);
static_assert((value.get() > 0.49) && (value.get() < 0.51), "constexpr floating clamped_value constructor");
CHECK(true);
}
#endif
#endif
}
} // namespace

View File

@ -3764,6 +3764,7 @@
<ClInclude Include="..\..\include\etl\bloom_filter.h" />
<ClInclude Include="..\..\include\etl\char_traits.h" />
<ClInclude Include="..\..\include\etl\checksum.h" />
<ClInclude Include="..\..\include\etl\clamped_value.h" />
<ClInclude Include="..\..\include\etl\cyclic_value.h" />
<ClInclude Include="..\..\include\etl\debounce.h" />
<ClInclude Include="..\..\include\etl\debug_count.h" />
@ -6303,6 +6304,7 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug MSVC C++20 - Force C++03|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug MSVC C++20 - Force C++03 - No virtual messages|Win32'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\syntax_check\clamped_value.h.t.cpp" />
<ClCompile Include="..\syntax_check\cyclic_value.h.t.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug MSVC C++20|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug Clang C++20|Win32'">true</ExcludedFromBuild>
@ -10446,6 +10448,7 @@
<ClCompile Include="..\test_compare.cpp" />
<ClCompile Include="..\test_constant.cpp" />
<ClCompile Include="..\test_container.cpp" />
<ClCompile Include="..\test_clamped_value.cpp" />
<ClCompile Include="..\test_cyclic_value.cpp" />
<ClCompile Include="..\test_debounce.cpp" />
<ClCompile Include="..\test_deque.cpp">

View File

@ -261,6 +261,9 @@
<ClInclude Include="..\..\include\etl\numeric.h">
<Filter>ETL\Utilities</Filter>
</ClInclude>
<ClInclude Include="..\..\include\etl\clamped_value.h">
<Filter>ETL\Utilities</Filter>
</ClInclude>
<ClInclude Include="..\..\include\etl\cyclic_value.h">
<Filter>ETL\Utilities</Filter>
</ClInclude>
@ -2087,6 +2090,9 @@
<ClCompile Include="..\test_pseudo_moving_average.cpp">
<Filter>Tests\Algorithms</Filter>
</ClCompile>
<ClCompile Include="..\test_clamped_value.cpp">
<Filter>Tests\Algorithms</Filter>
</ClCompile>
<ClCompile Include="..\test_cyclic_value.cpp">
<Filter>Tests\Algorithms</Filter>
</ClCompile>
@ -2909,6 +2915,9 @@
<ClCompile Include="..\syntax_check\crc64_ecma.h.t.cpp">
<Filter>Tests\Syntax Checks\Source</Filter>
</ClCompile>
<ClCompile Include="..\syntax_check\clamped_value.h.t.cpp">
<Filter>Tests\Syntax Checks\Source</Filter>
</ClCompile>
<ClCompile Include="..\syntax_check\cyclic_value.h.t.cpp">
<Filter>Tests\Syntax Checks\Source</Filter>
</ClCompile>