/**
 * \file signtest.cpp
 * \brief Test treatment of +/-0 and +/-180
 *
 * Copyright (c) Charles Karney (2022) <charles@karney.com> and licensed
 * under the MIT/X11 License.  For more information, see
 * https://geographiclib.sourceforge.io/
 **********************************************************************/

#include <iostream>
#include <limits>
#include <string>
#include <GeographicLib/Math.hpp>
#include <GeographicLib/Utility.hpp>
#include <GeographicLib/DMS.hpp>
#include <GeographicLib/Geodesic.hpp>
#include <GeographicLib/GeodesicExact.hpp>
#include <GeographicLib/UTMUPS.hpp>
#include <GeographicLib/MGRS.hpp>
#include <GeographicLib/PolygonArea.hpp>

// On Centos 7, remquo(810.0, 90.0 &q) returns 90.0 with q=8.  Rather than
// lousing up Math.cpp with this problem we just skip the failing tests.
#if defined(__GNUG__) && __GNUG__ < 11
#  define BUGGY_REMQUO 1
#else
#  define BUGGY_REMQUO 0
#endif

// Visual Studio C++ does not implement round to even, see
// https://developercommunity.visualstudio.com/t/stdfixed-output-does-not-implement-round-to-even/1671088
// reported on 2022-02-20.  Problem can't be reproduced at Microsoft, probably
// this is some issue with the runtime library.
//
// Broken on my Windows desktop system vc14 thru vc16
// Fixed on my Windows laptop system vc16 and vc17
// Broken on build-open machines, vc14 and vc15+win32
// Fixed on build-open machines, vc15+x64 and vc16
// Let's assume that it's OK for vc16 and later
#if defined(_MSC_VER) && _MSC_VER < 1920
#  define BUGGY_ROUNDING 1
#else
#  define BUGGY_ROUNDING 0
#endif

// use "do { } while (false)" idiom so it can be punctuated like a statement.
#define REMQUO_CHECK(CMD) do { if (!BUGGY_REMQUO) { CMD; } } while (false)
#define ROUNDING_CHECK(CMD) do { if (!BUGGY_ROUNDING) { CMD; } } while (false)

using namespace std;
using namespace GeographicLib;

typedef Math::real T;

static int equiv(T x, T y) {
  using std::isnan;             // Needed for Centos 7, ubuntu 14
  return ( (isnan(x) && isnan(y)) || (x == y && signbit(x) == signbit(y)) ) ?
    0 : 1;
}

static int checkEquals(T x, T y, T d) {
  if (fabs(x - y) <= d)
    return 0;
  cout << "checkEquals fails: " << x << " != " << y << " +/- " << d << "\n";
  return 1;
}

#define check(expr, r) do {                         \
    T s = T(r),  t = expr;                          \
    if (equiv(s, t)) {                              \
      cout << "Line " << __LINE__ << ": " << #expr  \
           << " != " << #r << " (" << t << ")\n";   \
      ++n;                                          \
    }                                               \
  } while (false)

#define checksincosd(x, s, c) do {                  \
    T sx, cx;                                       \
    Math::sincosd(x, sx, cx);                       \
    if (equiv(s, sx)) {                             \
      cout << "Line " << __LINE__ << ": sin(" << x  \
           << ") != " << s << " (" << sx << ")\n";  \
      ++n;                                          \
    }                                               \
    if (equiv(c, cx)) {                             \
      cout << "Line " << __LINE__ << ": cos(" << x  \
           << ") != " << c << " (" << cx << ")\n";  \
      ++n;                                          \
    }                                               \
  } while (false)

#define strcheck(expr, r) do {                     \
    string s = string(r),  ss = expr;              \
    if (!(s == ss)) {                              \
      cout << "Line " << __LINE__ << ": " << #expr \
           << " != " << #r << " (" << ss << ")\n"; \
      ++n;                                         \
    }                                              \
  } while (false)

