Chebyshev Rotation

Consider a regular grid, where each cell has integer coordinates. We can group the cells into (square-shaped) "rings" where the cells in each ring have the same Chebyshev distance (or chessboard distance) from the origin. Your task is to take such a cell coordinate and rotate that cell by one position counter-clockwise within its ring. This implements the following mapping:

So for example if the input is (3, -2) you should output (3, -1). Note that (0, 0) is the only input that should map to itself.

Rules

The I/O format is fairly flexible. You can use two individual numbers, a pair/list/array/tuple of numbers, a single complex number, a string containing two numbers, etc.

You may assume that -128 < x,y < 128.

You may write a program or a function and use any of the our standard methods of receiving input and providing output.

You may use any programming language, but note that these loopholes are forbidden by default.

This is , so the shortest valid answer – measured in bytes – wins.

Test Cases

(0, 0)       => (0, 0)
(1, 0)       => (1, 1)
(1, 1)       => (0, 1)
(0, 1)       => (-1, 1)
(-1, 1)      => (-1, 0)
(-1, 0)      => (-1, -1)
(-1, -1)     => (0, -1)
(0, -1)      => (1, -1)
(1, -1)      => (1, 0)
(95, -12)    => (95, -11)
(127, 127)   => (126, 127)
(-2, 101)    => (-3, 101)
(-65, 65)    => (-65, 64)
(-127, 42)   => (-127, 41)
(-9, -9)     => (-8, -9)
(126, -127)  => (127, -127)
(105, -105)  => (105, -104)

• Can we mix input and output format, e.g., take a tuple and output a complex number? Oct 16, 2016 at 16:36
• @Dennis yes that's fine. Oct 16, 2016 at 16:37

JavaScript (ES6), 60 59 bytes

Takes input with currying syntax (x)(y) and returns an array [new_x, new_y].

x=>y=>(x|y&&((z=x+(y<0))>-y?z>y?y++:x--:z>y?x++:y--),[x,y])


How it works

Our main task is to determine in which quadrant we are, so that we know in which direction to move.

We can use this formula as a first approximation:

x > -y ? (x > y ? 0 : 1) : (x > y ? 2 : 3)


Here is what we get:

3 1 1 1 1 1 1 1 1
3 3 1 1 1 1 1 1 0
3 3 3 1 1 1 1 0 0
3 3 3 3 1 1 0 0 0
3 3 3 3 3 0 0 0 0
3 3 3 3 2 2 0 0 0
3 3 3 2 2 2 2 0 0
3 3 2 2 2 2 2 2 0
3 2 2 2 2 2 2 2 2


Almost there. But the bottom left and bottom right corners of the rings are invalid. We need to shift the lower half of the matrix by one position to the left, so we define z as:

z = y < 0 ? x + 1 : x


And we replace x with z in our formula:

z > -y ? (z > y ? 0 : 1) : (z > y ? 2 : 3)


3 1 1 1 1 1 1 1 1
3 3 1 1 1 1 1 1 0
3 3 3 1 1 1 1 0 0
3 3 3 3 1 1 0 0 0
3 3 3 3 3 0 0 0 0
3 3 3 2 2 0 0 0 0
3 3 2 2 2 2 0 0 0
3 2 2 2 2 2 2 0 0
2 2 2 2 2 2 2 2 0


The whole matrix is now correct, except for the special case [0, 0] (no move at all) which must be addressed separately.

Test cases

let f =

x=>y=>(x|y&&((z=x+(y<0))>-y?z>y?y++:x--:z>y?x++:y--),[x,y])

console.log(f(0)(0));       // => (0, 0)
console.log(f(1)(0));       // => (1, 1)
console.log(f(1)(1));       // => (0, 1)
console.log(f(0)(1));       // => (-1, 1)
console.log(f(-1)(1));      // => (-1, 0)
console.log(f(-1)(0));      // => (-1, -1)
console.log(f(-1)(-1));     // => (0, -1)
console.log(f(0)(-1));      // => (1, -1)
console.log(f(1)(-1));      // => (1, 0)
console.log(f(95)(-12));    // => (95, -11)
console.log(f(127)(127));   // => (126, 127)
console.log(f(-2)(101));    // => (-3, 101)
console.log(f(-65)(65));    // => (-65, 64)
console.log(f(-127)(42));   // => (-127, 41)
console.log(f(-9)(-9));     // => (-8, -9)
console.log(f(126)(-127));  // => (127, -127)
console.log(f(105)(-105));  // => (105, -104)

