ken (04/20/83)
The CORDIC algorithms are superb for implementing trigonometric, inverse trigonometric, and hyperbolic functions. rotate() rotates a rectangular vector, but can be used to convert from polar to rectangular coordinates. It is given rectangular coordinates and an angle, and it returns rectangular coordinates. polarize() converts from rectangular to polar coordinates. It is given rectangular coordinates (x, y) and returns polar coordinates (r, theta). polarize() is more of an arctan2(x, y) than an arctan(x/y), but gives an angle around the whole circle, rather than just the right-half plane. BRADS is my name for binary radians, the natural measure of angle on a 16-bit machine: 32768 is a half circle, and 65536 is a whole circle. You get your choice of degrees, radians, or brads, or whatever other measure you want. The difference is in the arctangent table. The scheme is as follows: Rotate a vector through successively smaller angles until it meets a certain condition. For polarize(), it is until the Y-coordinate is zero. For rotate(), it is until the rotation angle is zero. The papers prove convergence. Approximately one bit of accuracy is got for each iteration through the loop. If you aren't interested in the magnitude of the vector, you can eliminate the final multiplication by COSCALE to save time. These were implemented on the PDP-11, and hence assume that an int is 16 bits. References: Chen, T. C. "Automatic Computation of Exponentials, Logarithms, Ratios, and Square Roots", IBM J. Res. Develop., July 1972, pp. 380-388 Despain, A. M. "Fourier Transform Computers Using CORDIC Iterations", IEEE Trans. Comp., vol. C-23, no. 10, Oct. 1974, pp. 993-1001 Turkowski, K. E. "Anti-Aliasing through the Use of Coordinate Transformations", ACM Transactions on Graphics, Vol. 1, No. 3, July 1982, pp. 215-234 Volder, J. E. "The CORDIC Trigonometric Computing Technique", IRE Transactions on Electronic Computers, vol. EC-8, no. 3, Sept. 1959, pp. 330-334 Walther, J. S. "A Unified Algorithm for Elementary Functions", Proc. AFIPS 1971 Spring Joint Computer Conference, pp. 379-385 /********************************************************/ cat << 'EOF' > polarize.c # define COSCALE 042606 /* 0.271572 */ # ifdef DEGREES # define QUARTER (90 << 6) /* Before shifting left by two */ static int arctantab[16] = { 16239,11520,6801,3593,1824,916,458,229,115,57,29,14,7,4,2,1 }; # else # ifdef RADIANS # define QUARTER ((int)(3.141592654 / 2.0 * 4096.0)) /* Before shifting */ static int arctantab[16] = { 18140,12868,7596,4014,2037,1023,512,256,128,64,32,16,8,4,2,1 }; # else # define BRADS BRADS # define QUARTER 16384 /* Before shifting by 1 */ static int arctantab[16] = { 23096,16384,9672,5110,2594,1302,652,326,163,81,41,20,10,5,3,1 }; # endif # endif struct rectangular { int x; int y; }; struct polar { int magnitude; int angle; }; /* This program changes from rectangular to polar coordinates by rotating * the vector several times until it lies within epsilon of 0 degrees. A * fixed magnitude correction is done at the end, as is a rounding of the * accumulated angle. * * To rotate a vector through an angle of theta, we calculate: * * x' = x cos(theta) - y sin(theta) * y' = x sin(theta) + y cos(theta) * * The rotate() routine performs multiple rotations of the form: * * x[i+1] = cos(theta[i]) { x[i] - y[i] tan(theta[i]) } * y[i+1] = cos(theta[i]) { y[i] + x[i] tan(theta[i]) } * * with the constraint that tan(theta[i]) = pow(2, -i), which can be implemented * by shifting. We always shift by either a positive or negative angle, so the * convergence has the ringing property. Since the cosine is always positive * for positive and negative angles between -90 and 90 degrees, a predictable * magnitude scaling occurs at each step, and can be compensated for instead * at the end of the iterations by a composite scaling of the product of all * the cos(theta[i])'s. * * Several modifications to the technically elegant program were made to * accomodate other desirable properties. * * We have accommodated any of the various ways of representing an angle: * 0 to 360 degrees, -180 to 180 degrees, or even possibly -360 to 0 degrees * (correspondingly with radians). After the angle has been brought to the * -90 to 90 degree range, the range is expanded to have the maximum accuracy * in the forthcoming calculations. For degrees, this means that the input * angle has 6 bits of fractional accuracy, and is increased to 8 bits for * calculation purposes. For radians, we have 12 bits of fractional accuracy * on input, and 14 bits during calculations. For brads (the natural way of * representing angles on a binary computer), we have 16 bits to represent the * full 360 degrees on input, and 16 bits to represent +/- 90 degrees during * calculations. * * The other modification made was to accommodate any radius we might encounter * in terms of sixteenths of pixels. There should be enough range to accommodate * any vectors that are legal, even in 4xPAL. * * An empirical analysis shows that the error is about 1/2 LSB until one feeds * in a vector with a magnitude of 6464 (404 pixels in terms of sixteenths); * after that, the least significant bit sometimes flips up or down. This gives * an accuracy of approximately 0.015%. */ struct polar polarize(vector) struct rectangular vector; { register int i, theta; long x0, y0, x1; struct polar tempolar; struct doubleword { int hiword; int loword; }; x0.loword = 0; x0.hiword = vector.x; y0.loword = 0; y0.hiword = vector.y; /* Get angle between -90 and 90 degrees */ if (x0.hiword < 0) { x0.hiword = -x0.hiword; if ((y0.hiword = -y0.hiword) <= 0) tempolar.angle = 2 * QUARTER; else tempolar.angle = - 2 * QUARTER; } else tempolar.angle = 0; theta = 0; /* Prescale vectors to allow for full dynamic range in sixteenths */ x0 >>= 1; y0 >>= 1; for (i = 1; i > -15; --i) { if (y0 < 0) { x1 = x0 - (y0 << i); y0 = y0 + (x0 << i); x0 = x1; theta -= arctantab[1 - i]; } else { x1 = x0 + (y0 << i); y0 = y0 - (x0 << i); x0 = x1; theta += arctantab[1 - i]; } } x0 += 0100000L; /* Round x0 */ /* Scale the rotated vectors to keep the magnitude the same, * compensating for the prescaling. */ tempolar.magnitude = frmul(x0.hiword, COSCALE); # ifdef BRADS tempolar.angle += (theta + 1) >> 1; # else tempolar.angle += (theta + 2) >> 2; # endif return(tempolar); } 'EOF' cat << 'EOF' > rotate.c # define COSCALE 042606 /* 0.271572 */ # ifdef DEGREES # define QUARTER (90 << 6) /* Before shifting left by two */ static int arctantab[16] = { 16239,11520,6801,3593,1824,916,458,229,115,57,29,14,7,4,2,1 }; # else # ifdef RADIANS # define QUARTER ((int)(3.141592654 / 2.0 * 4096.0)) /* Before shifting */ static int arctantab[16] = { 18140,12868,7596,4014,2037,1023,512,256,128,64,32,16,8,4,2,1 }; # else # define BRADS BRADS # define QUARTER 16384 /* Before shifting by 1 */ static int arctantab[16] = { 23096,16384,9672,5110,2594,1302,652,326,163,81,41,20,10,5,3,1 }; # endif # endif struct coordinate { int x; int y; }; /* To rotate a vector through an angle of theta, we calculate: * * x' = x cos(theta) - y sin(theta) * y' = x sin(theta) + y cos(theta) * * The rotate() routine performs multiple rotations of the form: * * x[i+1] = cos(theta[i]) { x[i] - y[i] tan(theta[i]) } * y[i+1] = cos(theta[i]) { y[i] + x[i] tan(theta[i]) } * * with the constraint that tan(theta[i]) = pow(2, -i), which can be implemented * by shifting. We always shift by either a positive or negative angle, so the * convergence has the ringing property. Since the cosine is always positive * for positive and negative angles between -90 and 90 degrees, a predictable * magnitude scaling occurs at each step, and can be compensated for instead * at the end of the iterations by a composite scaling of the product of all * the cos(theta[i])'s. * * Several modifications to the technically elegant program were made to * accomodate other desirable properties. * * We have accommodated any of the various ways of representing an angle: * 0 to 360 degrees, -180 to 180 degrees, or even possibly -360 to 0 degrees * (correspondingly with radians). After the angle has been brought to the * -90 to 90 degree range, the range is expanded to have the maximum accuracy * in the forthcoming calculations. For degrees, this means that the input * angle has 6 bits of fractional accuracy, and is increased to 8 bits for * calculation purposes. For radians, we have 12 bits of fractional accuracy * on input, and 14 bits during calculations. For brads (the natural way of * representing angles on a binary computer), we have 16 bits to represent the * full 360 degrees on input, and 16 bits to represent +/- 90 degrees during * calculations. * * The other modification made was to accommodate any radius we might encounter * in terms of sixteenths of pixels. There should be enough range to accommodate * any vectors that are legal, even in 4xPAL. * * An empirical analysis shows that the error is about 1/2 LSB until one feeds * in a vector with a magnitude of 6464 (404 pixels in terms of sixteenths); * after that, the least significant bit sometimes flips up or down. This gives * an accuracy of approximately 0.015%. */ struct coordinate rotate(vector, theta) struct coordinate vector; register int theta; { register int i; long x0, y0, x1; struct doubleword { int hiword; int loword; }; x0.loword = 0; x0.hiword = vector.x; y0.loword = 0; y0.hiword = vector.y; /* Get angle between -90 and 90 degrees */ # ifdef BRADS if (theta < -QUARTER) { # else while (theta < -QUARTER) { # endif x0.hiword = -x0.hiword; y0.hiword = -y0.hiword; theta += 2 * QUARTER; } # ifdef BRADS if (theta > QUARTER) { # else while (theta > QUARTER) { # endif x0.hiword = -x0.hiword; y0.hiword = -y0.hiword; theta -= 2 * QUARTER; } # ifdef BRADS /* Expand accuracy of angle specification. */ theta <<= 1; # else theta <<= 2; # endif /* Prescale vectors to allow for full dynamic range in sixteenths */ x0 >>= 1; y0 >>= 1; # ifdef DEBUG printf("starting: x = %d, y = %d, theta = %d\n", x0.hiword, y0.hiword, theta); # endif for (i = 1; i > -15; --i) { if (theta < 0) { x1 = x0 + (y0 << i); y0 = y0 - (x0 << i); x0 = x1; theta += arctantab[1 - i]; } else { x1 = x0 - (y0 << i); y0 = y0 + (x0 << i); x0 = x1; theta -= arctantab[1 - i]; } # ifdef DEBUG printf("x = %d, y = %d, theta = %d\n", x0.hiword, y0.hiword, theta); # endif } /* Round x0 and y0 */ x0 += 0100000L; y0 += 0100000L; /* Scale the rotated vectors to keep the magnitude the same, * compensating for the prescaling. */ vector.x = frmul(x0.hiword, COSCALE); vector.y = frmul(y0.hiword, COSCALE); return(vector); } 'EOF'