Mod Closest to Zero
Asked Answered
M

3

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.

Mur answered 21/7, 2015 at 14:6 Comment(11)
Aren't you simply looking for return std::remainder(angle, 180); ?Bauman
Can you please choose one of C and C++ for this?Actinomycete
@Bauman 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.Mur
@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.Mur
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.Actinomycete
@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.Mur
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 answersJoettajoette
@Joettajoette 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?Mur
@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 pointed out a mistake in my answer (now corrected).Joettajoette
@FUZxxl Looks like remainder(angle, 360.0) is the solution. Which is available in both C++ and C, so I'm planning on leaving both tags.Mur
@Bauman You were closer than I realized, looks like remainder(angle, 360) is the solution.Mur
J
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;
}
Joettajoette answered 21/7, 2015 at 16:5 Comment(5)
765 should return 45 and -765 should return -45. In your implementation they return 405 and 315 respectively.Mur
@JonathanMee It does return 170 for input of -190 so infact it does work. Look at ideone.com/T0Ewi6.Shaun
@JonathanMee It works here because the second parameter is 360 as it is used for mod in other functions here as it should be.Shaun
@JonathanMee Your example of "remainder doesn't work" uses remainder(-190, 180). This answer in func4() uses remainder(-190, 360).Joettajoette
@Joettajoette Sweet, you are correct sir. I was still using the 180 not the 360. Thank you.Mur
M
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.

Minton answered 21/7, 2015 at 14:26 Comment(7)
@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.Mur
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.Minton
@RobHague C and Python's operator% do work the same: (a / b) * b + a % b == a which in Python is: (a // b) * b + a % b == aMur
@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.Case
@Case 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]Mur
@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.Case
@Case Yeah, I've also learned today that remainder and fmod may have differing signs as well.Mur
J
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;
}
Joettajoette answered 21/7, 2015 at 16:5 Comment(5)
765 should return 45 and -765 should return -45. In your implementation they return 405 and 315 respectively.Mur
@JonathanMee It does return 170 for input of -190 so infact it does work. Look at ideone.com/T0Ewi6.Shaun
@JonathanMee It works here because the second parameter is 360 as it is used for mod in other functions here as it should be.Shaun
@JonathanMee Your example of "remainder doesn't work" uses remainder(-190, 180). This answer in func4() uses remainder(-190, 360).Joettajoette
@Joettajoette Sweet, you are correct sir. I was still using the 180 not the 360. Thank you.Mur
R
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;
}
Rockandroll answered 21/7, 2015 at 17:55 Comment(9)
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 */)Joettajoette
@chux: No, it's not off-by-one. That's why in the range <lower, upper) left "bracket" is "sharp" (which means the range includes the lower value), and right is "round" (which means the rang is up to, but NOT INCLUDING the upper value). So the value calculated is exactly: lower <= value < upper. As to the overflows, of course you can set the range to "unacceptable" one (e.g. make lower bigger the upper), but I did not include additional checks for the sake of clarity.Rockandroll
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.Joettajoette
I edited my comments to conform with this notationRockandroll
@Rockandroll This code seems... complicated. What would the rationale be for using this over say chux code or my original function?Mur
@Jonathan Mee: It's universal code to wrap value around any range. If you'd like to measure angle in gradians instead of degrees you won't have to write separate function, just change the range (as shown in examples in the main function).Rockandroll
@Rockandroll 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...Mur
Thank you :) Well, my function could be easily changed to use doubles, just change the data types and use fmod instead of %=Rockandroll
@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 argumentsJoettajoette

© 2022 - 2024 — McMap. All rights reserved.