Jelly, 2014 12 bytes

S;IṠN0n/¦Ạ¡+


Input and output are in form of arrays. Try it online! or verify all test cases.

Background

To figure out in which direction we have to move, we can observe the relative position of the start point to the quadrant bisectors x + y = 0 (blue) and x - y = 0 (red).

• The origin is fixed. We advance by adding [0, 0] to the start point.

• Points in the topmost triangle – including the bisector of the first quadrant – have positive sum and non-negative delta (y - x). We advance by adding [-1, 0] to the start point.

• Points in the leftmost triangle – including the bisector of the second quadrant – have non-positive sum and positive delta. We advance by adding [0, -1] to the start point.

• Points in the bottommost triangle – including the bisector of the third quadrant – have negative sum and non-positive delta. We advance by adding [1, 0] to the start point.

• Points in the rightmost triangle – including the bisector of the fourth quadrant – have non-negative sum and negative delta. We advance by adding [0, 1] to the start point.

To figure out the correct direction, we compute [-sign(x + y), -sign(y - x)], which has only nine possible outcomes.

The following table illustrates which outcomes have to get mapped to which directions.

    sign(x+y) |  sign(y-x) | -sign(x+y) | -sign(y-x) |     Δx     |     Δy
------------+------------+------------+------------+------------+------------
0     |      0     |      0     |      0     |      0     |      0
1     |      0     |     -1     |      0     |     -1     |      0
1     |      1     |     -1     |     -1     |     -1     |      0
0     |      1     |      0     |     -1     |      0     |     -1
-1     |      1     |      1     |     -1     |      0     |     -1
-1     |      0     |      1     |      0     |      1     |      0
-1     |     -1     |      1     |      1     |      1     |      0
0     |     -1     |      0     |      1     |      0     |      1
1     |     -1     |     -1     |      1     |      0     |      1


This leaves three cases.

• If at least one of the signs is 0, [Δx, Δy] = [-sign(x+y), -sign(y-x)].

• If the signs are equal and non-zero, [Δx, Δy] = [-sign(x+y), 0].

• If the signs are different and non-zero, [Δx, Δy] = [0, -sign(y-x)].

How it works

S;IṠN0n/¦Ạ¡+  Main link. Argument: [x, y] (pair of integers)

S             Sum; compute x + y.
I           Increments; compute [y - x].
;            Concatenate; yield [x + y, y - x].
Ṡ          Sign; compute [sign(x + y), sign(y - x)].
N         Negate; yield [-sign(x + y), -sign(y - x)].
¡   Do n times:
Ạ      Set n to all([-sign(x + y), -sign(y - x)]), i.e., 1 if the signs
are both non-zero and 0 otherwise.
¦       Conditional application:
n/          Yield 1 if the signs are not equal, 0 if they are.
0            Replace the coordinate at 1 or 0 with a 0.
This returns [Δx, Δy].
+  Add; yield  [Δx + x, Δy + y].


Pyth, 19 bytes

&Q+^.jZ1.RhycPQ.n0Z


Try it online!

&Q                    If input is 0, then 0, else:
PQ         Get phase of input
c  .n0      Divide by π
y            Double
.R        Z     Round to integer
^.jZ1                Raise i to this power

• Nice parabola answer! Oct 17, 2016 at 6:11

Python, 55 bytes

lambda x,y:(x-(-y<x<=y)+(y<=x<-y),y+(~x<y<x)-(x<y<=-x))


Detects the four diagonal quadrants, and shifts the appropriate coordinate.

x#y|y>=x,-x<y=(x-1,y)|y>x=(x,y-1)|y< -x=(x+1,y)|y<x=(x,y+1)|1>0=(0,0)


This is just checking each of those tilted quadrants, and modifying the input accordingly. Note that the spaces are necessary, otherwise e.g. >- would be understood as an operator (which is not defined).

Thank you @nimi for removing a few more bytes!

• , instead of && within the first guard saves a byte. And then you can switch the second comparison to -x<yfor another byte.
– nimi
Oct 16, 2016 at 15:10
• Thank you, I wasn't aware of the ,! Oct 16, 2016 at 15:45

Ruby, 68

Lambda function takes complex number as argument, returns complex number.

