Angle difference between two bearings: Difference between revisions

Content added Content deleted
m (→‎{{header|Haskell}}: ( applicative inlining of row display function ))
(→‎{{header|Fortran}}: Ah, vector analysis...)
Line 132: Line 132:


=={{header|Fortran}}==
=={{header|Fortran}}==
Rather than calculate angle differences and mess about with folding the results into +-180 and getting the sign right, why not use some mathematics? These days, trigonometrical functions are calculated swiftly by specialised hardware (well, microcode), and with the availability of functions working in degrees, matters are eased further nor is precision lost in converting from degrees to radians. So, the first step is to convert a bearing into an (x,y) unit vector via function CIS(t) = cos(t) + i.sin(t), which will handle all the annoyance of bearings specified in values above 360. Then, using the dot product of the two vectors allows the cosine of the angle to be known, and the cross product determines the sign.
Rather than calculate angle differences and mess about with folding the results into ±180 and getting the sign right, why not use some mathematics? These days, trigonometrical functions are calculated swiftly by specialised hardware (well, microcode), and with the availability of functions working in degrees, matters are eased further nor is precision lost in converting from degrees to radians. So, the first step is to convert a bearing into an (x,y) unit vector via function CIS(t) = cos(t) + i.sin(t), which will handle all the annoyance of bearings specified in values above 360. Then, using the dot product of the two vectors allows the cosine of the angle to be known, and the cross product determines the sign.

However, this relies on the unit vectors being accurately so, and their subsequent dot product not exceeding one in size: given the rounding of results with the limited precision actual floating-point arithmetic, there may be problems. Proving that a calculation will not suffer these on a specific computer is difficult, especially as the desire for such a result may mean that any apparent pretext leading to that belief will be seized upon. Because calculations on the IBM pc and similar computers are conducted with 80-bit floating-point arithmetic, rounding errors for 64-bit results are likely to be small, but past experience leads to a "fog of fear" about the precise behaviour of floating-point arithmetic.

As it happens, the test data did not provoke any objections from the ACOSD function, but even so, a conversion to using ''arctan'' instead of ''arccos'' to recover angles would be safer. By using the four-quadrant ''arctan(x,y)'' function, the sign of the angle difference is also delivered and although that result could be in 0°-360° it turns out to be in ±180° as desired. On the other hand, the library of available functions did not include an arctan for complex parameters, so the complex number Z had to be split into its real and imaginary parts, thus requiring two appearances and to avoid repeated calculation, a temporary variable Z is needed. Otherwise, the statement could have been just <code>T = ATAN2D(Z1*CONJG(Z2))</code> and the whole calculation could be effected in one statement, <code>T = ATAN2D(CIS(90 - B1)*CONJG(CIS(90 - B2)))</code> And, since ''cis(t) = exp(i.t)'', <code>T = ATAN2D(EXP(CMPLX(0,90 - B1))*CONJG(EXP(CMPLX(0,90 - B2))))</code> - although using the arithmetic statement function does seem less intimidating.


