ETLCPP / etl

Embedded Template Library
https://www.etlcpp.com
MIT License
2.04k stars 371 forks source link

Add an integrator to the library #894

Open drewr95 opened 1 month ago

drewr95 commented 1 month ago

I didn't see one when searching your library, if I missed it my apologies.

I have implemented my own that I think would do well here :). It is very similar to _cyclicvalue except it's clamped to the min and max range. It also has a compile-time MIN and MAX similar to cyclic value to save RAM.

Let me know if you'd like me to add it.

jwellbelove commented 1 month ago

Could you show an example of how it would be used?

drewr95 commented 1 month ago

Could you show an example of how it would be used?

Sure thing. I'll just paste the main incrementing and decrementing unittests. One for compile-time known min and maxes and run-time (I have 100% unit test coverage):

/**
 * @brief Tests preincrementing the integrator.
 */
TYPED_TEST(CompileTimeIntegratorTest, PreIncrement)
{
    constexpr TypeParam min{2};
    constexpr TypeParam max{10};
    common::Integrator<TypeParam, min, max> integrator{min};
    for (auto i = min; i < max; ++i)
    {
        EXPECT_EQ(i + 1, ++integrator);
    }
    EXPECT_EQ(max, integrator);

    // Try to increment past max.
    EXPECT_EQ(max, ++integrator);
}

/**
 * @brief Tests postincrementing the integer.
 */
TYPED_TEST(CompileTimeIntegratorTest, PostIncrement)
{
    constexpr TypeParam min{2};
    constexpr TypeParam max{10};
    common::Integrator<TypeParam, min, max> integrator{min};
    for (auto i = min; i < max; ++i)
    {
        EXPECT_EQ(i, integrator++);
    }
    EXPECT_EQ(max, integrator);

    // Try increment past max.
    integrator++;
    EXPECT_EQ(max, integrator);
}

/**
 * @brief Tests predecrementing the integrator.
 */
TYPED_TEST(CompileTimeIntegratorTest, PreDecrement)
{
    constexpr TypeParam min{2};
    constexpr TypeParam max{10};
    common::Integrator<TypeParam, min, max> integrator{max};
    for (auto i = max; i > min; --i)
    {
        EXPECT_EQ(i - 1, --integrator);
    }
    EXPECT_EQ(min, integrator);

    // Try to decrement past min.
    EXPECT_EQ(min, --integrator);
}

/**
 * @brief Tests the special handling when the integrator min is 0.  This is a
 * special case because the value can't be subtracted normally when the type is
 * unsigned.  This is because of unsigned integeger wrapping back to the max
 * value.
 */
TYPED_TEST(CompileTimeIntegratorTest, PreDecrementMin0)
{
    constexpr TypeParam min{0};
    constexpr TypeParam max{1};
    common::Integrator<TypeParam, min, max> integrator{max};
    EXPECT_EQ(min, --integrator);

    // Try to subtract past min.
    EXPECT_EQ(min, --integrator);
}

/**
 * @brief Tests postdecrementing the integrator.
 */
TYPED_TEST(CompileTimeIntegratorTest, PostDecrement)
{
    constexpr TypeParam min{2};
    constexpr TypeParam max{10};
    common::Integrator<TypeParam, min, max> integrator{max};
    for (auto i = max; i > min; --i)
    {
        EXPECT_EQ(i, integrator--);
    }
    EXPECT_EQ(min, integrator);

    // Try to decrement past min.
    integrator--;
    EXPECT_EQ(min, integrator);
}

/**
 * @brief Tests preincrementing the integrator.
 */
TYPED_TEST(RunTimeIntegratorTest, PreIncrement)
{
    constexpr TypeParam min{2};
    constexpr TypeParam max{10};
    common::Integrator<TypeParam> integrator{min, max};
    for (auto i = min; i < max; ++i)
    {
        EXPECT_EQ(i + 1, ++integrator);
    }
    EXPECT_EQ(max, integrator);

    // Try to increment past max.
    EXPECT_EQ(max, ++integrator);
}

I only show one example of the run time because it follows similarly.

jwellbelove commented 1 month ago

Yeah, that looks good. Do a PR if you like.

jwellbelove commented 1 month ago

Does the integrator also include other operators as well as ++ and --? +

+= -=

drewr95 commented 1 month ago

Yeah, that looks good. Do a PR if you like.

Okay sounds good!

Does the integrator also include other operators as well as ++ and --?

+ += -=

It does have ++ and -- as I show in the test. Testing both pre and post decrement. I did not implement += and -=. I don't think integrators do that though I don't see why I couldn't add that.

drewr95 commented 1 month ago

Fo the += and the -=, does the value get clamped to the max and min respectively? say value is 1 and the range is [0, 2], and I do integrator += 5; Does the integrator get clamped to 2 or is the value ignored? I assumed clamped but wanted to clarify

jwellbelove commented 1 month ago

I assumed that the value would always be clamped.

jwellbelove commented 1 month ago

I been giving the idea some thought over the past few days and I think that the 'integrator' idea is just a specific case of a more general functionality. I'm seeing it more as a specialisation of a numeric type. Sort of a 'clamped' numeric, with implicit casts to and from the underlying type.

template <typename T, T Minimum = etl::intergral_limits<T>::min, T maximum = etl::intergral_limits<T>::max>
class numeric
{
  //...
};

or possibly an accumulator type, with min/max bounds.

drewr95 commented 1 month ago

I been giving the idea some thought over the past few days and I think that the 'integrator' idea is just a specific case of a more general functionality. I'm seeing it more as a specialisation of a numeric type. Sort of a 'clamped' numeric, with implicit casts to and from the underlying type.

template <typename T, T Minimum = etl::intergral_limits<T>::min, T maximum = etl::intergral_limits<T>::max>
class numeric
{
  //...
};

or possibly an accumulator type, with min/max bounds.

Yeah that could work. Though if we do that, maybe you just want to extend type_def? You already did most of the work with that class, and I feel something like numeric is exactly that. Just with specified limits.

Also, if this supports floating point types, I don't think you can have a compile time specialization because of the template check would fail due to floating point equality comparisons.

jwellbelove commented 1 month ago

Yes, you're right. Maybe the original idea of an accumulator type similar to the implementation of etl::cyclic_value is the better option.

The ETL has got so many features now that I sometimes forget what's already been implemented!

drewr95 commented 1 month ago

Yes, you're right. Maybe the original idea of an accumulator type similar to the implementation of etl::cyclic_value is the better option.

Yeah I think I like the accumulator type as well :]

The ETL has got so many features now that I sometimes forget what's already been implemented!

You have made embedded development exponentially less of a pain with this library, it is amazing.

drewr95 commented 1 month ago

Seems like an accumulator is slightly different in controls.

I don't think it would take long to implement (basically just implementing operator+ and operator- as well). Would you like me to implement both? If so I will create a separate issue for accumulator.

Also looks like Boost has accumulators might want to look at those as well

jwellbelove commented 1 month ago

What do you see as the fundamental difference in functionality of accumulator and integrator?

drewr95 commented 1 month ago

What do you see as the fundamental difference in functionality of accumulator and integrator?

Honestly I'm not even sure myself lol. I'll go ahead and implement it, if it's wrong it can always be fixed.