->z{k=1
4.times{z*=?i.to_c
x,y=z.rect
y*y>=x*x&&y<-x&&(z+=k;k=0)}
z}


We rotate the point through 90 degrees 4 times by multiplying by i. It therefore passes through all 4 quadrants, and would be returned unchanged - except for the fact we modify it when it is in a specific one of them. The fact it is always modified in the same quadrant simplifies the modification.

It is easiest to follow if we alter it z when it is in the righthand quadrant. in this case we need to increase the y coordinate by 1 (i.e add i to z.)

We check x.abs>=y.abs by comparing the squares of x and y. This tells us the point is in the righthand or lefthand quadrant, not top or bottom. To check it is in fact in the righthand quadrant we further check that x>y (strictly greater because we want to exclude the case x=y which belongs to the "top" quadrant.) Where this is true we add i to z.

For golfing reasons, adding i is not desirable. Instead we modifiy the number when it is in the bottom quadrant, in which case we have to add 1 to the x coordinate (add 1 to z.) In this case we test that y*y>=x*x to check it is in the top or bottom quadrant. To further ensure it is in the bottom quadrant we need to check y<-x (strictly excluding the case of the bottom right corner where y=-x.)

An advantage of this check is there is no special case for the coordinate 0,0. Unfortunately it was found that moving the point can shift it to a different quadrant and this means that a second movement must be suppressed should that quadrant be checked again, which probably negates the advantage.

Example 1

Input                                        95,-12
Rotate 90deg                                 12,95
Rotate 90deg                                -95,12
Rotate 90deg                                -12,-95
Rotate 90deg                                 95,-12
y.abs>=x.abs=TRUE, y<-x=TRUE, increase x     95,-11

The check and alteration of the coordinate is done AFTER the rotation.
Thus in this case it gets done in the 4th iteration of the loop, not the 1st.
If the code were rewritten to do the check and alteration BEFORE the rotation,
it would be done in the 1st iteration instead of the 4th.


Example 2

Input                                        -1,0
Rotate 90deg                                  0,-1
y.abs>=x.abs=TRUE, y<-x=TRUE, increase x      1,-1
Rotate 90deg                                  1,1
Rotate 90deg                                  1,-1
Rotate 90deg                                 -1,-1
y.abs>=x.abs?=TRUE, y<-x=TRUE but DO NOT CHANGE x!

This is an unusual situation due to the fact that the first move caused the
point to advance by one quadrant. We do NOT want to move it again, for this
reason we need to set k to 0 the first time it is moved.


In test program

f=->z{k=1                   #amount to be added to coordinate
4.times{z*=?i.to_c          #iterate 4 times, rotating point by 90deg till it reaches the original orientation
x,y=z.rect                  #separate out x and y for testing
y*y>=x*x&&y<-x&&(z+=k;k=0)} #if y.abs>=x.abs and y negative and not equal -x, move the point and zero k.
z}                          #return z

puts f[Complex(0, 0)]       # (0, 0)
puts f[Complex(1, 0)]       # (1, 1)
puts f[Complex(1, 1)]       # (0, 1)
puts f[Complex(0, 1)]       # (-1, 1)
puts f[Complex(-1, 1)]      # (-1, 0)
puts
puts f[Complex(-1, 0)]      # (-1, -1)
puts f[Complex(-1, -1)]     # (0, -1)
puts f[Complex(0, -1)]      # (1, -1)
puts f[Complex(1, -1)]      # (1, 0)
puts f[Complex(95, -12)]    # (95, -11)
puts f[Complex(127, 127)]   # (126, 127)
puts
puts f[Complex(-2, 101)]    # (-3, 101)
puts f[Complex(-65, 65)]    # (-65, 64)
puts f[Complex(-127, 42)]   # (-127, 41)
puts f[Complex(-9, -9)]     # (-8, -9)
puts f[Complex(126, -127)]  # (127, -127)
puts f[Complex(105, -105)]  # (105, -104)


Diagram

The following image shows (blue) the area where x*x>=y*y, (yellow) the area where y<-x and (green) the intersection of these, which is the region where the correct transformation is the addition of 1 to z.

• Sorry, I'm not following the explanation. Would you mind adding either an example or a diagram? Oct 16, 2016 at 19:08
• @Martin explanation added. This was an interesting approach but due to the need to suppress double movement of points that change quadrant the first time they move, it didn´t turn out as elegant as I had hoped. Oct 16, 2016 at 20:04

