2

This question refers to this one, but applying to floating point numbers too.

In short, if count reaches min or max bound it should wrap to opposite bound taking into account remainder of step value overflow (for instance if bounds are 0 ... 10.5, value is 3 and step is -7.5). Also we should handle case when step value is greater than range value.

Ranges may be arbitrary (negative, positive or mixed) but taking into account given asserts.

So far I have this method which works for integers, but gives wrong result for doubles whenever step is fractional.

Also not sure we should use epsilon approach here while comparing doubles.

Question is how to implement this method properly for fractional values.

Runnable code

#include <iostream>
#include <cmath>
#include <assert.h>

double
countInRange(const double step, const double value, const double minValue, const double maxValue)
{
    assert(minValue < maxValue);
    assert(value >= minValue && value <= maxValue);

    auto range = maxValue - minValue + 1;
    assert(fabs(step) <= range);

    auto result = value + step;

    if (result < minValue) {
        result += range;
    } else if (result > maxValue) {
        result -= range;
    }

    return result;
}

int main()
{
    double v = 358;

    printf("increment\n");

    for (int i = 0; i < 10; i++) {
        v = countInRange(0.5, v, 0, 359.5);
        printf("%f\n", v);
    }

    printf("decrement\n");

    v = 2;

    for (int i = 0; i < 10; i++) {
        v = countInRange(-0.5, v, 0, 359.5);
        printf("%f\n", v);
    }
}

Edit

So I ended up with two methods for different interval logics, the first one taken from answer of Superlokkus, they may not cover all cases, but for now it's enough, thanks to all the commenters

    /**
     * Wraps to minValue or maxValue plus step using half-open interval logic
     */
    double
    countInRange(const double step, const double value, const double minValue, const double maxValue)
    {
        assert(minValue < maxValue);
        assert(value >= minValue && value <= maxValue);

        auto range = maxValue - minValue + 1;

        assert(fabs(step) <= range);

        auto result = value + step;

        if (result < minValue) {
            result = maxValue - abs(minValue - result);
        } else if (result > maxValue) {
            result = minValue + (result - maxValue);
        }

        return result;
    }
    /**
     * Wraps to minValue or maxValue using closed interval logic
     */
    double
    countInRange(const int step, const double value, const double minValue, const double maxValue)
    {
        assert(minValue < maxValue);
        assert(value >= minValue && value <= maxValue);

        auto range = maxValue - minValue + 1;

        assert(abs(step) <= range);

        auto result = value + step;

        if (result < minValue) {
            result += range;
        } else if (result > maxValue) {
            result -= range;
        }

        return result;
    }
