Finding the angle between two bearings is often confusing.[1]



Task

Find the angle which is the result of the subtraction b2 - b1, where b1 and b2 are the bearings.


Input bearings are expressed in the range   -180   to   +180   degrees.

The  result  is also expressed in the range   -180   to   +180   degrees.



Compute the angle for the following pairs:

 20 degrees (b1) and 45 degrees (b2)
 -45 and 45
 -85 and 90
 -95 and 90
 -45 and 125
 -45 and 145
 29.4803 and -88.6381
 -78.3251 and -159.036


Optional extra
 
Allow the input bearings to be any (finite) value.



Test cases

 -70099.74233810938 and 29840.67437876723
 -165313.6666297357 and 33693.9894517456
 1174.8380510598456 and -154146.66490124757
 60175.77306795546 and 42213.07192354373


 
-module(bearings).
 
%% API
-export([angle_sub_degrees/2,test/0]).
 
-define(RealAngleMultiplier,16#10000000000).
-define(DegreesPerTurn,360).
-define(Precision,9).
%%%===================================================================
%%% API
%%%===================================================================
 
%%--------------------------------------------------------------------
%% @doc
%% @spec
%% @end
%%--------------------------------------------------------------------
%%
angle_sub_degrees(B1,B2) when is_integer(B1), is_integer(B2) -> 
    angle_sub(B2-B1,?DegreesPerTurn);
angle_sub_degrees(B1,B2) ->
    NewB1 = trunc(B1*?RealAngleMultiplier),
    NewB2 = trunc(B2*?RealAngleMultiplier),
    round(angle_sub(NewB2 - NewB1,
	      ?DegreesPerTurn*?RealAngleMultiplier)
	/?RealAngleMultiplier,?Precision).
 
%%%===================================================================
%%% Internal functions
%%%===================================================================
 
%% delta normalises the angle difference.  Consider a turn from 350 degrees
%% to 20 degrees.  Subtraction results in 330 degress.  This is equivalent of
%% a turn in the other direction of 30 degrees, thus 330 degrees is equal
%% to -30 degrees.
 
 
angle_sub(Value,TurnSize) ->
    NormalisedValue = Value rem TurnSize,
    minimise_angle(NormalisedValue,TurnSize).
 
% X rem Turn result in 0..Turn for X > 0 and -Turn..0 for X < 0
% specification requires -Turn/2 < X < Turn/2.  This is achieved
% by adding or removing a turn as required.
% bsr 1 divides an integer by 2    
minimise_angle(Angle,Turn) when Angle + (Turn bsr 1) < 0 ->
    Angle+Turn;
minimise_angle(Angle,Turn) when Angle - (Turn bsr 1) > 0 -> 
    Angle-Turn;
minimise_angle(Angle,_) ->
    Angle.
 
round(Number,Precision) ->
    P = math:pow(10,Precision),
    round(Number*P)/P.
 
test() ->
    25 = angle_sub_degrees(20,45),
    90 = angle_sub_degrees(-45,45),
    175 = angle_sub_degrees(-85,90),
    -175 = angle_sub_degrees(-95,90),
    170 = angle_sub_degrees(-45,125),
    -170 = angle_sub_degrees(-45,145),
    -118.1184 = angle_sub_degrees( 29.4803,-88.6381), 
    -139.583283124=angle_sub_degrees(-70099.742338109,29840.674378767),
    -72.343918514=angle_sub_degrees( -165313.66662974,33693.989451746),
    -161.50295231=angle_sub_degrees(  1174.8380510598,-154146.66490125),
    37.298855589=angle_sub_degrees(  60175.773067955,42213.071923544),
 
    passed.
 