Python, 52 bytes

h=lambda z:z and 1j*h(z/1j)if'-'inz*1j-z-1else z+1


Complex input and output. To test the point for being in the lower diagonal quadrant, first rotate it 135 counterclockwise to move that quadrant to the (x>0, y>0) standard quadrant, and test if the result has no minus symbol in the string representation. Subtracting 1 first takes care of the boundary condition.

If it's not in that quadrant, rotate the whole problem 90 degrees. The input is zero is specially handled to output itself.

Other attempts with complex numbers:

## 56 bytes
## Coordinate input, complex output
q=lambda x,y:(y<=x<-y)*(1j*y-~x)or x+1j*y and 1j*q(y,-x)

## 60 bytes
h=lambda z:(z+1)*(z.imag<=z.real<-z.imag)or z and 1j*h(z/1j)

## 63 bytes
from cmath import*
h=lambda z:z and 1j**(phase(z*1j-z)*2//pi)+z


Mathematica, 34 bytes

±0=0
±z_:=z+I^Floor[2Arg@z/Pi+3/2]


This defines a unary operator ± which takes and returns a complex number whose components represent x and y.

Now that Lynn has revealed the complex number solution and Dennis has beaten my score, I don't feel so bad for posting my golfed referenced implementation. :) (It turns out to be virtually identical to Lynn's answer.)

• Would this help? ± 0 = 0 ⁢ ± z_ := z + I ^ ⌊ 2 ⁢ Arg @ z / Pi + 3 / 2 ⌋ (perhaps with a different char for the floor brackets) Oct 16, 2016 at 18:55
• @DavidC unfortunately not because then I'd have to use UTF-8 encoding and then the ± would cost 2 bytes each. Oct 16, 2016 at 18:56
• Wouldn't that be 4 bytes instead of 7, thus giving an economy of 3 bytes? Oct 16, 2016 at 18:57
• @DavidC no, the floor brackets would be 3 bytes each. Oct 16, 2016 at 18:58
• I wasn't aware of that. But, even so, you should still be saving 1 byte. Oct 16, 2016 at 19:01

MATL, 19 17 bytes

t|?JGJq*X/EYP/k^+


This uses complex numbers as input and output.

Explanation

Let's take input -127+42j as an example.

t|       % Implicit input. Duplicate and take absolute value
% STACK: -127+42j, 133.764718816286
?        % If nonzero
% STACK: -127+42j
J      %   Push j (imaginary unit)
%   STACK: -127+42j, j
GJq*   %   Push input multiplied by -1+j. This adds 3*pi/4 to the phase of the input
%   STACK: -127+42j, j, 85-169i
X/     %   Phase of complex number
%   STACK: -127+42j, j, -1.10478465600433
EYP/   %   Divide by pi/2
%   STACK: -127+42j, j, -0.703327756220671
k      %   Round towards minus infinity
%   STACK: -127+42j, j, -1
^      %   Power
%   STACK: -127+42j, -j
%   STACK: -127+41j
% Implicit end
% Implicit display



Ruby, 51 bytes

Original form

->x,y{d=x*x-y*y
[x+(d>0?0:-y<=>x),y+(d<0?0:x<=>y)]}


Alternate form per Xnor's comment

->x,y{[x+(x*x>y*y ?0:-y<=>x),y+(x*x<y*y ?0:x<=>y)]}


Uses the same type of inequalities as my other answer, but in a different way.

In test program

f=->x,y{d=x*x-y*y
[x+(d>0?0:-y<=>x), #if y.abs>=x.abs: x+=1 if -y>x, x-=1 if -y<x
y+(d<0?0:x<=>y)]}  #if x.abs>=y.abs: y+=1 if  x>y, y-=1 if  x<y

p f[0, 0]       # (0, 0)
p f[1, 0]       # (1, 1)
p f[1, 1]       # (0, 1)
p f[0, 1]       # (-1, 1)
p f[-1, 1]      # (-1, 0)
puts
p f[-1, 0]      # (-1, -1)
p f[-1, -1]     # (0, -1)
p f[0, -1]      # (1, -1)
p f[1, -1]      # (1, 0)
p f[95, -12]    # (95, -11)
p f[127, 127]   # (126, 127)
puts
p f[-2, 101]    # (-3, 101)
p f[-65, 65]    # (-65, 64)
p f[-127, 42]   # (-127, 41)
p f[-9, -9]     # (-8, -9)
p f[126, -12]   # (127, -127)
p f[105, -105]  # (105, -104)