schmidt9
  • 4,436
  • 1
  • 26
  • 32
  • 4
    This is going to be difficult with floating point types since [they are inaccurate](https://stackoverflow.com/questions/588004/is-floating-point-math-broken). If you can, I would suggest using integers, and just scaling them. So if your range is from [-100, 10] step by 3.2, the instead make the range [-1000, 100] and step by 32. That way you get exact "counting" and then you can convert back to a floating point type if needed. – NathanOliver Oct 08 '19 at 13:32
  • @YSC updated question: " Question is how to implement this method properly for fractional values." – schmidt9 Oct 08 '19 at 13:32
  • @NathanOliver could we also apply rounding here instead of scaling? – schmidt9 Oct 08 '19 at 13:42
  • You can use rounding or an epsilon instead of scaling. I just like scaling as it lets me do my math in integer space. – NathanOliver Oct 08 '19 at 13:43
  • I think, as a starting-point, `std::fmod(x,y)` is your friend, here. – Adrian Mole Oct 08 '19 at 13:49
  • As I thought about it, I think a solution to "have software floating point wrap around, using the built in floating points" is not portable possible. Either you would have to write your own software floating point type, or have no arbitary wrap around. Only if we modify your requirement with: Even for a small step, if it would wrap at least, but with loss of precision of the remainder or simply no wrap around for small steps. Whats it gonna be? – Superlokkus Oct 08 '19 at 13:52
  • @Superlokkus I think rounding/scaling to integer would be acceptable here since high precision is not required here, this method is going to be used for counting 1-2 decimal places fractionals – schmidt9 Oct 08 '19 at 14:35
  • 2
    First clarify the function you really want. The range is defined oddly, as one more than `maxValue-minValue`. Consider the effect when you are stepping from 0 with bounds of 0 to 4 by steps of .5. The `range` is 5. The steps will go 0, .5, 1, 1.5, 2, 2.5, 3, 3.5, 4. Then the candidate for the next step is 4.5. This exceeds the `maxValue` of 4, so the `range` of 5 is subtracted, yielding −.5. That is outside the bounds of 0 to 4, so it does not seem like what is intended. Would it be better to specify the bounds as a half-open interval [a, b), that includes a but not b, with a `range` of `b-a`? – Eric Postpischil Oct 08 '19 at 19:26
  • 2
    Your question says this code “gives wrong result for doubles whenever `step` is fractional.” What is wrong? Show examples of wrong results and the corresponding right results. – Eric Postpischil Oct 08 '19 at 19:27
  • @EricPostpischil you've actually given a good example yourself, my question is exactly how to make my code to work correctly with floats. Yes, given interval [0,4], if we use integer step (1), the code gives right result, wrapping from 4 to 0 and vice versa, but not with fractional step values, the idea is to make an universal method working with both integral and floating numbers, I'd kindly ask about correction of my code, sorry if I couldn't express my idea more clearly – schmidt9 Oct 09 '19 at 06:40
  • 1
    Show examples of wrong results and the corresponding right results. – Eric Postpischil Oct 09 '19 at 10:44
  • For positive `step`, Try `range = maxValue - minValue + 1;` --> `range = maxValue - minValue + step;` – chux - Reinstate Monica Oct 09 '19 at 13:11
  • " I think rounding/scaling to integer would be acceptable here since high precision is not required here" wouldn't that mean you simply could apply your referred question for integers? I am a bit confused, could you please elaborate more, what behavior you expect i.e. need? – Superlokkus Oct 10 '19 at 09:19
  • @Superlokkus I mean rounding to some precision _or_ scaling to integers (btw I tested the last suggestion, it is just the same wrong result), the point was to make the above code work with fractional values too, I don't know now to make it work, just run my example and you'll see, it works with integers, but not with fractionals, good example was given by Eric Postpischil in above comment with range [0,4] and step 0.5, where it wraps to -0.5, but not to 0 as required. This questions is actually not about precision, that's what I meant – schmidt9 Oct 10 '19 at 11:50
  • @schmidt9 Did you see my answer? – Superlokkus Oct 10 '19 at 12:00

2 Answers2

1

Is this what you had in mind?

#include <iostream>
#include <iomanip>
#include <stdexcept>
#include <cmath>

constexpr double
countInRange(const double step, const double value, const double minValue, const double maxValue)
{
    if (minValue > maxValue) {
        throw std::invalid_argument("minValue > maxValue");
    }

    auto result{value};
    result += step;

    if (result > maxValue) {
        result = minValue + (result - maxValue);
    } else if (result < minValue) {
        result = maxValue - std::abs(minValue - result);
    }

    return result;
}

int main()
{
    double v = 358;

   std::cout << std::showpoint <<"increment\n";

    for (int i = 0; i < 10; i++) {
        v = countInRange(0.5, v, 0, 359.5);
        std::cout << v << "\n";
    }

    std::cout << std::showpoint << "decrement\n";

    v = 2;

    for (int i = 0; i < 10; i++) {
        v = countInRange(-0.5, v, 0, 359.5);
        std::cout << v << "\n";
    }
}

Output:

increment 358.500 359.000 359.500 0.500000 1.00000 1.50000 2.00000 2.50000 3.00000 3.50000 decrement 1.50000 1.00000 0.500000 0.00000 359.000 358.500 358.000 357.500 357.000 356.500

I removed the assert, since they are not suitable for argument checking, since they are non portable deterministic left out, and can't be handled. Also since you seem to intend to write C++, I corrected the output usage to best C++ pratice.

Superlokkus
  • 4,731
  • 1
  • 25
  • 57
  • Thanks for your answer, unfortunately I see here 2 issues. For given range [0,359.5] at increment value 0 is skipped (359.500 _[0]_ 0.500000), at decrement value 359.5 is skipped (0.00000 _[359.5]_ 359.000) – schmidt9 Oct 10 '19 at 18:24
  • Huh this intervals-fuss drives me crazy, I really don't know any more how it should be done, now I'm tending you solution is the right one, because min and max values can be considered as just the same, like on clock we consider that 00:00 is the same as 24:00 – schmidt9 Oct 11 '19 at 09:14
  • so if we add 0.5 to 359.5, it is the same as we'd add it to 0 – schmidt9 Oct 11 '19 at 09:19
  • So as per Eric Postpischil's comments we should consider here half-open intervals - `(0, 359.5]` on increment and `[0, 359.5)` on decrement, it is actually done in your solution – schmidt9 Oct 11 '19 at 09:23
  • Well I am glad to hear that it seems my solution fits your needs. Take your time to evaluate, try to either more formalise your needs mathematical or (incl. or) write down all significant behaviours you need, and add all that to your question. Of course I things still fit, I really would appreciate an upvote i.e. accept of my answer. – Superlokkus Oct 11 '19 at 09:39
  • 1
    Hmm interestingly AppCode complains about `result` were not assignable, obviously getting `const` too, `auto result = value;` fixes it – schmidt9 Oct 11 '19 at 10:29
  • schmidt9 Which xcode version? Did you specific at least C++11 as language standard? – Superlokkus Oct 11 '19 at 20:55
  • Latest version, using c++14 – schmidt9 Oct 12 '19 at 07:36
  • 1
    Peculiar, couldn't reproduce it with Xcode Version 11.1 (11A1027), nor gcc nor MSVC. But as you already figured out you can also write auto x = {} – Superlokkus Oct 12 '19 at 12:49
  • Well after upgrade MacOS to Catalina it compiles now, even with AppCode, AppCode still underlines this case with red line though – schmidt9 Oct 12 '19 at 14:01
-4

You could use pointers to float variables in function arguments. This will allow to avoid correction of values while transferring them via stack.

This way looks a little bit dirty but if you want to save precision of arguments it should work.

AntonT
  • 1
  • 3
    This does not make any sense. Floating point is always accurate but not necessarily precise. And using them via pointers or values has no effect. – Richard Critten Oct 08 '19 at 14:16