int main() {
  T inf = Math::infinity(),
    nan = Math::NaN(),
    eps = numeric_limits<T>::epsilon(),
    ovf = 1 / Math::sq(eps),
    e;
  int n = 0;

  check( Math::AngRound(-eps/32), -eps/32);
  check( Math::AngRound(-eps/64), -0.0   );
  check( Math::AngRound(-  T(0)), -0.0   );
  check( Math::AngRound(   T(0)), +0.0   );
  check( Math::AngRound( eps/64), +0.0   );
  check( Math::AngRound( eps/32), +eps/32);
  check( Math::AngRound((1-2*eps)/64), (1-2*eps)/64);
  check( Math::AngRound((1-eps  )/64),  T(1)    /64);
  check( Math::AngRound((1-eps/2)/64),  T(1)    /64);
  check( Math::AngRound((1-eps/4)/64),  T(1)    /64);
  check( Math::AngRound( T(1)    /64),  T(1)    /64);
  check( Math::AngRound((1+eps/2)/64),  T(1)    /64);
  check( Math::AngRound((1+eps  )/64),  T(1)    /64);
  check( Math::AngRound((1+2*eps)/64), (1+2*eps)/64);
  check( Math::AngRound((1-eps  )/32), (1-eps  )/32);
  check( Math::AngRound((1-eps/2)/32),  T(1)    /32);
  check( Math::AngRound((1-eps/4)/32),  T(1)    /32);
  check( Math::AngRound( T(1)    /32),  T(1)    /32);
  check( Math::AngRound((1+eps/2)/32),  T(1)    /32);
  check( Math::AngRound((1+eps  )/32), (1+eps  )/32);
  check( Math::AngRound((1-eps  )/16), (1-eps  )/16);
  check( Math::AngRound((1-eps/2)/16), (1-eps/2)/16);
  check( Math::AngRound((1-eps/4)/16),  T(1)    /16);
  check( Math::AngRound( T(1)    /16),  T(1)    /16);
  check( Math::AngRound((1+eps/4)/16),  T(1)    /16);
  check( Math::AngRound((1+eps/2)/16),  T(1)    /16);
  check( Math::AngRound((1+eps  )/16), (1+eps  )/16);
  check( Math::AngRound((1-eps  )/ 8), (1-eps  )/ 8);
  check( Math::AngRound((1-eps/2)/ 8), (1-eps/2)/ 8);
  check( Math::AngRound((1-eps/4)/ 8),  T(1)    / 8);
  check( Math::AngRound((1+eps/2)/ 8),  T(1)    / 8);
  check( Math::AngRound((1+eps  )/ 8), (1+eps  )/ 8);
  check( Math::AngRound( 1-eps      ),  1-eps      );
  check( Math::AngRound( 1-eps/2    ),  1-eps/2    );
  check( Math::AngRound( 1-eps/4    ),  1          );
  check( Math::AngRound( T(1)       ),  1          );
  check( Math::AngRound( 1+eps/4    ),  1          );
  check( Math::AngRound( 1+eps/2    ),  1          );
  check( Math::AngRound( 1+eps      ),  1+  eps    );
  check( Math::AngRound(T(90)-64*eps),  90-64*eps  );
  check( Math::AngRound(T(90)-32*eps),  90         );
  check( Math::AngRound(T(90)       ),  90         );

  checksincosd(-  inf ,  nan,  nan);
  REMQUO_CHECK( checksincosd(-T(810), -1.0, +0.0) );
  checksincosd(-T(720), -0.0, +1.0);
  checksincosd(-T(630), +1.0, +0.0);
  checksincosd(-T(540), -0.0, -1.0);
  checksincosd(-T(450), -1.0, +0.0);
  checksincosd(-T(360), -0.0, +1.0);
  checksincosd(-T(270), +1.0, +0.0);
  checksincosd(-T(180), -0.0, -1.0);
  checksincosd(-T( 90), -1.0, +0.0);
  checksincosd(-T(  0), -0.0, +1.0);
  checksincosd(+T(  0), +0.0, +1.0);
  checksincosd(+T( 90), +1.0, +0.0);
  checksincosd(+T(180), +0.0, -1.0);
  checksincosd(+T(270), -1.0, +0.0);
  checksincosd(+T(360), +0.0, +1.0);
  checksincosd(+T(450), +1.0, +0.0);
  checksincosd(+T(540), +0.0, -1.0);
  checksincosd(+T(630), -1.0, +0.0);
  checksincosd(+T(720), +0.0, +1.0);
  REMQUO_CHECK( checksincosd(+T(810), +1.0, +0.0) );
  checksincosd(+  inf ,  nan,  nan);
  checksincosd(   nan ,  nan,  nan);

  {
    T s1, c1, s2, c2, s3, c3;
    Math::sincosd(T(         9), s1, c1);
    Math::sincosd(T(        81), s2, c2);
    Math::sincosd(T(-123456789), s3, c3);
    int j = equiv(s1, c2) + equiv(c1, s2);
    REMQUO_CHECK( j += equiv(s1, s3) + equiv(c1, -c3) );
    if (j) {
      cout << "Line " << __LINE__ << " : sincos accuracy fail\n";
      ++n;
    }
  }

  check( Math::sind(-  inf ),  nan);
  check( Math::sind(-T(720)), -0.0);
  check( Math::sind(-T(540)), -0.0);
  check( Math::sind(-T(360)), -0.0);
  check( Math::sind(-T(180)), -0.0);
  check( Math::sind(-T(  0)), -0.0);
  check( Math::sind(+T(  0)), +0.0);
  check( Math::sind(+T(180)), +0.0);
  check( Math::sind(+T(360)), +0.0);
  check( Math::sind(+T(540)), +0.0);
  check( Math::sind(+T(720)), +0.0);
  check( Math::sind(+  inf ),  nan);

  check( Math::cosd(-  inf ),  nan);
  REMQUO_CHECK( check( Math::cosd(-T(810)), +0.0) );
  check( Math::cosd(-T(630)), +0.0);
  check( Math::cosd(-T(450)), +0.0);
  check( Math::cosd(-T(270)), +0.0);
  check( Math::cosd(-T( 90)), +0.0);
  check( Math::cosd(+T( 90)), +0.0);
  check( Math::cosd(+T(270)), +0.0);
  check( Math::cosd(+T(450)), +0.0);
  check( Math::cosd(+T(630)), +0.0);
  REMQUO_CHECK( check( Math::cosd(+T(810)), +0.0) );
  check( Math::cosd(+  inf ),  nan);

#if !(defined(_MSC_VER) && _MSC_VER <= 1900)
  check( Math::tand(-  inf ),  nan);
#endif
  REMQUO_CHECK( check( Math::tand(-T(810)), -ovf) );
  check( Math::tand(-T(720)), -0.0);
  check( Math::tand(-T(630)), +ovf);
  check( Math::tand(-T(540)), +0.0);
  check( Math::tand(-T(450)), -ovf);
  check( Math::tand(-T(360)), -0.0);
  check( Math::tand(-T(270)), +ovf);
  check( Math::tand(-T(180)), +0.0);
  check( Math::tand(-T( 90)), -ovf);
  check( Math::tand(-T(  0)), -0.0);
  check( Math::tand(+T(  0)), +0.0);
  check( Math::tand(+T( 90)), +ovf);
  check( Math::tand(+T(180)), -0.0);
  check( Math::tand(+T(270)), -ovf);
  check( Math::tand(+T(360)), +0.0);
  check( Math::tand(+T(450)), +ovf);
  check( Math::tand(+T(540)), -0.0);
  check( Math::tand(+T(630)), -ovf);
  check( Math::tand(+T(720)), +0.0);
  REMQUO_CHECK( check( Math::tand(+T(810)), +ovf) );
#if !(defined(_MSC_VER) && _MSC_VER <= 1900)
  check( Math::tand(+  inf ),  nan);
#endif

  check( Math::atan2d(+T(0), -T(0)), +180 );
  check( Math::atan2d(-T(0), -T(0)), -180 );
  check( Math::atan2d(+T(0), +T(0)), +0.0 );
  check( Math::atan2d(-T(0), +T(0)), -0.0 );
  check( Math::atan2d(+T(0), -T(1)), +180 );
  check( Math::atan2d(-T(0), -T(1)), -180 );
  check( Math::atan2d(+T(0), +T(1)), +0.0 );
  check( Math::atan2d(-T(0), +T(1)), -0.0 );
  check( Math::atan2d(-T(1), +T(0)),  -90 );
  check( Math::atan2d(-T(1), -T(0)),  -90 );
  check( Math::atan2d(+T(1), +T(0)),  +90 );
  check( Math::atan2d(+T(1), -T(0)),  +90 );
  check( Math::atan2d(+T(1),  -inf), +180 );
  check( Math::atan2d(-T(1),  -inf), -180 );
  check( Math::atan2d(+T(1),  +inf), +0.0 );
  check( Math::atan2d(-T(1),  +inf), -0.0 );
  check( Math::atan2d( +inf, +T(1)),  +90 );
  check( Math::atan2d( +inf, -T(1)),  +90 );
  check( Math::atan2d( -inf, +T(1)),  -90 );
  check( Math::atan2d( -inf, -T(1)),  -90 );
  check( Math::atan2d( +inf,  -inf), +135 );
  check( Math::atan2d( -inf,  -inf), -135 );
  check( Math::atan2d( +inf,  +inf),  +45 );
  check( Math::atan2d( -inf,  +inf),  -45 );
  check( Math::atan2d(  nan, +T(1)),  nan );
  check( Math::atan2d(+T(1),   nan),  nan );

  {
    T s = 7e-16;
    if ( equiv( Math::atan2d(s, -T(1)), 180 - Math::atan2d(s, T(1)) ) ) {
      cout << "Line " << __LINE__ << " : atan2d accuracy fail\n";
      ++n;
    }
  }

  check( Math::sum(+T(9), -T(9), e), +0.0 );
  check( Math::sum(-T(9), +T(9), e), +0.0 );
  check( Math::sum(-T(0), +T(0), e), +0.0 );
  check( Math::sum(+T(0), -T(0), e), +0.0 );
  check( Math::sum(-T(0), -T(0), e), -0.0 );
  check( Math::sum(+T(0), +T(0), e), +0.0 );

  check( Math::AngNormalize(-T(900)), -180 );
  check( Math::AngNormalize(-T(720)), -0.0 );
  check( Math::AngNormalize(-T(540)), -180 );
  check( Math::AngNormalize(-T(360)), -0.0 );
  check( Math::AngNormalize(-T(180)), -180 );
  check( Math::AngNormalize(  -T(0)), -0.0 );
  check( Math::AngNormalize(  +T(0)), +0.0 );
  check( Math::AngNormalize( T(180)), +180 );
  check( Math::AngNormalize( T(360)), +0.0 );
  check( Math::AngNormalize( T(540)), +180 );
  check( Math::AngNormalize( T(720)), +0.0 );
  check( Math::AngNormalize( T(900)), +180 );

  check( Math::AngDiff(+T(  0), +T(  0), e), +0.0 );
  check( Math::AngDiff(+T(  0), -T(  0), e), -0.0 );
  check( Math::AngDiff(-T(  0), +T(  0), e), +0.0 );
  check( Math::AngDiff(-T(  0), -T(  0), e), +0.0 );
  check( Math::AngDiff(+T(  5), +T(365), e), +0.0 );
  check( Math::AngDiff(+T(365), +T(  5), e), -0.0 );
  check( Math::AngDiff(+T(  5), +T(185), e), +180.0 );
  check( Math::AngDiff(+T(185), +T(  5), e), -180.0 );
  check( Math::AngDiff( +eps  , +T(180), e), +180.0 );
  check( Math::AngDiff( -eps  , +T(180), e), -180.0 );
  check( Math::AngDiff( +eps  , -T(180), e), +180.0 );
  check( Math::AngDiff( -eps  , -T(180), e), -180.0 );

  {
    T x = 138 + 128 * eps, y = -164;
    if ( equiv( Math::AngDiff(x, y), 58 - 128 * eps ) ) {
      cout << "Line " << __LINE__ << " : AngDiff accuracy fail\n";
      ++n;
    }
  }

  check( Utility::val<T>("+0"), +0.0 );
  check( Utility::val<T>("-0"), -0.0 );
  check( Utility::val<T>("nan"), nan );
  check( Utility::val<T>("+inf"), +inf );
  check( Utility::val<T>( "inf"), +inf );
  check( Utility::val<T>("-inf"), -inf );

  strcheck( Utility::str<T>( nan, 0),  "nan" );
  strcheck( Utility::str<T>(-inf, 0), "-inf" );
  strcheck( Utility::str<T>(+inf, 0),  "inf" );
  strcheck( Utility::str<T>(-T(3.5), 0),   "-4" );
  ROUNDING_CHECK( strcheck( Utility::str<T>(-T(2.5), 0),   "-2" ) );
  strcheck( Utility::str<T>(-T(1.5), 0),   "-2" );
  ROUNDING_CHECK( strcheck( Utility::str<T>(-T(0.5), 0),   "-0" ) );
  strcheck( Utility::str<T>(-T(0  ), 0),   "-0" );
  strcheck( Utility::str<T>(+T(0  ), 0),    "0" );
  ROUNDING_CHECK( strcheck( Utility::str<T>(+T(0.5), 0),    "0" ) );
  strcheck( Utility::str<T>(+T(1.5), 0),    "2" );
  ROUNDING_CHECK( strcheck( Utility::str<T>(+T(2.5), 0),    "2" ) );
  strcheck( Utility::str<T>(+T(3.5), 0),    "4" );
  strcheck( Utility::str<T>(-T(1.75), 1), "-1.8");
  ROUNDING_CHECK( strcheck( Utility::str<T>(-T(1.25), 1), "-1.2") );
  strcheck( Utility::str<T>(-T(0.75), 1), "-0.8");
  ROUNDING_CHECK( strcheck( Utility::str<T>(-T(0.25), 1), "-0.2") );
  strcheck( Utility::str<T>(-T(0   ), 1), "-0.0");
  strcheck( Utility::str<T>(+T(0   ), 1),  "0.0");
  ROUNDING_CHECK( strcheck( Utility::str<T>(+T(0.25), 1),  "0.2") );
  strcheck( Utility::str<T>(+T(0.75), 1),  "0.8");
  ROUNDING_CHECK( strcheck( Utility::str<T>(+T(1.25), 1),  "1.2") );
  strcheck( Utility::str<T>(+T(1.75), 1),  "1.8");

  strcheck( DMS::Encode( nan, DMS::DEGREE, 0),  "nan" );
  strcheck( DMS::Encode(-inf, DMS::DEGREE, 0), "-inf" );
  strcheck( DMS::Encode(+inf, DMS::DEGREE, 0),  "inf" );
  strcheck( DMS::Encode(-T(3.5), DMS::DEGREE, 0),   "-4" );
  ROUNDING_CHECK( strcheck( DMS::Encode(-T(2.5), DMS::DEGREE, 0),   "-2" ) );
  strcheck( DMS::Encode(-T(1.5), DMS::DEGREE, 0),   "-2" );
  ROUNDING_CHECK( strcheck( DMS::Encode(-T(0.5), DMS::DEGREE, 0),   "-0" ) );
  strcheck( DMS::Encode(-T(0  ), DMS::DEGREE, 0),   "-0" );
  strcheck( DMS::Encode(+T(0  ), DMS::DEGREE, 0),    "0" );
  ROUNDING_CHECK( strcheck( DMS::Encode(+T(0.5), DMS::DEGREE, 0),    "0" ) );
  strcheck( DMS::Encode(+T(1.5), DMS::DEGREE, 0),    "2" );
  ROUNDING_CHECK( strcheck( DMS::Encode(+T(2.5), DMS::DEGREE, 0),    "2" ) );
  strcheck( DMS::Encode(+T(3.5), DMS::DEGREE, 0),    "4" );
  strcheck( DMS::Encode(-T(1.75), DMS::DEGREE, 1), "-1.8");
  ROUNDING_CHECK( strcheck( DMS::Encode(-T(1.25), DMS::DEGREE, 1), "-1.2") );
  strcheck( DMS::Encode(-T(0.75), DMS::DEGREE, 1), "-0.8");
  ROUNDING_CHECK( strcheck( DMS::Encode(-T(0.25), DMS::DEGREE, 1), "-0.2") );
  strcheck( DMS::Encode(-T(0   ), DMS::DEGREE, 1), "-0.0");
  strcheck( DMS::Encode(+T(0   ), DMS::DEGREE, 1),  "0.0");
  ROUNDING_CHECK( strcheck( DMS::Encode(+T(0.25), DMS::DEGREE, 1),  "0.2") );
  strcheck( DMS::Encode(+T(0.75), DMS::DEGREE, 1),  "0.8");
  ROUNDING_CHECK( strcheck( DMS::Encode(+T(1.25), DMS::DEGREE, 1),  "1.2") );
  strcheck( DMS::Encode(+T(1.75), DMS::DEGREE, 1),  "1.8");
  strcheck( DMS::Encode( T(1e20), DMS::DEGREE, 0), "100000000000000000000");
  strcheck( DMS::Encode( T(1e21), DMS::DEGREE, 0), "1000000000000000000000");
  {
    T t = -(1 + 2/T(60) + T(2.99)/3600);

    strcheck( DMS::Encode( t,DMS::DEGREE,0,DMS::NONE     ), "-1"         );
    strcheck( DMS::Encode( t,DMS::DEGREE,0,DMS::LATITUDE ), "01S"         );
    strcheck( DMS::Encode( t,DMS::DEGREE,0,DMS::LONGITUDE),"001W"         );
    strcheck( DMS::Encode(-t,DMS::DEGREE,0,DMS::AZIMUTH  ),"001"          );
    strcheck( DMS::Encode( t,DMS::DEGREE,1,DMS::NONE     ), "-1.0"       );
    strcheck( DMS::Encode( t,DMS::DEGREE,1,DMS::LATITUDE ), "01.0S"       );
    strcheck( DMS::Encode( t,DMS::DEGREE,1,DMS::LONGITUDE),"001.0W"       );
    strcheck( DMS::Encode(-t,DMS::DEGREE,1,DMS::AZIMUTH  ),"001.0"        );
    strcheck( DMS::Encode( t,DMS::MINUTE,0,DMS::NONE     ), "-1d02'"      );
    strcheck( DMS::Encode( t,DMS::MINUTE,0,DMS::LATITUDE ), "01d02'S"      );
    strcheck( DMS::Encode( t,DMS::MINUTE,0,DMS::LONGITUDE),"001d02'W"      );
    strcheck( DMS::Encode(-t,DMS::MINUTE,0,DMS::AZIMUTH  ),"001d02'"       );
    strcheck( DMS::Encode( t,DMS::MINUTE,1,DMS::NONE     ), "-1d02.0'"    );
    strcheck( DMS::Encode( t,DMS::MINUTE,1,DMS::LATITUDE ), "01d02.0'S"    );
    strcheck( DMS::Encode( t,DMS::MINUTE,1,DMS::LONGITUDE),"001d02.0'W"    );
    strcheck( DMS::Encode(-t,DMS::MINUTE,1,DMS::AZIMUTH  ),"001d02.0'"     );
    strcheck( DMS::Encode( t,DMS::SECOND,0,DMS::NONE     ), "-1d02'03\""  );
    strcheck( DMS::Encode( t,DMS::SECOND,0,DMS::LATITUDE ), "01d02'03\"S"  );
    strcheck( DMS::Encode( t,DMS::SECOND,0,DMS::LONGITUDE),"001d02'03\"W"  );
    strcheck( DMS::Encode(-t,DMS::SECOND,0,DMS::AZIMUTH  ),"001d02'03\""   );
    strcheck( DMS::Encode( t,DMS::SECOND,1,DMS::NONE     ), "-1d02'03.0\"");
    strcheck( DMS::Encode( t,DMS::SECOND,1,DMS::LATITUDE ), "01d02'03.0\"S");
    strcheck( DMS::Encode( t,DMS::SECOND,1,DMS::LONGITUDE),"001d02'03.0\"W");
    strcheck( DMS::Encode(-t,DMS::SECOND,1,DMS::AZIMUTH  ),"001d02'03.0\"" );
  }
  DMS::flag ind;
  check( DMS::Decode(" +0 ", ind),  +0.0 );
  check( DMS::Decode("-0  ", ind),  -0.0 );
  check( DMS::Decode(" nan", ind),   nan );
  check( DMS::Decode("+inf", ind),  +inf );
  check( DMS::Decode(" inf", ind),  +inf );
  check( DMS::Decode("-inf", ind),  -inf );
  check( DMS::Decode(" +0N", ind),  +0.0 );
  check( DMS::Decode("-0N ", ind),  -0.0 );
  check( DMS::Decode("+0S ", ind),  -0.0 );
  check( DMS::Decode(" -0S", ind),  +0.0 );

  {
    // azimuth of geodesic line with points on equator determined by signs of
    // latitude
    // lat1 lat2 azi1/2
    T C[2][3] = {
      { +T(0), -T(0), 180 },
      { -T(0), +T(0),   0 }
    };
    const Geodesic& g = Geodesic::WGS84();
    const GeodesicExact& ge = GeodesicExact::WGS84();
    T azi1, azi2;
    int i = 0;
    for (int k = 0; k < 2; ++k) {
      g.Inverse(C[k][0], T(0), C[k][1], T(0), azi1, azi2);
      if ( equiv(azi1, C[k][2]) + equiv(azi2, C[k][2]) ) ++i;
      ge.Inverse(C[k][0], T(0), C[k][1], T(0), azi1, azi2);
      if ( equiv(azi1, C[k][2]) + equiv(azi2, C[k][2]) ) ++i;
    }
    if (i) {
      cout << "Line " << __LINE__
           << ": Geodesic::Inverse coincident points on equator fail\n";
      ++n;
    }
  }

  {
    // Does the nearly antipodal equatorial solution go north or south?
    // lat1 lat2 azi1 azi2
    T C[2][4] = {
      { +T(0), +T(0),  56, 124},
      { -T(0), -T(0), 124,  56}
    };
    const Geodesic& g = Geodesic::WGS84();
    const GeodesicExact& ge = GeodesicExact::WGS84();
    T azi1, azi2;
    int i = 0;
    for (int k = 0; k < 2; ++k) {
      g.Inverse(C[k][0], T(0), C[k][1], T(179.5), azi1, azi2);
      i += checkEquals(azi1, C[k][2], 1) + checkEquals(azi2, C[k][3], 1);
      ge.Inverse(C[k][0], T(0), C[k][1], T(179.5), azi1, azi2);
      i += checkEquals(azi1, C[k][2], 1) + checkEquals(azi2, C[k][3], 1);
    }
    if (i) {
      cout << "Line " << __LINE__
           << ": Geodesic::Inverse nearly antipodal points on equator fail\n";
      ++n;
    }
  }

  {
    // How does the exact antipodal equatorial path go N/S + E/W
    // lat1 lat2 lon2 azi1 azi2
    T C[4][5] = {
      { +T(0), +T(0), +180,   +T(0), +180},
      { -T(0), -T(0), +180, +180,   +T(0)},
      { +T(0), +T(0), -180,   -T(0), -180},
      { -T(0), -T(0), -180, -180,   -T(0)}
    };
    const Geodesic& g = Geodesic::WGS84();
    const GeodesicExact& ge = GeodesicExact::WGS84();
    T azi1, azi2;
    int i = 0;
    for (int k = 0; k < 4; ++k) {
      g.Inverse(C[k][0], T(0), C[k][1], C[k][2], azi1, azi2);
      if ( equiv(azi1, C[k][3]) + equiv(azi2, C[k][4]) ) ++i;
      ge.Inverse(C[k][0], T(0), C[k][1], C[k][2], azi1, azi2);
      if ( equiv(azi1, C[k][3]) + equiv(azi2, C[k][4]) ) ++i;
    }
    if (i) {
      cout << "Line " << __LINE__
           << ": Geodesic::Inverse antipodal points on equator fail\n";
      ++n;
    }
  }

  {
    // Antipodal points on the equator with prolate ellipsoid
    // lon2 azi1/2
    T C[2][2] = {
      { +180, +90 },
      { -180, -90 }
    };
    const Geodesic g(T(6.4e6), -1/T(300));
    const GeodesicExact ge(T(6.4e6), -1/T(300));
    T azi1, azi2;
    int i = 0;
    for (int k = 0; k < 2; ++k) {
      g.Inverse(T(0), T(0), T(0), C[k][0], azi1, azi2);
      if ( equiv(azi1, C[k][1]) + equiv(azi2, C[k][1]) ) ++i;
      ge.Inverse(T(0), T(0), T(0), C[k][0], azi1, azi2);
      if ( equiv(azi1, C[k][1]) + equiv(azi2, C[k][1]) ) ++i;
    }
    if (i) {
      cout << "Line " << __LINE__
           << ": Geodesic::Inverse antipodal points on equator, prolate, fail\n";
      ++n;
    }
  }

  {
    // azimuths = +/-0 and +/-180 for the direct problem
    // azi1, lon2, azi2
    T C[4][3] = {
      { +T(0), +180, +180  },
      { -T(0), -180, -180  },
      { +180 , +180, +T(0) },
      { -180 , -180, -T(0) }
    };
    const Geodesic& g = Geodesic::WGS84();
    const GeodesicExact& ge = GeodesicExact::WGS84();
    T lon2, azi2;
    int i = 0;
    for (int k = 0; k < 4; ++k) {
      T t;
      g.GenDirect(T(0), T(0), C[k][0], false, T(15e6),
                  Geodesic::LONGITUDE | Geodesic::AZIMUTH |
                  Geodesic::LONG_UNROLL,
                  t, lon2, azi2,
                  t, t, t, t, t);
      if ( equiv(lon2, C[k][1]) + equiv(azi2, C[k][2]) ) ++i;
      ge.GenDirect(T(0), T(0), C[k][0], false, T(15e6),
                   Geodesic::LONGITUDE | Geodesic::AZIMUTH |
                   Geodesic::LONG_UNROLL,
                   t, lon2, azi2,
                   t, t, t, t, t);
      if ( equiv(lon2, C[k][1]) + equiv(azi2, C[k][2]) ) ++i;
    }
    if (i) {
      cout << "Line " << __LINE__
           << ": Geodesic::Direct azi1 = +/-0 +/-180, fail\n";
      ++n;
    }
  }

  {
    // lat = +/-0 in UTMUPS::Forward
    // lat y northp
    T C[2][3] = {
      { +T(0), T(0), 1 },
      { -T(0), 10e6, 0 }
    };
    int i = 0;
    bool northp; int zone; T x, y; string mgrs;
    for (int k = 0; k < 2; ++k) {
      UTMUPS::Forward(C[k][0], T(3), zone, northp, x, y);
      if ( equiv(y, C[k][1]) + (northp == (C[k][2] > 0) ? 0 : 1) ) ++i;
      MGRS::Forward(zone, northp, x, y, 2, mgrs);
      if (!( mgrs == (k == 0 ? "31NEA0000" : "31MEV0099") )) ++i;
      MGRS::Forward(zone, northp, x, y, +T(0), 2, mgrs);
      if (!( mgrs == (k == 0 ? "31NEA0000" : "31MEV0099") )) ++i;
      MGRS::Forward(zone, northp, x, y, -T(0), 2, mgrs);
      if (!( mgrs == (k == 0 ? "31NEA0000" : "31MEV0099") )) ++i;
    }
    if (i) {
      cout << "Line " << __LINE__
           << ": UTMUPS/MGRS::Forward lat = +/-0, fail\n";
      ++n;
    }
  }

  {
    // Implement check on AddEdge bug: AddPoint(0,0) +
    // AddEdge(90, 1000) + AddEdge(0, 1000) + AddEdge(-90, 0).  The area
    // should be 1e6.  Prior to the fix it was 1e6 - A/2, where A = ellipsoid
    // area.
    // add_test (NAME Planimeter29 COMMAND Planimeter ...)
    const Geodesic& g = Geodesic::WGS84();
    PolygonArea polygon(g);
    polygon.AddPoint(0, 0);
    polygon.AddEdge( 90, 1000);
    polygon.AddEdge(  0, 1000);
    polygon.AddEdge(-90, 1000);
    T perim, area;
    // The area should be 1e6.  Prior to the fix it was 1e6 - A/2, where
    // A = ellipsoid area.
    polygon.Compute(false, true, perim, area);
    int i = checkEquals(area, 1000000, 0.01);
    if (i)  {
      cout << "Line " << __LINE__
           << ": Planimeter29 fail\n";
      ++n;
    }
  }

  if (n) {
    cout << n << " failure" << (n > 1 ? "s" : "") << "\n";
    return 1;
  }
}
