From 11623e686257d73de5e14579d32b51877eadda70 Mon Sep 17 00:00:00 2001 From: Drew Rife Date: Tue, 8 Sep 2026 05:21:33 -0400 Subject: [PATCH] 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 --- docs/utilities/clamped-value.md | 170 +++++ include/etl/clamped_value.h | 956 ++++++++++++++++++++++++ test/CMakeLists.txt | 1 + test/maincpp03check.cpp | 1 + test/meson.build | 1 + test/syntax_check/CMakeLists.txt | 1 + test/syntax_check/clamped_value.h.t.cpp | 36 + test/test_clamped_value.cpp | 515 +++++++++++++ test/vs2022/etl.vcxproj | 5 +- test/vs2022/etl.vcxproj.filters | 11 +- 10 files changed, 1695 insertions(+), 2 deletions(-) create mode 100644 docs/utilities/clamped-value.md create mode 100644 include/etl/clamped_value.h create mode 100644 test/syntax_check/clamped_value.h.t.cpp create mode 100644 test/test_clamped_value.cpp diff --git a/docs/utilities/clamped-value.md b/docs/utilities/clamped-value.md new file mode 100644 index 00000000..18fba437 --- /dev/null +++ b/docs/utilities/clamped-value.md @@ -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 +class clamped_value; +``` + +```cpp +etl::clamped_value value_ct; // Fixed range [2, 7]. + +etl::clamped_value value_rt(2, 7, 5); // Runtime range and initial value. + +#if ETL_HAS_FLOATING_POINT_CLAMPED_VALUE +etl::clamped_value float_ct(0.5f); +etl::clamped_value 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 value; +``` + +Creates a compile-time clamped value initialized to `Min`. + +```cpp +clamped_value value(min, max); +clamped_value 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 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. diff --git a/include/etl/clamped_value.h b/include/etl/clamped_value.h new file mode 100644 index 00000000..5448548a --- /dev/null +++ b/include/etl/clamped_value.h @@ -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 ::value> + struct traits; + + template + struct traits + { + typedef typename etl::make_signed::type difference_type; + typedef typename etl::make_unsigned::type unsigned_type; + typedef etl::numeric_limits limits_type; + typedef etl::numeric_limits difference_limits_type; + }; + + template + struct traits + { + ETL_STATIC_ASSERT(ETL_HAS_FLOATING_POINT_CLAMPED_VALUE&& etl::is_floating_point::value, + "clamped_value requires an integral type, or a floating-point type when supported"); + + typedef T difference_type; + typedef etl::numeric_limits limits_type; + typedef etl::numeric_limits difference_limits_type; + }; + + template ::value> + struct arithmetic; + + //************************************************************************* + /// Saturating arithmetic policy for integral values. + //************************************************************************* + template + struct arithmetic + { + typedef typename traits::unsigned_type unsigned_type; + + ETL_NODISCARD + static ETL_CONSTEXPR14 T advance(T value, T min_value, T max_value, typename traits::difference_type n) ETL_NOEXCEPT + { + if (n == 0) + { + return value; + } + + const unsigned_type current = static_cast(value); + const unsigned_type step = etl::absolute_unsigned(n); + + if ((n > 0) && (value < max_value)) + { + const unsigned_type distance = static_cast(max_value) - current; + + value = (step >= distance) ? max_value : static_cast(current + step); + } + else if ((n < 0) && (value > min_value)) + { + const unsigned_type distance = current - static_cast(min_value); + + value = (step >= distance) ? min_value : static_cast(current - step); + } + + return value; + } + + ETL_NODISCARD + static ETL_CONSTEXPR14 T subtract(T value, T min_value, T max_value, typename traits::difference_type n) ETL_NOEXCEPT + { + if (n == traits::difference_limits_type::lowest()) ETL_UNLIKELY + { + value = advance(value, min_value, max_value, traits::difference_limits_type::max()); + return advance(value, min_value, max_value, 1); + } + + return advance(value, min_value, max_value, static_cast::difference_type>(-n)); + } + }; + +#if ETL_HAS_FLOATING_POINT_CLAMPED_VALUE + //************************************************************************* + /// Saturating arithmetic policy for floating-point values. + //************************************************************************* + template + struct arithmetic + { + 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(value + n); + } + else if ((n < T(0)) && (value > min_value)) + { + const T distance = value - min_value; + value = (-n >= distance) ? min_value : static_cast(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 + ETL_NODISCARD ETL_CONSTEXPR14 T advance(T value, T min_value, T max_value, typename traits::difference_type n) ETL_NOEXCEPT + { + return arithmetic::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 + ETL_NODISCARD ETL_CONSTEXPR14 T subtract(T value, T min_value, T max_value, typename traits::difference_type n) ETL_NOEXCEPT + { + return arithmetic::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 + 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 + 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 + 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 + class clamped_value + { + public: + + /// The type used to advance or subtract from the stored value. + typedef typename private_clamped_value::traits::difference_type difference_type; + + /// Numeric limits for the stored value type. + typedef typename private_clamped_value::traits::limits_type limits_type; + + /// Numeric limits for difference_type. + typedef typename private_clamped_value::traits::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 + class clamped_value + { + public: + + /// The type used to advance or subtract from the stored value. + typedef typename private_clamped_value::traits::difference_type difference_type; + + /// Numeric limits for the stored value type. + typedef typename private_clamped_value::traits::limits_type limits_type; + + /// Numeric limits for difference_type. + typedef typename private_clamped_value::traits::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 diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index fbbf091e..56699a41 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -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 diff --git a/test/maincpp03check.cpp b/test/maincpp03check.cpp index a1745c6e..2c5c9936 100644 --- a/test/maincpp03check.cpp +++ b/test/maincpp03check.cpp @@ -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" diff --git a/test/meson.build b/test/meson.build index add9b29b..b4bc601d 100644 --- a/test/meson.build +++ b/test/meson.build @@ -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', diff --git a/test/syntax_check/CMakeLists.txt b/test/syntax_check/CMakeLists.txt index 3546cfa3..3c4e8e54 100644 --- a/test/syntax_check/CMakeLists.txt +++ b/test/syntax_check/CMakeLists.txt @@ -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 diff --git a/test/syntax_check/clamped_value.h.t.cpp b/test/syntax_check/clamped_value.h.t.cpp new file mode 100644 index 00000000..a09da520 --- /dev/null +++ b/test/syntax_check/clamped_value.h.t.cpp @@ -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 + +#if ETL_HAS_FLOATING_POINT_CLAMPED_VALUE +etl::clamped_value clamped_float_compile_time; +etl::clamped_value clamped_double_run_time; +#endif diff --git a/test/test_clamped_value.cpp b/test/test_clamped_value.cpp new file mode 100644 index 00000000..d74dfb13 --- /dev/null +++ b/test/test_clamped_value.cpp @@ -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 +#include + +namespace +{ + SUITE(test_clamped_value) + { + //************************************************************************* + TEST(test_compile_time_initialisation) + { + etl::clamped_value value; + const etl::clamped_value 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 value(4); + CHECK_EQUAL(2, value.min()); + CHECK_EQUAL(7, value.max()); + } + + //************************************************************************* + TEST(test_run_time_initialisation) + { + etl::clamped_value value(2, 7, 5); + etl::clamped_value default_value; + etl::clamped_value range_value(2, 7); + const etl::clamped_value 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::lowest(), default_value.min()); + CHECK_EQUAL(etl::numeric_limits::max(), default_value.max()); + CHECK_EQUAL(etl::numeric_limits::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 compile_time(1); + etl::clamped_value 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 compile_time(7); + etl::clamped_value 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 value(4); + etl::clamped_value previous_increment = value++; + CHECK_EQUAL(4, previous_increment.get()); + CHECK_EQUAL(5, value.get()); + etl::clamped_value previous_decrement = value--; + CHECK_EQUAL(5, previous_decrement.get()); + CHECK_EQUAL(4, value.get()); + } + + //************************************************************************* + TEST(test_advance_saturates) + { + etl::clamped_value 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 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 value1(4); + etl::clamped_value 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 value1(3); + etl::clamped_value value2(3); + CHECK(value1 == value2); + } + + //************************************************************************* + TEST(test_runtime_equality_uses_current_value) + { + etl::clamped_value equal_value1(2, 7, 3); + etl::clamped_value equal_value2(2, 8, 3); + etl::clamped_value 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 value1(2, 7, 5); + etl::clamped_value value2(3, 8, 6); + value1 = value2; + CHECK_EQUAL(6, value1.get()); + CHECK_EQUAL(3, value1.min()); + CHECK_EQUAL(8, value1.max()); + etl::clamped_value* 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 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 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 value(2, 7, 4); + etl::clamped_value previous_increment = value++; + CHECK_EQUAL(4, previous_increment.get()); + CHECK_EQUAL(5, value.get()); + etl::clamped_value 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 negative(-4); + etl::clamped_value wide(0); + etl::clamped_value 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 value(4); + etl::clamped_value 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 narrow(10); + etl::clamped_value 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 value(2, 7, 4); + etl::clamped_value 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 compile_time(4); + etl::clamped_value 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 compile_time(0); + etl::clamped_value run_time(-10, 10, 0); + + compile_time -= etl::clamped_value::difference_limits_type::lowest(); + run_time -= etl::clamped_value::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 value1(-4, 8, 2); + etl::clamped_value 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(7, 2)), etl::exception); + CHECK_THROW((etl::clamped_value(7, 2, 4)), etl::exception); + + etl::clamped_value 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 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 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 compile_time(0.25f); + etl::clamped_value 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 compile_time1(-0.5); + etl::clamped_value compile_time2(1.5); + etl::clamped_value run_time1(-3.0, 3.0, -1.0); + etl::clamped_value 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::infinity(); + etl::clamped_value value(0.0); + etl::clamped_value 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::max(); + etl::clamped_value 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::quiet_NaN(); + CHECK_THROW((etl::clamped_value(nan, 1.0)), etl::exception); + CHECK_THROW((etl::clamped_value(-1.0, nan)), etl::exception); + CHECK_THROW((etl::clamped_value(-1.0, 1.0, nan)), etl::exception); + + etl::clamped_value 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 value; + CHECK_EQUAL(etl::numeric_limits::lowest(), value.min()); + CHECK_EQUAL(etl::numeric_limits::max(), value.max()); + CHECK_EQUAL(etl::numeric_limits::lowest(), value.get()); + } +#endif + +#if ETL_USING_CPP14 + //************************************************************************* + TEST(test_clamped_value_constexpr_ctor) + { + constexpr etl::clamped_value 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 value(0.5); + static_assert((value.get() > 0.49) && (value.get() < 0.51), "constexpr floating clamped_value constructor"); + CHECK(true); + } + #endif +#endif + } +} // namespace diff --git a/test/vs2022/etl.vcxproj b/test/vs2022/etl.vcxproj index 503563eb..7a69c37e 100644 --- a/test/vs2022/etl.vcxproj +++ b/test/vs2022/etl.vcxproj @@ -3764,6 +3764,7 @@ + @@ -6303,6 +6304,7 @@ true true + true true @@ -10446,6 +10448,7 @@ + @@ -11804,4 +11807,4 @@ - \ No newline at end of file + diff --git a/test/vs2022/etl.vcxproj.filters b/test/vs2022/etl.vcxproj.filters index f20f1a7a..10afee24 100644 --- a/test/vs2022/etl.vcxproj.filters +++ b/test/vs2022/etl.vcxproj.filters @@ -261,6 +261,9 @@ ETL\Utilities + + ETL\Utilities + ETL\Utilities @@ -2087,6 +2090,9 @@ Tests\Algorithms + + Tests\Algorithms + Tests\Algorithms @@ -2909,6 +2915,9 @@ Tests\Syntax Checks\Source + + Tests\Syntax Checks\Source + Tests\Syntax Checks\Source @@ -4115,4 +4124,4 @@ Resource Files\Images - \ No newline at end of file +