/*
 * (C) Copyright 1996-2017 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

#include <cmath>

#include "eckit/types/FloatCompare.h"

#include "eckit/testing/Test.h"

using namespace std;
using namespace eckit;
using namespace eckit::testing;

namespace eckit {
namespace test {

namespace {

bool is_equal(float a, float b, float epsilon, int maxUlps) {
    return eckit::types::is_approximately_equal(a, b, epsilon, maxUlps);
}

bool is_equal(float a, float b, float epsilon) {
    return eckit::types::is_approximately_equal(a, b, epsilon);
}

bool is_equal(float a, float b) {
    return eckit::types::is_approximately_equal(a, b, 0.00001f);
}

const float dEps = std::numeric_limits<float>::epsilon();
const float dInf = std::numeric_limits<float>::infinity();
const float sMin = std::numeric_limits<float>::denorm_min();
const float dMin = std::numeric_limits<float>::min();
const float dMax = std::numeric_limits<float>::max();
const float qNaN = std::numeric_limits<float>::quiet_NaN();
const float sNaN = std::numeric_limits<float>::signaling_NaN();

};

//-----------------------------------------------------------------------------

CASE( "test_large_numbers" )
{
   Log::info() << "test_large_numbers" << std::endl;

   EXPECT(  is_equal(1000000,       1000000      ));
   EXPECT(  is_equal(1000000,       1000000.00001));
   EXPECT(  is_equal(1000000.00001, 1000000      ));

   EXPECT(! is_equal(1000000.0,     1000001.0    ));
   EXPECT(! is_equal(1000001.0,     1000000.0    ));

   // -----------------------------------------------
   EXPECT(is_equal(dMax, dMax));
   EXPECT(is_equal(dMax, dMax, dEps));

   EXPECT(is_equal(dMin, dMin));
   EXPECT(is_equal(dMin, dMin, dEps));
}

CASE( "test_negative_large_numbers" )
{
   Log::info() << "test_negative_large_numbers " << dMin << std::endl;

   EXPECT(  is_equal(-1000000,       -1000000      ));
   EXPECT(  is_equal(-1000000,       -1000000.00001));
   EXPECT(  is_equal(-1000000.00001, -1000000      ));

   EXPECT(! is_equal(-1000000.0,     -1000001.0    ));
   EXPECT(! is_equal(-1000001.0,     -1000000.0    ));

   // -----------------------------------------------
   EXPECT(is_equal(-dMax, -dMax      ));
   EXPECT(is_equal(-dMax, -dMax, dEps));

   EXPECT(is_equal(-dMin, -dMin      ));
   EXPECT(is_equal(-dMin, -dMin, dEps));
}

CASE( "test_large_numbers_of_opposite_sign" )
{
    EXPECT(! is_equal(-1000000,       1000000      ));
    EXPECT(! is_equal(-1000000,       1000000.00001));
    EXPECT(! is_equal(-1000000.00001, 1000000      ));

    EXPECT(! is_equal(-1000000.0,     1000001.0    ));
    EXPECT(! is_equal(-1000001.0,     1000000.0    ));

    // -----------------------------------------------
    EXPECT(! is_equal(-dMax, dMax      ));
    EXPECT(! is_equal(-dMax, dMax, dEps));
}

CASE( "test_ulp_around_one" )
{
   Log::info() << "test_ulp_around_one" << std::endl;

   // ULP distances up to 10 are equal
   // Going right from 1 by eps increases distance by 1
   // Going left from 1 by eps increases distance by 2
   for (int i = 0; i <= 10; ++i) {
       EXPECT(is_equal(1.0 + i * dEps,   1.0,              dEps));
       EXPECT(is_equal(1.0,              1.0 + i * dEps,   dEps));
       EXPECT(is_equal(1.0 - i * dEps/2, 1.0,              dEps));
       EXPECT(is_equal(1.0,              1.0 - i * dEps/2, dEps));
   }
   // ULP distances greater 10 are not equal
   EXPECT(! is_equal(1.0 + 11 * dEps,   1.0,               dEps));
   EXPECT(! is_equal(1.0,               1.0 + 11 * dEps,   dEps));
   EXPECT(! is_equal(1.0 - 11 * dEps/2, 1.0,               dEps));
   EXPECT(! is_equal(1.0,               1.0 - 11 * dEps/2, dEps));
}

CASE( "test_numbers_around_one" )
{
   Log::info() << "test_numbers_around_one" << std::endl;

   EXPECT(  is_equal(1.0000001, 1.0000002));
   EXPECT(  is_equal(1.0000002, 1.0000001));

   EXPECT(  is_equal(1.123456,  1.123457 ));
   EXPECT(  is_equal(1.12345,   1.12344, 0.001));

   EXPECT(! is_equal(1.0001,    1.0002   ));
   EXPECT(! is_equal(1.0002,    1.0001   ));
}

CASE( "test_numbers_around_negative_one" )
{
   Log::info() << "test_numbers_around_negative_one" << std::endl;

   EXPECT(  is_equal(-1.0000001, -1.0000002));
   EXPECT(  is_equal(-1.0000002, -1.0000001));

   EXPECT(! is_equal(-1.0001,    -1.0002   ));
   EXPECT(! is_equal(-1.0002,    -1.0001   ));
}

CASE( "test_numbers_between_one_and_zero" )
{
   Log::info() << "test_numbers_between_one_and_zero" << std::endl;

   EXPECT(  is_equal(0.000000001000001, 0.000000001000002));
   EXPECT(  is_equal(0.000000001000002, 0.000000001000001));

   EXPECT(! is_equal(0.0012,            0.0011           ));
   EXPECT(! is_equal(0.0011,            0.0012           ));

}

CASE( "test_numbers_between_minusone_and_zero" )
{
   Log::info() << "test_numbers_between_minusone_and_zero" << std::endl;

   EXPECT(  is_equal(-0.000000001000001, -0.000000001000002));
   EXPECT(  is_equal(-0.000000001000002, -0.000000001000001));

   EXPECT(! is_equal(-0.0012,            -0.0011           ));
   EXPECT(! is_equal(-0.0011,            -0.0012           ));
}

CASE( "test_comparisons_involving_zero" )
{
   Log::info() << "test_comparisons_involving_zero" << std::endl;

   EXPECT(  is_equal(0.0,     0.0   ));
   EXPECT(  is_equal(0.0,    -0.0   ));
   EXPECT(  is_equal(-0.0,   -0.0   ));

   EXPECT(! is_equal(0.0001,  0.0   ));
   EXPECT(! is_equal(0.0,     0.0001));
   EXPECT(! is_equal(-0.0001, 0.0   ));
   EXPECT(! is_equal(0.0,    -0.0001));

   EXPECT(  is_equal(0.0,     1e-30, 0.01 ));
   EXPECT(  is_equal(1e-30,   0.0,   0.01 ));
   EXPECT(! is_equal(1e-30,   0.0,   1e-31));
   EXPECT(! is_equal(0.0,     1e-30, 1e-31));

   EXPECT(  is_equal(0.0,    -1e-30, 0.1  ));
   EXPECT(  is_equal(-1e-30,  0.0,   0.1  ));
   EXPECT(! is_equal(-1e-30,  0.0,   1e-31));
   EXPECT(! is_equal(0.0,    -1e-30, 1e-31));
}

CASE( "test_comparisons_involving_infinity" )
{
   Log::info() << "test_comparisons_involving_infinity" << std::endl;

   if (std::numeric_limits<float>::has_infinity) {
      EXPECT(  is_equal( dInf,  dInf));
      EXPECT(  is_equal(-dInf, -dInf));
      EXPECT(! is_equal( dInf,  dMax));
      EXPECT(! is_equal( dMax,  dInf));
      EXPECT(! is_equal(-dInf, -dMax));
      EXPECT(! is_equal(-dMax, -dInf));
   } else {
      Log::info() << "test_comparisons_involving_infinity NOT VALID on this platform" << std::endl;
   }
}

CASE( "test_comparisons_involving_nan" )
{
    Log::info() << "test_comparisons_involving_nan" << std::endl;

    // The value NaN (Not a Number) is used to represent a value that does not represent a real number.
    // NaN's are represented by a bit pattern with an exponent of all 1s and a non-zero fraction. T
    // there are two categories of NaN: QNaN (Quiet NaN) and SNaN (Signalling NaN).
    //
    // A QNaN is a NaN with the most significant fraction bit set.
    // QNaN's propagate freely through most arithmetic operations.
    // These values pop out of an operation when the result is not mathematically defined.

    // An SNaN is a NaN with the most significant fraction bit clear.
    // It is used to signal an exception when used in operations.
    // SNaN's can be handy to assign to uninitialized variables to trap premature usage.

    // Semantically, QNaN's denote indeterminate operations, while SNaN's denote invalid operations.

    EXPECT(! is_equal( qNaN,  qNaN));
    EXPECT(! is_equal( qNaN,  0.0 ));
    EXPECT(! is_equal(-0.0 ,  qNaN));
    EXPECT(! is_equal( qNaN, -0.0 ));
    EXPECT(! is_equal( 0.0 ,  qNaN));
    EXPECT(! is_equal( qNaN,  dInf));
    EXPECT(! is_equal( dInf,  qNaN));
    EXPECT(! is_equal( qNaN,  dMax));
    EXPECT(! is_equal( dMax,  qNaN));
    EXPECT(! is_equal( qNaN, -dMax));
    EXPECT(! is_equal(-dMax,  qNaN));
    EXPECT(! is_equal( qNaN,  dMin));
    EXPECT(! is_equal( dMin,  qNaN));
    EXPECT(! is_equal( qNaN, -dMin));
    EXPECT(! is_equal(-dMin,  qNaN));

    EXPECT(! is_equal( sNaN,  sNaN));
    EXPECT(! is_equal( sNaN,  0.0 ));
    EXPECT(! is_equal(-0.0 ,  sNaN));
    EXPECT(! is_equal( sNaN, -0.0 ));
    EXPECT(! is_equal( 0.0 ,  sNaN));
    EXPECT(! is_equal( sNaN,  dInf));
    EXPECT(! is_equal( dInf,  sNaN));
    EXPECT(! is_equal( sNaN,  dMax));
    EXPECT(! is_equal( dMax,  sNaN));
    EXPECT(! is_equal( sNaN, -dMax));
    EXPECT(! is_equal(-dMax,  sNaN));
    EXPECT(! is_equal( sNaN,  dMin));
    EXPECT(! is_equal( dMin,  sNaN));
    EXPECT(! is_equal( sNaN, -dMin));
    EXPECT(! is_equal(-dMin,  sNaN));
}

CASE( "test_comparisons_opposite_side_of_zero" )
{
   Log::info() << "test_comparisons_opposite_side_of_zero" << std::endl;

   EXPECT(! is_equal( 1.0000001, -1.0      ));
   EXPECT(! is_equal(-1.0,        1.0000001));
   EXPECT(! is_equal(-1.0000001,  1.0      ));
   EXPECT(! is_equal( 1.0,       -1.0000001));

   EXPECT(  is_equal(   10.0 * dMin,    10.0 * -dMin));
   EXPECT(  is_equal(10000   * dMin, 10000   * -dMin));
}

CASE( "test_comparisons_very_close_to_zero" )
{
   Log::info() << "test_comparisons_very_close_to_zero" << std::endl;

   EXPECT(  is_equal( dMin, -dMin, dEps));
   EXPECT(  is_equal(-dMin,  dMin, dEps));
   EXPECT(  is_equal( dMin,  0   , dEps));
   EXPECT(  is_equal( 0,     dMin, dEps));
   EXPECT(  is_equal(-dMin,  0   , dEps));
   EXPECT(  is_equal( 0,    -dMin, dEps));


   EXPECT(  is_equal( 0.000000001, -dMin       ));
   EXPECT(  is_equal( 0.000000001,  dMin       ));
   EXPECT(  is_equal( dMin,         0.000000001));
   EXPECT(  is_equal(-dMin,         0.000000001));


   EXPECT(! is_equal( 0.000000001, -dMin,        1e-10));
   EXPECT(! is_equal( 0.000000001,  dMin,        1e-10));
   EXPECT(! is_equal( dMin,         0.000000001, 1e-10));
   EXPECT(! is_equal(-dMin,         0.000000001, 1e-10));
}

CASE( "test_comparisons_with_denormal_numbers" )
{
   Log::info() << "test_comparisons_with_denormal_numbers" << std::endl;

   EXPECT(  is_equal( sMin, -sMin, dEps));
   EXPECT(  is_equal(-sMin,  sMin, dEps));
   EXPECT(  is_equal( sMin,  0   , dEps));
   EXPECT(  is_equal( 0,     sMin, dEps));
   EXPECT(  is_equal(-sMin,  0   , dEps));
   EXPECT(  is_equal( 0,    -sMin, dEps));

   const float lMin = dMin - sMin;  // largest denormal number
   EXPECT(  is_equal( lMin, -lMin, dEps));
   EXPECT(  is_equal(-lMin,  lMin, dEps));
   EXPECT(  is_equal( lMin,  0   , dEps));
   EXPECT(  is_equal( 0,     lMin, dEps));
   EXPECT(  is_equal(-lMin,  0   , dEps));
   EXPECT(  is_equal( 0,    -lMin, dEps));
}

CASE( "test_comparisons_ulps" )
{
   Log::info() << "test_comparisons_ulps" << std::endl;

   EXPECT(  is_equal( dMin, -dMin, 0, 2));
   EXPECT(  is_equal(-dMin,  dMin, 0, 2));
   EXPECT(  is_equal( dMin,  0   , 0, 1));
   EXPECT(  is_equal( 0,     dMin, 0, 1));
   EXPECT(  is_equal(-dMin,  0   , 0, 1));
   EXPECT(  is_equal( 0,    -dMin, 0, 1));

   EXPECT(! is_equal( dMin, -dMin, 0, 1));
   EXPECT(! is_equal(-dMin,  dMin, 0, 1));
   EXPECT(! is_equal( dMin,  0   , 0, 0));
   EXPECT(! is_equal( 0,     dMin, 0, 0));
   EXPECT(! is_equal(-dMin,  0   , 0, 0));
   EXPECT(! is_equal( 0,    -dMin, 0, 0));
}

//-----------------------------------------------------------------------------

} // namespace test
} // namespace eckit

//-----------------------------------------------------------------------------

int main(int argc,char **argv)
{
    return run_tests ( argc, argv );
}

