6

I have an angle and I need to return a representative angle in the range [-180:180].

I have written a function to do this but it seems such a simple process, I was wondering if there was an operator or function that already did this:

int func(int angle){
    angle %= 360;

    if(angle > 180){
        angle -=360;
    }else if(angle < -180){
        angle += 360;
    }   
    return angle;
}

I've made a live example for testing expected functionality.

Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • 2
    Aren't you simply looking for `return std::remainder(angle, 180);` ? – atlaste Jul 21 '15 at 14:31
  • 1
    Can you please choose one of C and C++ for this? – fuz Jul 21 '15 at 14:32
  • @atlaste Props for something that solves my examples, but this sadly will not work. For example an input of -190 should give 170, but `remainder(-190, 180)` is -10. – Jonathan Mee Jul 21 '15 at 14:54
  • @FUZxxl I'm using C++, but I don't think there is a function available in the standard library to solve this. So I expect the solution will fall in the intersection of the languages. I rather expect some magical C hack to save me. If you feel that leaves the question too broad though, feel free to remove the [C] tag. – Jonathan Mee Jul 21 '15 at 15:07
  • What's the problem with just using this function you just wrote? It looks fine to me and the machine code generated is pretty okay, too. – fuz Jul 21 '15 at 15:11
  • @FUZxxl There is no problem with it and it is what I'm currently using. I just thought that there would be something better to solve it. If there isn't I'm OK with this function. – Jonathan Mee Jul 21 '15 at 15:20
  • Note: The tricky bit is that the result is [-180 to +180] _inclusive_ or 361 different answers. It is an easier problem if the result was [-180 to +179], 360 different answers – chux - Reinstate Monica Jul 21 '15 at 15:44
  • @chux Interesting point. There isn't actually a need to support +180. I could just say [-180:180). I just hadn't thought about it. Does that open the possibility for some clever-er math? – Jonathan Mee Jul 21 '15 at 16:51
  • 1
    @Jonathan Mee Robust code works in the middle and at edge cases too. More options allow for more clever math, sometimes too clever. Case in point [Jonathan Mee](http://stackoverflow.com/users/2642059/jonathan-mee) pointed out a mistake in my [answer](http://stackoverflow.com/a/31544165/2410359) (now corrected). – chux - Reinstate Monica Jul 21 '15 at 17:08
  • @FUZxxl Looks like [`remainder(angle, 360.0)` is the solution](http://stackoverflow.com/a/31544165/2642059). Which is available in both C++ and C, so I'm planning on leaving both tags. – Jonathan Mee Jul 21 '15 at 19:37
  • @atlaste You were closer than I realized, looks like [`remainder(angle, 360)` is the solution](http://stackoverflow.com/a/31544165/2642059). – Jonathan Mee Jul 21 '15 at 19:38

3 Answers3

3

I don't know of a standard operator or function, but you can do it in a single expression:

int func(int angle) {
    return ((((angle + 180) % 360) + 360) % 360) - 180;
}

Note: My original answer used the following expression:

((angle + 180) % 360) - 180;

This is far neater, but relies on the modulus of a negative number being positive. Some languages (such as Python) have these semantics, but C and C++ typically don't. The above expression accounts for this by adding an extra shift of 360.

Rob Hague
  • 1,409
  • 9
  • 14
  • 1
    @MarioZannone -190 is a good example of how this implementation fails. It should return 170, but instead it returns -190, which is *not* greater than -180. This solution does *not* solve the problem. – Jonathan Mee Jul 21 '15 at 14:45
  • Damn. My recollection was that the C and C++ % operator had the same semantics as Python, so I took the shortcut of testing in a Python interpreter instead. I'll update the question with a corrected version. – Rob Hague Jul 21 '15 at 15:18
  • @RobHague [C](http://en.cppreference.com/w/cpp/language/operator_arithmetic#Multiplicative_operators) and [Python](https://docs.python.org/3.3/reference/expressions.html#binary-arithmetic-operations)'s `operator%` do work the same: `(a / b) * b + a % b == a` which in Python is: `(a // b) * b + a % b == a` – Jonathan Mee Jul 21 '15 at 15:29
  • 2
    @JonathanMee, no. In Python, "The modulo operator always yields a result with the same sign as its second operand", this (-3)%2==1. In C++ it is -1. – Petr Jul 21 '15 at 15:33
  • @Petr Groan, this is what I get for testing on a C++03 compiler: "Until C++11, if one or both operands to binary `operator %` were negative, the sign of the remainder was implementation-defined, as it depends on the rounding direction of integer division. The function `std::div` provided well-defined behavior in that case." [[source](http://en.cppreference.com/w/cpp/language/operator_arithmetic#Multiplicative_operators)] – Jonathan Mee Jul 21 '15 at 16:55
  • @JonathanMee, interesting, did not know it was implementation-defined before c++11. Anyway, c++11 behavior (and behavior of major c++03 compilers on major architectures, which is the same) does not match that of Python. – Petr Jul 21 '15 at 19:56
  • 1
    @Petr Yeah, I've also learned today that [`remainder`](http://stackoverflow.com/a/31544165/2642059) and [`fmod`](http://en.cppreference.com/w/cpp/numeric/math/fmod) may have differing signs as well. – Jonathan Mee Jul 21 '15 at 20:00
3

Code is optimal or at least nearly so. Some platforms may work better with some variation.

There is not a single C integer operator that handles this.

The challenges to this is the problem is that the range of results is [-180:180] and this is 361 different values. It is unclear if it is allowed to have func(180) return -180.

The next challenge is to have code work over the entire [INT_MIN...INT_MAX] range as angle + 180 can overflow. angle %= 360; takes care of that.

Following is a effectively a variation of OP's code which may run faster on pipe-lined machines. It only does one % operation - conceivably the most expensive. Positive angle returns [-179:180] and negative angle returns [-180:179]

int func2(int angle) {
  angle %= 360; 
  return angle + 360*((angle < -180) - (angle > 180));
}

Following is a one-liner that returns values [-180:179]. It does not use angle + 180 as that may overflow.

int func3(int angle) {
  return ((angle % 360) + (360+180))%360 - 180;
}

There is the <math.h> function double remainder(double x, double y); that closely meets OP's goal. (Maybe available since C99.) It will return FP values [-180:180]. Note: int could have an integer range that exceeds what double can represent exactly.

int func4(int angle) {
  angle = remainder(angle, 360.0);
  return angle;
}
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
0

What you need is simple wrap function implementation:

#include <stdio.h>

int wrap(int value, int lower_bound, int upper_bound) {
    int range = upper_bound - lower_bound;
    value -= lower_bound; // shift from [lower, upper) to [0, upper - lower)...
    value %= range;       // ... so modulo operator could do all the job
    if (value < 0) {      // deal with negative values
        value += range;
    }
    value += lower_bound; // shift back to [lower, upper)
    return value;
}

void show(int value, int lower_bound, int upper_bound) {
    printf("%4d wrapped to the range of [%d, %d) is %d\n",
        value, lower_bound, upper_bound,
        wrap(value, lower_bound, upper_bound)
    );
}

int main(void) {
    // examples
    show(0, -180, 180);
    show(-200, -180, 180);
    show(720, -180, 180);
    show(1234, -180, 180);
    show(5, 0, 10);
    show(-1, 0, 10);
    show(112, 0, 10);
    show(-3, -10, 0);
    show(7, -10, 0);
    show(-11, -10, 0);
    return 0;
}
nsilent22
  • 2,763
  • 10
  • 14
  • Code appears to be off-by-1. e,g, `wrap(value, -180,180)` never returns `180` and `wrap(value, 2,2)` does a `% 0`. Maybe `upper_bound` is really `upper_bound_plus_1`? Note: code overflows on `wrap(value, INT_MIN, 0 /* or more */)` – chux - Reinstate Monica Jul 21 '15 at 18:34
  • @chux: No, it's not off-by-one. That's why in the range – nsilent22 Jul 21 '15 at 18:54
  • With reading the complementary variable names `lower_bound, upper_bound`, I though the bound were symmetric . I see that in your comments that they are not. – chux - Reinstate Monica Jul 21 '15 at 19:08
  • I edited my comments to conform with [this notation](https://en.wikipedia.org/wiki/Bracket_%28mathematics%29#Intervals) – nsilent22 Jul 21 '15 at 19:13
  • 1
    @nsilent22 This code seems... complicated. What would the rationale be for using this over say [chux code](http://stackoverflow.com/a/31544165/2642059) or my original function? – Jonathan Mee Jul 21 '15 at 19:35
  • @Jonathan Mee: It's universal code to wrap value around any range. If you'd like to measure angle in [gradians](https://en.wikipedia.org/wiki/Gradian) instead of degrees you won't have to write separate function, just change the range (as shown in examples in the `main` function). – nsilent22 Jul 21 '15 at 19:44
  • @nsilent22 This does have the flexibility to have non-matching endpoints but that's not valuable to this problem. `remainder` is very capable of using radians as well as degrees, and would therefore eliminate even the need for a function. I'm giving a +1 for you going beyond the call of duty though... – Jonathan Mee Jul 21 '15 at 19:57
  • Thank you :) Well, my function could be easily changed to use doubles, just change the data types and use `fmod` instead of `%=` – nsilent22 Jul 21 '15 at 20:04
  • 1
    @Jonathan Mee Concerning "`remainder` is very capable of using radians as well as degrees". Using `remainder(angle,2*M_PI)` introduces precision loss not seen with `remainder(angle,360.0)` as `2*M_PI` is an approximation of a revolution of 2*π radians whereas 360°, 400 gradians is exact. See [Argument reduction for huge arguments](http://www.csee.umbc.edu/~phatak/645/supl/Ng-ArgReduction.pdf) – chux - Reinstate Monica Jul 21 '15 at 21:13