• Is the d assignment worth it? It looks like you can just compare x*x>y*y.
– xnor
Oct 17, 2016 at 1:06
• @Xnor unfortunately Ruby requires a space between y*y and ? so it's exactly the same length. I've included it as I think your way is in some ways neater. I think Ruby is trying to pass it as y? which would be a legal function name. Oct 17, 2016 at 1:43

Julia, 38 34 bytes

!z=z==0?0:z+im^int(2angle(z)/pi+1)


Dennis saved four bytes. Thanks!

Try it online!

• Looks like I mixed up int's behavior across different versions of Julia (which, in my defense, is terribly inconsistent). Julia 0.4 (the version on TIO) rounds halves towards even, so this won't work as is. In Julia 0.3, you can use int(2angle(z)/pi+5) for the same byte count (negative powers cause an error for whatever reason). Oct 18, 2016 at 1:49
• Also, you can save a byte with !z=z+(z!=0)im^... in all versions. Oct 18, 2016 at 1:49

C++, 94 bytes

#define a(x) (x>0?x:-(x))
#define f(x,y) y>a(x-.5)?x--:-y>a(x+.5)?x++:x>a(y+.5)?y++:x|y?y--:x;


Ungolfed:

#define a(x) (x>0?x:-(x))  //shorter than std::abs from <cmath>
#define f(x,y)
y>a(x-.5)?      // shift absolute value function by 0.5 to the right to get upper fourth
x--:
-y>a(x+.5)? //same for lower fourth
x++:
x>a(y+.5)? //same for right fourth
y++:
x|y? //only left fourth and 0 are left
y--:
x; //can't be empty, just does nothing


Usage:

#include <iostream>
void test(int x, int y, int rx, int ry){
std::cout << "(" << x << ", " << y << ")=>";
f(x,y);
std::cout << "(" << x << ", " << y << ") - " << ((x==rx&&y==ry)?"OK":"FAILURE") << std::endl;
}

//Using the test cases from the question
int main() {
test(0, 0, 0, 0);
test(1, 0, 1, 1);
test(1, 1, 0, 1);
test(0, 1, -1, 1);
test(-1, 1, -1, 0);
test(-1, 0, -1, -1);
test(-1, -1, 0, -1);
test(0, -1, 1, -1);
test(1, -1, 1, 0);
test(95, -12, 95, -11);
test(127, 127, 126, 127);
test(-2, 101, -3, 101);
test(-65, 65, -65, 64);
test(-127, 42, -127, 41);
test(-9, -9, -8, -9);
test(126, -127, 127, -127);
test(105, -105, 105, -104);

return 0;
}


Try it online

• I'm pretty sure that (x>0?x:-(x)) can be (x>0?x:-x). Oct 17, 2016 at 8:16
• Unfortunately not, since the token x will be replaced by e.g. x+.5 which would just get -x+.5. Oct 17, 2016 at 10:29
• Alright. I had a mindset where negation without parentheses flipped the sign :D Oct 17, 2016 at 10:31
• Strictly speaking, you have used the C preprocessor (which is admittedly a part of C++, but is also shared with other C variants and descendants) Oct 18, 2016 at 13:14

R, 131 110 bytes

A function that takes the two integers, x,y as inputs and writes the output to stdout. The solution follows @Dennis' control flow scheme but could probably be golfed.

EDIT: Updated code based on @JDL's suggestions and saved a bunch of bytes.

function(x,y){X=sign(x+y);Y=sign(y-x);if(!X|!Y){x=x-X;y=y-Y}else if(X==Y&X&Y)x=x-X else if(X-Y&X)y=y-Y;c(x,y)}


Ungolfed

f=function(x,y){
X=sign(x+y)                 # calculate sign
Y=sign(y-x)                 #  =||=
if(!X|!Y){x=x-X;y=y-Y}      # if at least one is 0: subtract sign
else if(X==Y&X&Y)x=x-X      # if signs are equal and non-zero: add sign to x
else if(X-Y&X)y=y-Y         # if signs are not equal and non-zero: add sign to y
c(x,y)                      # print to stdout
}

