distractedhamster
distractedhamster

Reputation: 777

How to simply loop a custom scale

This must be really simple math but I don't know how to solve it.

I need to write a function that returns a number on a scale from 1 to 3 like this:

input: -5 output: 1
input: -4 output: 2
input: -3 output: 3
input: -2 output: 1
input: -1 output: 2
input: 0  output: 3
input: 1  output: 1
input: 2  output: 2
input: 3  output: 3
input: 4  output: 1
input: 5  output: 2
input: 6  output: 3
input: 7  output: 1

The following function works great with positive numbers

static const int getOnScaleOneToThree(int input)
{
    int x = ceil(input / 3);
    return abs((input - (x * 3)));
}

But it inverts (of course) with negative input like this:

input: -6 output: 3
input: -5 output: 2
input: -4 output: 1
input: -3 output: 3
input: -2 output: 2
input: -1 output: 1
input: 0  output: 3
input: 1  output: 1
input: 2  output: 2
input: 3  output: 3

Any ideas? Thanks!

Upvotes: 1

Views: 179

Answers (6)

Aberrant
Aberrant

Reputation: 3431

Alright I've been contemplating this for a bit, but based on this:

https://meta.stackexchange.com/q/213976/171816

I'm just going to post this as an extra answer, referencing the answer on that other question.


The other answer has a solution more elegant than my first answer: https://stackoverflow.com/a/1082938/982107

by writing the modulo function as

static int mod(int x, int m)
{
    int r = x%m;
    return r<0 ? r+m : r;
}

you will get the right answers for this problem even when x is negative.

This will allow you to write the function like so:

static int getOnScaleOneToThree(int input)
{
    return (mod((input + 2), 3) + 1);
}

Here's a general solution again, this time keeping the amount of code to a minimum:

static int getOnScale(int input, int minVal, int maxVal, int zeroVal)
{
    int dividend = input;
    if(minVal > maxVal)
    {
        //Alter values for reverse scale
        std::swap(minVal, maxVal);
        dividend *= -1;
    }
    dividend += (zeroVal - minVal);
    int divisor = maxVal - minVal + 1;
    return (mod(dividend , divisor) + minVal);
}

Upvotes: 1

Aberrant
Aberrant

Reputation: 3431

This should work, and avoids the implementation-dependent outcome of a modulo operation with a negative number.

if(input >= 0)
{
    return (1 + ((input + 2) % 3)); 
}
else
{
    return (3 - ((-input) % 3));
}

Additionally, here's a more general (and verbose) solution:

static const int getOnScale(int input, int minVal, int maxVal, int zeroVal)
{
    if(input == 0)
    {
        //Might as well return this right away.
        return zeroVal;
    }
    else
    {
        if(maxVal >= minVal)
        {
            int divisor = maxVal - minVal + 1;
            if(input > 0)
            {
                int dividend = input + zeroVal - minVal;
                return (minVal + (dividend % divisor));
            }
            else
            {
                int dividend = maxVal - zeroVal - input;
                return (maxVal - (dividend % divisor));
            }
        }
        else
        {
            //Reverse scale
            int divisor = minVal - zeroVal + 1;
            if(input > 0)
            {
                int dividend = minVal - zeroVal + input;
                return (minVal - (dividend % divisor));
            }
            else
            {
                int dividend = -input + zeroVal - maxVal;
                return (maxVal + (dividend % divisor));
            }
        }
    }
}

In this question's case, this function would be called as:

getOnScale(input, 1, 3, 3);

Upvotes: 2

Component 10
Component 10

Reputation: 10497

Try:

int out( int in )
{
    return ( in >= 0 ) ? ( ( in + 2 ) % 3 ) + 1 : 3 - ( -1 * in % 3 ); }
}

Good point about the implementation-defined behaviour when the dividend goes below zero and the divisor is positive.

Upvotes: 1

It's important not to get tangled up in premature optimization, so the following is probably sufficient.

long get_remainder(long n) {
    long * remainders = new[(unsigned int)0xffffffff];

    long remainder = 1;
    for ( long i = LONG_MIN; i < LONG_MAX; ++i ) {
        remainders[i + -LONG_MIN] = remainder++;
        if ( remainder > 3 )
            remainder = 1;
    }

    int rtn = remainders[n + -LONG_MIN];

    delete[] remainders;

    return rtn;
}

Use like so:

for ( int i = -10; i < 10; ++i )
{
    int remainder = get_remainder(i);
}

Upvotes: 0

alain
alain

Reputation: 12047

static const int getOnScaleOneToThree(int input)
{
    return (int)((unsigned int)(input + 0x80000000)%3 + 1);
}

This works if int is 32 bits.

Upvotes: 1

ezaquarii
ezaquarii

Reputation: 1916

You can use LUT (look-up table). Here is some pseudocode:

int output[] = { 3, 2, 1, 3, 2, 1, 3, 1, 2, 3 };
assert(input >= -6 && input <= 3);
int index = input+6;
int value = output[index];

LUTs are super-fast, easy to maintain and perfect for strange mappings that are difficult to calculate at runtime.

They pose a challange when the input range is large - memory requirements can obviously reach the stratosphere. There are solutions for that, such as:

  1. approximation using linear functions (map range of input to linear function)
  2. approximation using polynominals
  3. range maps

Upvotes: 1

Related Questions