From 56ceab67f5e05bf58068527065670411ce96d434 Mon Sep 17 00:00:00 2001 From: dhruv13j Date: Thu, 6 Mar 2014 01:04:58 +0530 Subject: [PATCH] fixes issue #1888 --- src/shogun/base/Parameter.cpp | 59 ++++--- src/shogun/base/Parameter.h | 9 +- src/shogun/base/SGObject.cpp | 4 +- src/shogun/base/SGObject.h | 3 +- src/shogun/mathematics/Math.cpp | 18 ++- src/shogun/mathematics/Math.h | 79 ++++++++- tests/integration/python_modular/tester.py | 4 +- tests/unit/base/main_unittest.cc | 3 +- .../io/SerializationJSON_unittest.cc.jinja2 | 4 +- tests/unit/mathematics/Math_unittest.cc | 152 ++++++++++++++++++ 10 files changed, 284 insertions(+), 51 deletions(-) diff --git a/src/shogun/base/Parameter.cpp b/src/shogun/base/Parameter.cpp index cd9211460ad..601e86314cd 100644 --- a/src/shogun/base/Parameter.cpp +++ b/src/shogun/base/Parameter.cpp @@ -3133,7 +3133,7 @@ void TParameter::copy_data(const TParameter* source) SG_SDEBUG("leaving TParameter::copy_data for %s\n", m_name) } -bool TParameter::equals(TParameter* other, float64_t accuracy) +bool TParameter::equals(TParameter* other, float64_t accuracy, bool tolerant) { SG_SDEBUG("entering TParameter::equals()\n"); @@ -3181,7 +3181,7 @@ bool TParameter::equals(TParameter* other, float64_t accuracy) if (!TParameter::compare_stype(m_datatype.m_stype, m_datatype.m_ptype, m_parameter, other->m_parameter, - accuracy)) + accuracy, tolerant)) { SG_SDEBUG("leaving TParameter::equals(): scalar data differs\n"); return false; @@ -3204,7 +3204,8 @@ bool TParameter::equals(TParameter* other, float64_t accuracy) void* pointer_b=&((*(char**)other->m_parameter)[x]); if (!TParameter::compare_stype(m_datatype.m_stype, - m_datatype.m_ptype, pointer_a, pointer_b, accuracy)) + m_datatype.m_ptype, pointer_a, pointer_b, + accuracy, tolerant)) { SG_SDEBUG("leaving TParameter::equals(): vector element " "differs\n"); @@ -3237,14 +3238,15 @@ bool TParameter::equals(TParameter* other, float64_t accuracy) for (index_t i=0; im_parameter)[x]); if (!TParameter::compare_stype(m_datatype.m_stype, - m_datatype.m_ptype, pointer_a, pointer_b, accuracy)) + m_datatype.m_ptype, pointer_a, pointer_b, + accuracy, tolerant)) { SG_SDEBUG("leaving TParameter::equals(): vector element " "differs\n"); @@ -3277,10 +3279,10 @@ bool TParameter::equals(TParameter* other, float64_t accuracy) } bool TParameter::compare_ptype(EPrimitiveType ptype, void* data1, void* data2, - floatmax_t accuracy) + float64_t accuracy, bool tolerant) { SG_SDEBUG("entering TParameter::compare_ptype()\n"); - + if ((data1 && !data2) || (!data1 && data2)) { SG_SINFO("leaving TParameter::compare_ptype(): data1 is at %p while " @@ -3431,38 +3433,33 @@ bool TParameter::compare_ptype(EPrimitiveType ptype, void* data1, void* data2, { float32_t casted1=*((float32_t*)data1); float32_t casted2=*((float32_t*)data2); - - if (CMath::abs(casted1-casted2)>accuracy) - { - SG_SINFO("leaving TParameter::compare_ptype(): PT_FLOAT32: " - "data1=%f, data2=%f\n", casted1, casted2); - return false; - } + + SG_SINFO("leaving TParameter::compare_ptype(): PT_FLOAT32: " + "data1=%f, data2=%f\n", casted1, casted2); + + return CMath::fequals(casted1, casted2, accuracy, tolerant); break; } case PT_FLOAT64: { float64_t casted1=*((float64_t*)data1); float64_t casted2=*((float64_t*)data2); + + SG_SINFO("leaving TParameter::compare_ptype(): PT_FLOAT64: " + "data1=%f, data2=%f\n", casted1, casted2); - if (CMath::abs(casted1-casted2)>accuracy) - { - SG_SINFO("leaving TParameter::compare_ptype(): PT_FLOAT64: " - "data1=%f, data2=%f\n", casted1, casted2); - return false; - } + return CMath::fequals(casted1, casted2, accuracy, tolerant); break; } case PT_FLOATMAX: { floatmax_t casted1=*((floatmax_t*)data1); floatmax_t casted2=*((floatmax_t*)data2); - if (CMath::abs(casted1-casted2)>accuracy) - { - SG_SINFO("leaving TParameter::compare_ptype(): PT_FLOATMAX: " - "data1=%f, data2=%f\n", casted1, casted2); - return false; - } + + SG_SINFO("leaving TParameter::compare_ptype(): PT_FLOATMAX: " + "data1=%f, data2=%f\n", casted1, casted2); + + return CMath::fequals(casted1, casted2, accuracy, tolerant); break; } case PT_COMPLEX128: @@ -3671,10 +3668,10 @@ bool TParameter::copy_ptype(EPrimitiveType ptype, void* source, void* target) } bool TParameter::compare_stype(EStructType stype, EPrimitiveType ptype, - void* data1, void* data2, floatmax_t accuracy) + void* data1, void* data2, float64_t accuracy, bool tolerant) { SG_SDEBUG("entering TParameter::compare_stype()\n"); - + size_t size_ptype=TSGDataType::sizeof_ptype(ptype); /* Avoid comparing NULL */ @@ -3697,7 +3694,7 @@ bool TParameter::compare_stype(EStructType stype, EPrimitiveType ptype, case ST_NONE: { SG_SDEBUG("ST_NONE\n"); - return TParameter::compare_ptype(ptype, data1, data2, accuracy); + return TParameter::compare_ptype(ptype, data1, data2, accuracy, tolerant); break; } case ST_SPARSE: @@ -3736,7 +3733,7 @@ bool TParameter::compare_stype(EStructType stype, EPrimitiveType ptype, void* pointer2=&(cur2->entry)-char_offset+ptype_offset; if (!TParameter::compare_ptype(ptype, pointer1, - pointer2, accuracy)) + pointer2, accuracy, tolerant)) { SG_SINFO("leaving TParameter::compare_stype(): Data of" " sparse vector element is different\n"); @@ -3778,7 +3775,7 @@ bool TParameter::compare_stype(EStructType stype, EPrimitiveType ptype, void* pointer2=str_ptr2->string+i*size_ptype; if (!TParameter::compare_ptype(ptype, pointer1, - pointer2, accuracy)) + pointer2, accuracy, tolerant)) { SG_SINFO("leaving TParameter::compare_stype(): Data of" " string element is different\n"); diff --git a/src/shogun/base/Parameter.h b/src/shogun/base/Parameter.h index b8f157a6ad4..fe3afc91d7c 100644 --- a/src/shogun/base/Parameter.h +++ b/src/shogun/base/Parameter.h @@ -82,9 +82,10 @@ struct TParameter * * @param other other instance to compare with * @param accuracy accuracy for numerical comparison + * @param tolerant allows linient check on float equality (within accuracy) * @return true if given parameter instance is equal, false otherwise */ - bool equals(TParameter* other, float64_t accuracy=0.0); + bool equals(TParameter* other, float64_t accuracy=0.0, bool tolerant=false); /** Given two pointers to a scalar element of a given primitive-type, this * method compares the values up to a given accuracy. @@ -96,10 +97,11 @@ struct TParameter * @param data1 pointer 1 * @param data2 pointer 2 * @param accuracy accuracy to compare + * @param tolerant allows linient check on float equality (within accuracy) * @return whether the data was equal */ static bool compare_ptype(EPrimitiveType ptype, void* data1, void* data2, - floatmax_t accuracy=0.0); + float64_t accuracy=0.0, bool tolerant=false); /** Given two pointers to a string element of a given primitive-type, this * method compares the values up to a given accuracy. @@ -112,10 +114,11 @@ struct TParameter * @param data1 pointer 1 * @param data2 pointer 2 * @param accuracy accuracy to compare + * @param tolerant allows linient check on float equality (within accuracy) * @return whether the data was equal */ static bool compare_stype(EStructType stype, EPrimitiveType ptype, - void* data1, void* data2, floatmax_t accuracy=0.0); + void* data1, void* data2, float64_t accuracy=0.0, bool tolerant=false); /** copy primitive type from source to target * diff --git a/src/shogun/base/SGObject.cpp b/src/shogun/base/SGObject.cpp index 34f7874a004..88167c63be6 100644 --- a/src/shogun/base/SGObject.cpp +++ b/src/shogun/base/SGObject.cpp @@ -1192,7 +1192,7 @@ void CSGObject::build_gradient_parameter_dictionary(CMapequals(other_param, accuracy)) + if (!this_param->equals(other_param, accuracy, tolerant)) { SG_INFO("leaving %s::equals(): parameters at position %d with name" " \"%s\" differs from other object parameter with name " diff --git a/src/shogun/base/SGObject.h b/src/shogun/base/SGObject.h index 13950cdd06c..2e882b95613 100644 --- a/src/shogun/base/SGObject.h +++ b/src/shogun/base/SGObject.h @@ -413,9 +413,10 @@ class CSGObject : public SGRefObject * * @param other object to compare with * @param accuracy accuracy to use for comparison (optional) + * @param tolerant allows linient check on float equality (within accuracy) * @return true if all parameters were equal, false if not */ - virtual bool equals(CSGObject* other, float64_t accuracy=0.0); + virtual bool equals(CSGObject* other, float64_t accuracy=0.0, bool tolerant=false); /** Creates a clone of the current object. This is done via recursively * traversing all parameters, which corresponds to a deep copy. diff --git a/src/shogun/mathematics/Math.cpp b/src/shogun/mathematics/Math.cpp index 33758456c10..f5c664e6b76 100644 --- a/src/shogun/mathematics/Math.cpp +++ b/src/shogun/mathematics/Math.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #ifndef NAN #include @@ -44,12 +45,18 @@ int32_t CMath::LOGRANGE = 0; // range for logtable: log(1+exp(x)) -2 const float64_t CMath::NOT_A_NUMBER = NAN; const float64_t CMath::INFTY = INFINITY; // infinity -const float64_t CMath::ALMOST_INFTY = +1e+20; //a large number -const float64_t CMath::ALMOST_NEG_INFTY = -1000; +const float64_t CMath::ALMOST_INFTY = +1e+300; //a large number +const float64_t CMath::ALMOST_NEG_INFTY = -1e+300; const float64_t CMath::PI=M_PI; -const float64_t CMath::MACHINE_EPSILON=5E-16; -const float64_t CMath::MAX_REAL_NUMBER=1E300; -const float64_t CMath::MIN_REAL_NUMBER=1E-300; +const float64_t CMath::MACHINE_EPSILON=DBL_EPSILON; +const float64_t CMath::MAX_REAL_NUMBER=DBL_MAX; +const float64_t CMath::MIN_REAL_NUMBER=DBL_MIN; +const float32_t CMath::F_MAX_VAL32=FLT_MAX; +const float32_t CMath::F_MIN_NORM_VAL32=FLT_MIN; +const float64_t CMath::F_MAX_VAL64=DBL_MAX; +const float64_t CMath::F_MIN_NORM_VAL64=DBL_MIN; +const float32_t CMath::F_MIN_VAL32=(FLT_MIN * FLT_EPSILON); +const float64_t CMath::F_MIN_VAL64=(DBL_MIN * DBL_EPSILON); #ifdef USE_LOGCACHE float64_t* CMath::logtable = NULL; @@ -207,7 +214,6 @@ void CMath::linspace(float64_t* output, float64_t start, float64_t end, int32_t output[n-1] = end; } - int CMath::is_nan(double f) { #ifndef HAVE_STD_ISNAN diff --git a/src/shogun/mathematics/Math.h b/src/shogun/mathematics/Math.h index 835cd64ab4d..d3646f60ec7 100644 --- a/src/shogun/mathematics/Math.h +++ b/src/shogun/mathematics/Math.h @@ -146,7 +146,7 @@ class CMath : public CSGObject /**@name min/max/abs functions. */ //@{ - + ///return the minimum of two integers // template @@ -187,7 +187,7 @@ class CMath : public CSGObject else return -a; } - + ///return the absolute value of a complex number static inline float64_t abs(complex128_t a) { @@ -199,7 +199,70 @@ class CMath : public CSGObject /**@name misc functions */ //@{ - + + /** Compares the value of two floats based on eps only + * @param a first value to compare + * @param b second value to compare + * @param eps threshold for values to be equal/different + * @return true if values are equal within eps accuracy, false if not. + */ + template + static inline bool fequals_abs(const T& a, const T& b, + const float64_t eps) + { + const T diff = CMath::abs((a-b)); + return (diff < eps); + } + + /** Compares the value of two floats (handles special cases, such as NaN, Inf etc.) + * Note: returns true if a == b == NAN + * Implementation inspired by http://floating-point-gui.de/errors/comparison/ + * @param a first value to compare + * @param b second value to compare + * @param eps threshold for values to be equal/different + * @param tolerant allows linient check on float equality (within accuracy) + * @return true if values are equal within eps accuracy, false if not. + */ + template + static inline bool fequals(const T& a, const T& b, + const float64_t eps, bool tolerant=false) + { + const T absA = CMath::abs(a); + const T absB = CMath::abs(b); + const T diff = CMath::abs((a-b)); + T comp; + + // Handle this separately since NAN is unordered + if (CMath::is_nan((float64_t)a) && CMath::is_nan((float64_t)b)) + return true; + + // Required for JSON Serialization Tests + if (tolerant) + return CMath::fequals_abs(a, b, eps); + + // handles float32_t and float64_t separately + if (sizeof(T) == 4) + comp = CMath::F_MIN_NORM_VAL32; + + else + comp = CMath::F_MIN_NORM_VAL64; + + if (a==b) + return true; + + // both a and b are 0 and relative error is less meaningful + else if ( (a==0) || (b==0) || (diff < comp) ) + return (diff<(eps * comp)); + + // use max(relative error, diff) to handle large eps + else + { + T check = ((diff/(absA + absB)) > diff)? + (diff/(absA + absB)):diff; + return (check < eps); + } + } + static inline float64_t round(float64_t d) { return ::floor(d+0.5); @@ -1342,6 +1405,16 @@ class CMath : public CSGObject /* largest and smallest possible float64_t */ static const float64_t MAX_REAL_NUMBER; static const float64_t MIN_REAL_NUMBER; + + /* Floating point Limits, Normalized */ + static const float32_t F_MAX_VAL32; + static const float32_t F_MIN_NORM_VAL32; + static const float64_t F_MAX_VAL64; + static const float64_t F_MIN_NORM_VAL64; + + /* Floating point limits, Denormalized */ + static const float32_t F_MIN_VAL32; + static const float64_t F_MIN_VAL64; protected: /// range for logtable: log(1+exp(x)) -LOGRANGE <= x <= 0 diff --git a/tests/integration/python_modular/tester.py b/tests/integration/python_modular/tester.py index 6fa0d6c0041..0ea63b0ca26 100755 --- a/tests/integration/python_modular/tester.py +++ b/tests/integration/python_modular/tester.py @@ -59,7 +59,7 @@ def compare(a, b, tolerance, sgtolerance): if pickle.dumps(a) == pickle.dumps(b): result = True else: - result = a.equals(b, sgtolerance) + result = a.equals(b, sgtolerance, True) # print debug output in case of failure if not result: @@ -68,7 +68,7 @@ def compare(a, b, tolerance, sgtolerance): print("Equals failed with debug output") old_loglevel=a.io.get_loglevel() a.io.set_loglevel(modshogun.MSG_INFO) - a.equals(b, sgtolerance) + a.equals(b, sgtolerance, True) a.io.set_loglevel(old_loglevel) return result diff --git a/tests/unit/base/main_unittest.cc b/tests/unit/base/main_unittest.cc index dc78473796b..ae84c1f01c4 100644 --- a/tests/unit/base/main_unittest.cc +++ b/tests/unit/base/main_unittest.cc @@ -2,6 +2,7 @@ #include #include #include +#include using namespace shogun; using ::testing::Test; @@ -66,9 +67,9 @@ int main(int argc, char** argv) } init_shogun_with_defaults(); + //sg_io->set_loglevel(MSG_DEBUG); int ret = RUN_ALL_TESTS(); exit_shogun(); return ret; } - diff --git a/tests/unit/io/SerializationJSON_unittest.cc.jinja2 b/tests/unit/io/SerializationJSON_unittest.cc.jinja2 index f6e74670d4e..49943f6c5d7 100644 --- a/tests/unit/io/SerializationJSON_unittest.cc.jinja2 +++ b/tests/unit/io/SerializationJSON_unittest.cc.jinja2 @@ -47,7 +47,7 @@ TEST(SerializationJSON, {{class}}) // check whether they are equal, up to accuracy since json is lossy float64_t accuracy=1e-6; - ASSERT(object->equals(deserializedObject, accuracy)); + ASSERT(object->equals(deserializedObject, accuracy, true)); SG_UNREF(object) SG_UNREF(deserializedObject); @@ -89,7 +89,7 @@ TEST(SerializationJSON,{{class}}_{{type}}) // check whether they are equal, up to accuracy since json is lossy float64_t accuracy=1e-6; - ASSERT(object->equals(deserializedObject, accuracy)); + ASSERT(object->equals(deserializedObject, accuracy, true)); SG_UNREF(object) SG_UNREF(deserializedObject); diff --git a/tests/unit/mathematics/Math_unittest.cc b/tests/unit/mathematics/Math_unittest.cc index 2e9d1d4f794..6e0ffa44def 100644 --- a/tests/unit/mathematics/Math_unittest.cc +++ b/tests/unit/mathematics/Math_unittest.cc @@ -212,3 +212,155 @@ TEST(CMath, strtolongdouble) EXPECT_TRUE(CMath::strtold("1.234567890123", &long_double_result)); EXPECT_DOUBLE_EQ(1.234567890123, long_double_result); } + +TEST(CMath, fequals_regular_large_numbers) +{ + float64_t eps = 0.00001; + + EXPECT_TRUE(CMath::fequals(1000000.0, 1000000.0, eps)); + EXPECT_TRUE(CMath::fequals(1000000.0, 1000000.0, eps)); + EXPECT_FALSE(CMath::fequals(10001.0, 10000.0, eps)); + EXPECT_FALSE(CMath::fequals(10000.0, 10001.0, eps)); +} + +TEST(CMath, fequals_negative_large_numbers) +{ + float64_t eps = 0.00001; + + EXPECT_TRUE(CMath::fequals(-100000.0, -100000.0, eps)); + EXPECT_TRUE(CMath::fequals(-1000001.0, -1000001.0, eps)); + EXPECT_FALSE(CMath::fequals(1000001.0, 1000000.0, eps)); + EXPECT_FALSE(CMath::fequals(1000000.0, 1000001.0, eps)); +} + +TEST(CMath, fequals_numbers_around_1) +{ + float64_t eps = 0.00001; + + EXPECT_TRUE(CMath::fequals(1.0000001, 1.0000002, eps)); + EXPECT_TRUE(CMath::fequals(1.0000002, 1.0000001, eps)); + EXPECT_FALSE(CMath::fequals(1.0002, 1.0001, eps)); + EXPECT_FALSE(CMath::fequals(1.0002, 1.0001, eps)); +} + +TEST(CMath, fequals_numbers_around_minus_1) +{ + float64_t eps = 0.00001; + + EXPECT_TRUE(CMath::fequals(-1.0000001, -1.0000002, eps)); + EXPECT_TRUE(CMath::fequals(-1.0000002, -1.0000001, eps)); + EXPECT_FALSE(CMath::fequals(-1.0002, -1.0001, eps)); + EXPECT_FALSE(CMath::fequals(-1.0002, -1.0001, eps)); +} + +TEST(CMath, fequals_small_pos_numbers) +{ + float64_t eps = 0.00001; + + EXPECT_TRUE(CMath::fequals(0.000000001000001, 0.000000001000002, eps)); + EXPECT_TRUE(CMath::fequals(0.000000001000002, 0.000000001000001, eps)); + EXPECT_FALSE(CMath::fequals(0.000000000001002, 0.000000000001001, eps)); + EXPECT_FALSE(CMath::fequals(0.000000000001001, 0.000000000001002, eps)); +} + +TEST(CMath, fequals_small_neg_numbers) +{ + float64_t eps = 0.00001; + + EXPECT_TRUE(CMath::fequals(-0.000000001000001, -0.000000001000002, eps)); + EXPECT_TRUE(CMath::fequals(-0.000000001000002, -0.000000001000001, eps)); + EXPECT_FALSE(CMath::fequals(-0.000000000001002, -0.000000000001001, eps)); + EXPECT_FALSE(CMath::fequals(-0.000000000001001, -0.000000000001002, eps)); +} + +TEST(CMath, fequals_zero) +{ + float64_t eps = 0.00001; + + EXPECT_TRUE(CMath::fequals(0.0, 0.0, eps)); + EXPECT_TRUE(CMath::fequals(0.0, -0.0, eps)); + EXPECT_TRUE(CMath::fequals(-0.0, -0.0, eps)); + EXPECT_FALSE(CMath::fequals(0.00000001, 0.0, eps)); + EXPECT_FALSE(CMath::fequals(0.0, 0.00000001, eps)); + EXPECT_FALSE(CMath::fequals(-0.00000001, 0.0, eps)); + EXPECT_FALSE(CMath::fequals(0.0, -0.00000001, eps)); + + EXPECT_TRUE(CMath::fequals(0.0, 1e-40, 0.01)); + EXPECT_TRUE(CMath::fequals(1e-40, 0.0, 0.01)); + EXPECT_TRUE(CMath::fequals(0.0, 1e-40, 0.01)); + EXPECT_TRUE(CMath::fequals(1e-40, 0.0, 0.01)); + + EXPECT_FALSE(CMath::fequals(0.0, 1e-40, 0.01)); + EXPECT_FALSE(CMath::fequals(1e-40, 0.0, 0.01)); + EXPECT_FALSE(CMath::fequals(1e-40, 0.0, 0.000001)); + EXPECT_FALSE(CMath::fequals(0.0, 1e-40, 0.000001)); + + EXPECT_FALSE(CMath::fequals(0.0, -1e-40, 0.1)); + EXPECT_FALSE(CMath::fequals(-1e-40, 0.0, 0.1)); + EXPECT_FALSE(CMath::fequals(-1e-40, 0.0, 0.00000001)); + EXPECT_FALSE(CMath::fequals(0.0, -1e-40, 0.00000001)); +} + +TEST(CMath, fequals_inf) +{ + float64_t eps = 0.00001; + + EXPECT_TRUE(CMath::fequals(CMath::INFTY, CMath::INFTY, eps)); + EXPECT_TRUE(CMath::fequals(-CMath::INFTY, -CMath::INFTY, eps)); + EXPECT_FALSE(CMath::fequals(-CMath::INFTY, CMath::INFTY, eps)); + EXPECT_FALSE(CMath::fequals(CMath::INFTY, CMath::F_MAX_VAL64, eps)); + EXPECT_FALSE(CMath::fequals(-CMath::INFTY, -CMath::F_MAX_VAL64, eps)); +} + +TEST(CMath, fequals_nan) +{ + float64_t eps = 0.00001; + + EXPECT_TRUE(CMath::fequals(CMath::NOT_A_NUMBER, CMath::NOT_A_NUMBER, eps)); + EXPECT_FALSE(CMath::fequals(CMath::NOT_A_NUMBER, 0.0f, eps)); + EXPECT_FALSE(CMath::fequals(-0.0f, CMath::NOT_A_NUMBER, eps)); + EXPECT_FALSE(CMath::fequals(CMath::NOT_A_NUMBER, -0.0f, eps)); + EXPECT_FALSE(CMath::fequals(0.0f, CMath::NOT_A_NUMBER, eps)); + EXPECT_FALSE(CMath::fequals(CMath::NOT_A_NUMBER, CMath::INFTY, eps)); + EXPECT_FALSE(CMath::fequals(CMath::INFTY, CMath::NOT_A_NUMBER, eps)); + EXPECT_FALSE(CMath::fequals(CMath::NOT_A_NUMBER, -CMath::INFTY, eps)); + EXPECT_FALSE(CMath::fequals(-CMath::INFTY, CMath::NOT_A_NUMBER, eps)); + EXPECT_FALSE(CMath::fequals(CMath::NOT_A_NUMBER, CMath::F_MAX_VAL64, eps)); + EXPECT_FALSE(CMath::fequals(CMath::F_MAX_VAL64, CMath::NOT_A_NUMBER, eps)); + EXPECT_FALSE(CMath::fequals(CMath::NOT_A_NUMBER, -CMath::F_MAX_VAL64, eps)); + EXPECT_FALSE(CMath::fequals(-CMath::F_MAX_VAL64, CMath::NOT_A_NUMBER, eps)); + EXPECT_FALSE(CMath::fequals(CMath::NOT_A_NUMBER, CMath::F_MIN_VAL64, eps)); + EXPECT_FALSE(CMath::fequals(CMath::F_MIN_VAL64, CMath::NOT_A_NUMBER, eps)); + EXPECT_FALSE(CMath::fequals(CMath::NOT_A_NUMBER, -CMath::F_MIN_VAL64, eps)); + EXPECT_FALSE(CMath::fequals(-CMath::F_MIN_VAL64, CMath::NOT_A_NUMBER, eps)); +} + +TEST(CMath, fequals_opposite_sign) +{ + float64_t eps = 0.00001; + + EXPECT_FALSE(CMath::fequals(1.000000001f, -1.0f, eps)); + EXPECT_FALSE(CMath::fequals(-1.0f, 1.000000001f, eps)); + EXPECT_FALSE(CMath::fequals(-1.000000001f, 1.0f, eps)); + EXPECT_FALSE(CMath::fequals(1.0f, -1.000000001f, eps)); + EXPECT_TRUE(CMath::fequals(10 * CMath::F_MIN_VAL64, 10 * -CMath::F_MIN_VAL64, eps)); + EXPECT_FALSE(CMath::fequals(10000 * CMath::F_MIN_VAL32, 10000 * -CMath::F_MIN_VAL32, eps)); + EXPECT_TRUE(CMath::fequals(10000 * CMath::F_MIN_VAL64, 10000 * -CMath::F_MIN_VAL64, eps)); +} + +TEST(CMath, fequals_close_to_zero) +{ + float64_t eps = 0.00001; + + EXPECT_TRUE(CMath::fequals(CMath::F_MIN_VAL64, -CMath::F_MIN_VAL64, eps)); + EXPECT_TRUE(CMath::fequals(-CMath::F_MIN_VAL64, CMath::F_MIN_VAL64, eps)); + EXPECT_TRUE(CMath::fequals(CMath::F_MIN_VAL64, 0, eps)); + EXPECT_TRUE(CMath::fequals(0, CMath::F_MIN_VAL64, eps)); + EXPECT_TRUE(CMath::fequals(-CMath::F_MIN_VAL64, 0, eps)); + EXPECT_TRUE(CMath::fequals(0, -CMath::F_MIN_VAL64, eps)); + + EXPECT_FALSE(CMath::fequals(0.000000001f, -CMath::F_MIN_VAL64, eps)); + EXPECT_FALSE(CMath::fequals(0.000000001f, CMath::F_MIN_VAL64, eps)); + EXPECT_FALSE(CMath::fequals(CMath::F_MIN_VAL64, 0.000000001f, eps)); + EXPECT_FALSE(CMath::fequals(-CMath::F_MIN_VAL64, 0.000000001f, eps)); +}