• I think some of the logical conditions can be shortened: as.logical(-1) is TRUE, so X==0|Y==0 can become !X|!Y, and the condition if(X!=Y...) can become if(X-Y). Also, if X==Y and X!=0 then Y!=0 is redundant. Actually, all the !=0 parts are redundant; if(X!=0) is equivalent to if(X).
– JDL
Oct 17, 2016 at 10:20
• Also, given that "the I/O format is fairly flexible", it's probably fair game to output implicitly with c(x,y) instead of cat(x,y).
– JDL
Oct 17, 2016 at 10:23
• @JDL Those are some very useful golfing tips I never thought about, thanks alot! Updated the answer. Oct 17, 2016 at 11:33

JavaScript (ES6), 57 bytes (55–63†)

Accepts an [x, y] array, modifies it in-place, and returns it.

c=>([x,y]=c,i=x>y|x==y&x<0,c[i^x<-y|x==-y]-=-i|!!(x|y),c)


How it works

c=>(


This is a single-parameter arrow function with a return-free concise body.

[x,y]=c


The parameter is immediately destructured into x and y variables.

,


The comma operator combines multiple expressions into one, using the result of the last.

i=x>y|x==y&x<0


i is used for differentiating increment and decrement cases. When x is greater than y, we are in either the bottom or right quadrant, and need to advance in one dimension (i=1 by boolean-to-number coercion). Likewise when we are on the negative portion of the dividing x = y diagonal. In all other cases—including the origin—no increment is required (i=0).

c[i^x<-y|x==-y]


We use a somewhat similar expression for controlling which array index to adjust. When we are incrementing and not in the left or bottom quadrants (or when we are not incrementing and in the left or bottom), then the bitwise XOR will produce 1 and we will adjust the y value. Likewise for when we are on the dividing x = -y diagonal (including the origin). In all other cases, the index will be 0 (x).

-=-i|!!(x|y)


When i is 1, we will add it to the specified value. When i is 0, we will subtract 1 from the value if and only if we are not at the origin. The latter is detected by x|y yielding nonzero, clipped to {0, 1} by boolean coercion, and the negation of i allows us to use bitwise OR instead of logical (since -1 has no zero bits, it is safe from modification).

c


The array is last, so it will be returned.

Testing

let f =
c=>([x,y]=c,i=x>y|x==y&x<0,c[i^x<-y|x==-y]-=-i|!!(x|y),c)

let cases = 
(0, 0)       => (0, 0)
(1, 0)       => (1, 1)
(1, 1)       => (0, 1)
(0, 1)       => (-1, 1)
(-1, 1)      => (-1, 0)
(-1, 0)      => (-1, -1)
(-1, -1)     => (0, -1)
(0, -1)      => (1, -1)
(1, -1)      => (1, 0)
(95, -12)    => (95, -11)
(127, 127)   => (126, 127)
(-2, 101)    => (-3, 101)
(-65, 65)    => (-65, 64)
(-127, 42)   => (-127, 41)
(-9, -9)     => (-8, -9)
(126, -127)  => (127, -127)
(105, -105)  => (105, -104)
;

let failures = cases.trim().split("\n").filter(l => {
const FAIL = true;
let m = /.*?(-?\d+).*?(-?\d+).*?(-?\d+).*?(-?\d+)/.exec(l);
if (!m) return FAIL;
let [_, x, y, X, Y] = m;
let [x2, y2] = f([+x, +y]);
if (x2==X && y2==Y) return !FAIL;
console.log("(%s) yielded (%s); expected (%s)", [x, y], [x2, y2], [X, Y]);
return FAIL;
});
console.log("failures: " + failures.length);
// => failures: 0

†Variations

We can save two more bytes by skipping a meaningful return value and using only the input mutation:

c=>([x,y]=c,i=x>y|x==y&x<0,c[i^x<-y|x==-y]-=-i|!!(x|y))


…or we can skip input mutation and make all variables local for a pure function, at the cost of six bytes:

([x,y],i=x>y|x==y&x<0,c=[x,y])=>(c[i^x<-y|x==-y]-=-i|!!(x|y),c)


JavaScript (ES6), 80 76 bytes

(x,y,s=Math.max(x,y,-x,-y))=>(s?x+s?y-s?x-s?x++:y++:x--:y+s?y--:x++:0,[x,y])


0%0=(0,0)
x%y|y>=0-x,y<x=(x,y+1)|(p,q)<-(-y)%x=(q,-p)


Takes two numbers, outputs a tuple. If the point is in the east section -x<=y<x, increase the second coordinate by 1. Otherwise, cycle the quadrants by rotating the input point 90 degrees, calling the function on it, then rotating back.

Racket 191 bytes

(cond[(= 0 x y)(list x y)][(= x y)(if(> x 0)(list(sub1 x)y)(list(add1 x)y))][(> x y)(if(>= x(abs y))


Ungolfed (directly translating figure directions to code without using any intermediate formula):

(define(f x y)
(cond
[(= 0 x y) (list x y)]
[(= x y)
(if (> x 0)
(list (sub1 x) y)   ; left
(list (add1 x) y))] ; right
[(> x y)
(if (>= x (abs y))
(list x (add1 y))   ; up
(list (add1 x) y))] ; right
[(< x y)
(if (> y (abs x))
(list (sub1 x) y)   ; left
(list x (sub1 y)))] ; down
))


Testing:

(f 0  0)
(f 1  0)
(f 1  1)
(f 0  1)
(f -1  1)
(f -1  0)
(f -1  -1)
(f 0  -1)
(f 1  -1)
(f 95  -12)
(f 127  127)
(f -2  101)
(f -65  65)
(f -127  42)
(f -9  -9)
(f 126  -127)
(f 105  -105)


Output:

'(0 0)
'(1 1)
'(0 1)
'(-1 1)
'(-1 0)
'(-1 -1)
'(0 -1)
'(1 -1)
'(1 0)
'(95 -11)
'(126 127)
'(-3 101)
'(-65 64)
'(-127 41)
'(-8 -9)
'(127 -127)
'(105 -104)


Actually, 16 bytes

This takes a complex number as input and outputs another complex number. Golfing suggestions welcome! Try it online!

;₧╦@/τuLïⁿ+0╬X


Ungolfing

         Implicit input z.
;        Duplicate z.
...╬   If z is non-zero (any a+bi except 0+0j), run the following function.
Stack: z, z
₧        Get phase(z).
╦@/      Divide phase(z) by pi.
τuL      Push floor(2*phase(z)/pi + 1).
ïⁿ       Push 1j ** floor(2*phase(z)/pi + 1).
+        And add it to z. This is our rotated z.
0        Push 0 to end the function.
X        Discard either the duplicate (0+0j) or the 0 from the end of function.
Implicit return.


Scala, 184 bytes

val s=math.signum _
(x:Int,y:Int)=>{val m=x.abs max y.abs
if(x.abs==y.abs)if(s(x)==s(y))(x-s(x),y)else(x,y-s(y))else
if(x.abs==m)(x,y+Seq(0,x).indexOf(m))else(x-Seq(0,y).indexOf(m),y)}


Ungolfed:

import math._

(x: Int, y: Int) => {
val max = max(x.abs, y.abs)
if (x.abs == y.abs)
if (signum(x) == signum(y))
(x - signum(x), y)
else
(x, y - signum(y))
else
if (x.abs == max)
(x, y + Seq(0, x).indexOf(max))
else
(x - Seq(0, y).indexOf(max), y)
}


Explanation:

val s=math.signum _             //define s as an alias to math.signum
(x:Int,y:Int)=>{                //define an anonymous function
val m=x.abs max y.abs           //calculate the maximum of the absolute values,
//which is 1 for the innermost circle and so on.
if(x.abs==y.abs)                //if we have a cell at a corner of a circle
if(s(x)==s(y))                  //if it's at the top-left or bottom-right, we need to
//modify the x value
(x-s(x),y)                      //if x is positive (bottom-right),
//we need to return (x+1,y),
//(x-1,y) If it's at the top-left.
//This can be simplified to (x-s(x),y)
else                            //for top-right and bottom-left,
(x,y-s(y))                      //modify y in the same way.
else                            //we don't have a corner piece
if(x.abs==m)                    //if we're at the left or right edge of the square
(x,y+Seq(0,x).indexOf(m))       //if it's a piece from the right edge, add one
//to y, else subtract 1
else                            //it's a piece from the top or bottm edge
(x-Seq(0,y).indexOf(m),y)       //subtract 1 from x if it's from the top edge,
//else subtract -1
}