3

I've got some code that calculates a cyclic offset for a given integer, and I want it to calculate the cyclic-offset such that the function's output will not show any anomalous behavior as the input value transitions from positive to negative and back.

Here's my current implementation, which appears to work correctly, AFAICT:

 #include <stdio.h>

 unsigned int PositiveModulus(int value, unsigned int divisor)
 {
    if (value < 0)
    {
       const int numCyclesToAdd = 1+((-value)/divisor);
       value += numCyclesToAdd*divisor;
    }
    return value%divisor;
 }

 // unit test
 int main(int argc, char ** argv)
 {
    for (int i=-10; i<=10; i++) printf("%i -> %u\n", i, PositiveModulus(i,5));
    return 0;
 }

... in particular, when I run the above, I get the following output, which demonstrates the behavior I want:

 $ ./a.out
 -10 -> 0
 -9 -> 1
 -8 -> 2
 -7 -> 3
 -6 -> 4
 -5 -> 0
 -4 -> 1
 -3 -> 2
 -2 -> 3
 -1 -> 4   // note output transitions from 4 to 0 on these two
 0 -> 0    // lines, just like every other iteration of the cycle
 1 -> 1
 2 -> 2
 3 -> 3
 4 -> 4
 5 -> 0
 6 -> 1
 7 -> 2
 8 -> 3
 9 -> 4
 10 -> 0

My question is, does this function have a well-known/official name? (Searching for "positive modulus" returns some results, but the function described in those results doesn't seem to behave the same as the above code)

... and a bonus question is: Is the function shown above the best way to implement this behavior, or is there another approach that is more concise and/or mathematically correct?

Jeremy Friesner
  • 70,199
  • 15
  • 131
  • 234
  • 1
    The answer to the name will be better posed on math.stackexchange.com. Generally, the concept you're looking for is "congruence class" in the sense it is used in modular arithmetic. Well, technically, you're computing a "representative of the congruence class or residue", choosing the smallest positive representative in the set. The [Wikipedia page](https://en.wikipedia.org/wiki/Modular_arithmetic#Congruence_classes) is somewhat opaque, but it's a start. – lockcmpxchg8b Aug 22 '18 at 03:41
  • 2
    It's maybe worth a second comment to point out that the function you describe is the normal modulus function from the perspective of discrete math/abstract algebra. The implementation of modulus you get in programming languages and computers is the one that's baffling. But in the ring of integers modulo `n`, the values `-1`, `n-1`, `2n-1`, `...` are all in the same class, so `-1` can be read as `n-1`. In your example, `n=5`. – lockcmpxchg8b Aug 22 '18 at 03:52
  • 1
    @lockcmpxchg8b: modulo, not modulus. Modulus is the absolute value or norm. – Cris Luengo Aug 22 '18 at 04:11
  • @CrisLuengo: oops. you're correct. The operation I meant is modulo. (I was not aware of that usage of "modulus", I've only heard that term for the second operand of the modulo operation) – lockcmpxchg8b Aug 22 '18 at 04:20
  • @lockcmpxchg8b: I'm looking into this [tag:modulo] / [tag:modulus] thing. They both seem to be used mostly for the same purpose. Seems nobody cares as much about this as I do... https://meta.stackoverflow.com/questions/274503/clean-up-modulus-and-modulo – Cris Luengo Aug 22 '18 at 04:22
  • Why not normalize after the modulo operator? Much simpler logic – Mad Physicist Aug 22 '18 at 04:30
  • 1
    @CrisLuengo: Well, "modulo" is unambiguous, which makes it the clearly superior choice. Changing definitions to match incorrect usage, [as with "literally"](https://www.merriam-webster.com/dictionary/literally) just hurts. – lockcmpxchg8b Aug 22 '18 at 04:33
  • @lockcmpxchg8b Perhaps a math.stackexchange.com review would be useful, yet OP's code has troubles with the full `int` range for `value` - and issues like that are better addressed on SO. It has 2 case of UB (both `int` overflow): `PositiveModulus(INT_MIN, divisor)` and `PositiveModulus(INT_MIN+1, 1)` – chux - Reinstate Monica Aug 22 '18 at 04:41
  • 1
    @MadPhysicist Re: "Why not normalize after the modulo operator? " --> There are corner cases given the `int/unsigned` mix applied to `%` that make that approach challenging. Yet if code used `int divisor`, that approach would work for `divisors` in the reduced positive range of `int`. – chux - Reinstate Monica Aug 22 '18 at 04:47

2 Answers2

4

What is this zero-invariant modulus function called?

A potential C-like name is Euclidean modulo.
Just mod(), @lockcmpxchg8b, would be OK if parameters were both int, yet int value, unsigned divisor is a special combination.


Here's my current implementation, which appears to work correctly, AFAICT:

Code has avoidable corner issues on this line of code:

const int numCyclesToAdd = 1+((-value)/divisor);

PositiveModulus(INT_MIN, any_divisor)
// UB (undefined behavior)
// int overflow with -INT_MIN

PositiveModulus(INT_MIN+1, 1)
// Implementation defined behavior on conversion `unsigned` to `int` during assignment.
// int numCyclesToAdd = 1+((-(INT_MIN+1)/1u);

Candidate improvement useful when * is expensive.

Even works when value == INT_MIN and avoids the UB of OP's (-INT_MIN).

unsigned PositiveModulus(int value, unsigned divisor) {
  unsigned result;
  if (value < 0) {
    unsigned uv = -1 - value;
    uv %= divisor;
    result = divisor - 1 - uv;
  } else {
    result = value%divisor;
  }
  return result;
}
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
0

Not sure about the name of the function, but there is a slightly better way.

If you simply return value%divisor from the function and change divisor and the return value to int, you get this:

-10 -> 0
-9 -> -4
-8 -> -3
-7 -> -2
-6 -> -1
-5 -> 0
-4 -> -4
-3 -> -3
-2 -> -2
-1 -> -1
0 -> 0
1 -> 1
2 -> 2
3 -> 3
4 -> 4
5 -> 0
6 -> 1
7 -> 2
8 -> 3
9 -> 4
10 -> 0

Note that you can go from the negative mod to the positive mod by just adding the divisor. So you can simplify the function like this:

unsigned int PositiveModulus(int value, int divisor)
{
    int result = value%divisor;
    if (result < 0) {
        return result + divisor;
    } else {
        return result;
    }
}

Note that it's necessary to change the type of divisor to int instead of unsigned, otherwise a negative value gets converted to a large unsigned values.

dbush
  • 205,898
  • 23
  • 218
  • 273
  • 1
    Try this code's `PositiveModulus(some_negative_value, 7)` provides different results than OP's. This one relies on `INT_MAX%divisor == 0`. – chux - Reinstate Monica Aug 22 '18 at 03:55
  • @chux Looks like a signed to unsigned conversion messed things up. Fixed by changing `divisor` from `unsigned int` to `int`. Not much reason to have it unsigned anyway. – dbush Aug 22 '18 at 12:19
  • A reason for `unsigned divisor` vs. `int` divisor is to support `divisor == INT_MAX+1` - a reasonable case OP may need. – chux - Reinstate Monica Aug 22 '18 at 13:48