The source style is F77 (even using the old-style arithmetic statement function) except for the convenience of generic functions taking the type of their parameters to save on the bother of DCMPLX instead of just CMPLX, etc. Floating-point constants in the test data are specified with ~D0, the exponential form that signifies double precision otherwise they would be taken as single precision values. Some compilers offer an option stating that all floating-point constants are to be taken as double precision. REAL*8 precision amounts to about sixteen decimal digits, so some of the supplied values will not be accurately represented, unless something beyond REAL*8 is available. <lang Fortran> SUBROUTINE BDIFF (B1,B2) !Difference B2 - B1, as bearings. All in degrees, not radians.
The source style is F77 (even using the old-style arithmetic statement function) except for the convenience of generic functions taking the type of their parameters to save on the bother of DCMPLX instead of just CMPLX, etc. Floating-point constants in the test data are specified with ~D0, the exponential form that signifies double precision otherwise they would be taken as single precision values. Some compilers offer an option stating that all floating-point constants are to be taken as double precision. REAL*8 precision amounts to about sixteen decimal digits, so some of the supplied values will not be accurately represented, unless something beyond REAL*8 is available. <lang Fortran> SUBROUTINE BDIFF (B1,B2) !Difference B2 - B1, as bearings. All in degrees, not radians.
REAL*8 B1,B2 !Maximum precision, for large-angle folding.
REAL*8 B1,B2 !Maximum precision, for large-angle folding.
COMPLEX*16 CIS,Z1,Z2 !Scratchpads.
COMPLEX*16 CIS,Z1,Z2,Z !Scratchpads.
CIS(T) = CMPLX(COSD(T),SIND(T)) !Convert an angle into a unit vector.
CIS(T) = CMPLX(COSD(T),SIND(T)) !Convert an angle into a unit vector.
Z1 = CIS(90 - B1) !Bearings run clockwise from north (y) around to east (x).
Z1 = CIS(90 - B1) !Bearings run clockwise from north (y) around to east (x).
Z2 = CIS(90 - B2) !Mathematics runs counterclockwise from x (east).
Z2 = CIS(90 - B2) !Mathematics runs counterclockwise from x (east).
T = ACOSD(REAL(Z1)*REAL(Z2) + AIMAG(Z1)*AIMAG(Z2)) !Dot product: Z1.Z2 = cos(T) for unit vectors.
Z = Z1*CONJG(Z2) !(Z1x,Z1y)(Z2x,-Z2y) = (Z1x.Z2x + Z1y.Z2y, Z1y.Z2x - Z1x.Z2y)
IF (REAL(Z1)*AIMAG(Z2) .GT. REAL(Z2)*AIMAG(Z1)) T = -T !Adjust the sign via the cross product.
T = ATAN2D(AIMAG(Z),REAL(Z)) !Madly, arctan(x,y) is ATAN(Y,X)!
WRITE (6,10) B1,Z1,B2,Z2,T !Two sets of numbers, and a result.
WRITE (6,10) B1,Z1,B2,Z2,T !Two sets of numbers, and a result.
10 FORMAT (2(F14.4,"(",F9.6,",",F9.6,")"),F9.3) !Two lots, and a tail.
10 FORMAT (2(F14.4,"(",F9.6,",",F9.6,")"),F9.3) !Two lots, and a tail.
Line 149: Line 153:
REAL*8 B(24) !Just prepare a wad of values.
REAL*8 B(24) !Just prepare a wad of values.
DATA B/20D0,45D0, -45D0,45D0, -85D0,90D0, -95D0,90D0, !As specified.
DATA B/20D0,45D0, -45D0,45D0, -85D0,90D0, -95D0,90D0, !As specified.
1 -45D0,125D0, -45D0,145D0, 29.4803D0,-88.6381,
1 -45D0,125D0, -45D0,145D0, 29.4803D0,-88.6381D0,
2 -78.3251D0, -159.036D0,
2 -78.3251D0, -159.036D0,
3 -70099.74233810938D0, 29840.67437876723D0,
3 -70099.74233810938D0, 29840.67437876723D0,
4 -165313.6666297357D0, 33693.9894517456D0,
4 -165313.6666297357D0, 33693.9894517456D0,
5 1174.8380510598456D0, -154146.66490124757D0,
5 1174.8380510598456D0, -154146.66490124757D0,
6 60175.77306795546D0, 42213.07192354373D0/
6 60175.77306795546D0, 42213.07192354373D0/

WRITE (6,1) "B1","x","y","B2","x","y","B2 - B1"
WRITE (6,1) ("B",I,"x","y", I = 1,2) !Or, one could just list them twice.
1 FORMAT (28X,"Bearing calculations, in degrees"//
1 FORMAT (28X,"Bearing calculations, in degrees"//
* 2(A14,"(",A9,",",A9,")"),A9) !Compare format 10, above.
* 2(A13,I1,"(",A9,",",A9,")"),A9) !Compare format 10, above.

DO I = 1,23,2 !Step through the pairs.
DO I = 1,23,2 !Step through the pairs.
CALL BDIFF(B(I),B(I + 1))
CALL BDIFF(B(I),B(I + 1))
END DO
END DO

END</lang>
END</lang>
The output shows the stages:
The output shows the stages:
Line 169: Line 173:
Bearing calculations, in degrees
Bearing calculations, in degrees


B1( x, y) B2( x, y) B2 - B1
B1( x, y) B2( x, y)
20.0000( 0.342020, 0.939693) 45.0000( 0.707107, 0.707107) 25.000
20.0000( 0.342020, 0.939693) 45.0000( 0.707107, 0.707107) 25.000
-45.0000(-0.707107, 0.707107) 45.0000( 0.707107, 0.707107) 90.000
-45.0000(-0.707107, 0.707107) 45.0000( 0.707107, 0.707107) 90.000
Line 176: Line 180:
-45.0000(-0.707107, 0.707107) 125.0000( 0.819152,-0.573576) 170.000
-45.0000(-0.707107, 0.707107) 125.0000( 0.819152,-0.573576) 170.000
-45.0000(-0.707107, 0.707107) 145.0000( 0.573576,-0.819152) -170.000
-45.0000(-0.707107, 0.707107) 145.0000( 0.573576,-0.819152) -170.000
29.4803( 0.492124, 0.870525) -88.6381(-0.999718, 0.023768) -118.118
29.4803( 0.492124, 0.870525) -88.6381(-0.999718, 0.023767) -118.118
-78.3251(-0.979312, 0.202358) -159.0360(-0.357781,-0.933805) -80.711
-78.3251(-0.979312, 0.202358) -159.0360(-0.357781,-0.933805) -80.711
-70099.7423( 0.984016,-0.178078) 29840.6744(-0.633734, 0.773551) -139.584
-70099.7423( 0.984016,-0.178078) 29840.6744(-0.633734, 0.773551) -139.584