areClose(), isClose()

This commit is contained in:
Pavel Kirienko 2014-08-28 20:25:27 +04:00
parent f255a725c1
commit 9d806c2be6
5 changed files with 148 additions and 9 deletions

View File

@ -107,13 +107,15 @@ struct UAVCAN_EXPORT ${t.cpp_type_name}
#endif
}
/**
* Comparison operators are based on @ref uavcan::areClose(),
* which allows to safely compare floating point values.
*/
bool operator==(ParameterType rhs) const;
bool operator!=(ParameterType rhs) const { return !operator==(rhs); }
/**
* This comparison is based on @ref uavcan::areClose(), which ensures proper comparison of
* floating point fields at any depth.
*/
bool isClose(ParameterType rhs) const;
static int encode(ParameterType self, ::uavcan::ScalarCodec& codec,
::uavcan::TailArrayOptimizationMode tao_mode = ::uavcan::TailArrayOptEnabled);
@ -182,6 +184,20 @@ UAVCAN_PACKED_END
template <int _tmpl>
bool ${scope_prefix}<_tmpl>::operator==(ParameterType rhs) const
{
% if fields:
return
% for idx,a in enumerate(fields):
${a.name} == rhs.${a.name}${' &&' if (idx + 1) < len(fields) else ';'}
% endfor
% else:
(void)rhs;
return true;
% endif
}
template <int _tmpl>
bool ${scope_prefix}<_tmpl>::isClose(ParameterType rhs) const
{
% if fields:
return

View File

@ -577,11 +577,36 @@ public:
/**
* This operator accepts any container with size() and [].
* Members are compared via @ref areClose().
* Members must be comparable via operator ==.
*/
template <typename R>
typename EnableIf<sizeof(((const R*)(0U))->size()) && sizeof((*((const R*)(0U)))[0]), bool>::Type
operator==(const R& rhs) const
{
if (size() != rhs.size())
{
return false;
}
for (SizeType i = 0; i < size(); i++) // Bitset does not have iterators
{
if (!(Base::at(i) == rhs[i]))
{
return false;
}
}
return true;
}
/**
* This method compares two arrays using @ref areClose(), which ensures proper comparison of
* floating point values, or DSDL data structures which contain floating point fields at any depth.
* Please refer to the documentation of @ref areClose() to learn more about how it works and how to
* define custom fuzzy comparison behavior.
* Any container with size() and [] is acceptable.
*/
template <typename R>
typename EnableIf<sizeof(((const R*)(0U))->size()) && sizeof((*((const R*)(0U)))[0]), bool>::Type
isClose(const R& rhs) const
{
if (size() != rhs.size())
{

View File

@ -23,6 +23,7 @@ inline bool areFloatsExactlyEqual(const T& left, const T& right)
* This function performs fuzzy comparison of two floating point numbers.
* Type of T can be either float, double or long double.
* For details refer to http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
* See also: @ref UAVCAN_FLOAT_COMPARISON_EPSILON_MULT.
*/
template <typename T>
UAVCAN_EXPORT
@ -55,15 +56,102 @@ inline bool areFloatsClose(T a, T b, const T absolute_epsilon, const T relative_
}
/**
* Generic comparison function.
* This namespace contains implementation details for areClose().
* Don't try this at home.
*/
namespace are_close_impl_
{
struct Applicable { char foo[1]; };
struct NotApplicable { long long foo[16]; };
template <typename This, typename Rhs>
struct HasIsCloseMethod
{
template <typename U, typename R, bool (U::*)(const R&) const> struct ConstRef { };
template <typename U, typename R, bool (U::*)(R) const> struct ByValue { };
template <typename U, typename R> static Applicable test(ConstRef<U, R, &U::isClose>*);
template <typename U, typename R> static Applicable test(ByValue<U, R, &U::isClose>*);
template <typename U, typename R> static NotApplicable test(...);
enum { Result = sizeof(test<This, Rhs>(NULL)) };
};
/// First stage: bool L::isClose(R)
template <typename L, typename R>
UAVCAN_EXPORT
inline bool areCloseImplFirst(const L& left, const R& right, IntToType<sizeof(Applicable)>)
{
return left.isClose(right);
}
/// Second stage: bool R::isClose(L)
template <typename L, typename R>
UAVCAN_EXPORT
inline bool areCloseImplSecond(const L& left, const R& right, IntToType<sizeof(Applicable)>)
{
return right.isClose(left);
}
/// Second stage: L == R
template <typename L, typename R>
UAVCAN_EXPORT
inline bool areCloseImplSecond(const L& left, const R& right, IntToType<sizeof(NotApplicable)>)
{
return left == right;
}
/// First stage: select either L == R or bool R::isClose(L)
template <typename L, typename R>
UAVCAN_EXPORT
inline bool areCloseImplFirst(const L& left, const R& right, IntToType<sizeof(NotApplicable)>)
{
return are_close_impl_::areCloseImplSecond(left, right,
IntToType<are_close_impl_::HasIsCloseMethod<R, L>::Result>());
}
} // namespace are_close_impl_
/**
* Generic fuzzy comparison function.
*
* This function properly handles floating point comparison, including mixed floating point type comparison,
* e.g. float with long double.
*
* Two objects of types A and B will be fuzzy comparable if either method is defined:
* - bool A::isClose(const B&)
* - bool A::isClose(B)
* - bool B::isClose(const A&)
* - bool B::isClose(A)
* Alternatively, a custom specialization of this function can be defined.
*
* Note that all floating types and their combinations are fuzzy comparable by default:
* - float
* - double
* - long double
*
* If the arguments aren't fuzzy comparable, this function will resort to the plain comparison operator ==.
*
* See also: @ref UAVCAN_FLOAT_COMPARISON_EPSILON_MULT.
*
* Examples:
* areClose(1.0, 1.0F) --> true
* areClose(1.0, 1.0F + std::numeric_limits<float>::epsilon()) --> true
* areClose(1.0, 1.1) --> false
* areClose("123", std::string("123")) --> true (using std::string's operator ==)
* areClose(inf, inf) --> true
* areClose(inf, -inf) --> false
* areClose(nan, nan) --> false
* areClose(123, "123") --> compilation error: operator == is not defined
*/
template <typename L, typename R>
UAVCAN_EXPORT
inline bool areClose(const L& left, const R& right)
{
return left == right;
return are_close_impl_::areCloseImplFirst(left, right,
IntToType<are_close_impl_::HasIsCloseMethod<L, R>::Result>());
}
/*

View File

@ -2,6 +2,10 @@
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#if __GNUC__
# pragma GCC diagnostic ignored "-Wfloat-equal"
#endif
#include <gtest/gtest.h>
#include <uavcan/transport/transfer_buffer.hpp>
#include <limits>
@ -85,10 +89,12 @@ TEST(Dsdl, CloseComparison)
ASSERT_TRUE(first == second);
first.vector[1].vector[1] = std::numeric_limits<double>::epsilon();
ASSERT_TRUE(first == second); // Still equals
ASSERT_TRUE(first.isClose(second)); // Still close
ASSERT_FALSE(first == second); // But not exactly
first.vector[1].vector[1] = std::numeric_limits<float>::epsilon();
ASSERT_FALSE(first == second); // Nope
ASSERT_FALSE(first.isClose(second)); // Nope
ASSERT_FALSE(first == second); // Ditto
}
/*

View File

@ -2,6 +2,10 @@
* Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
*/
#if __GNUC__
# pragma GCC diagnostic ignored "-Wfloat-equal"
#endif
#include <gtest/gtest.h>
#include <uavcan/marshal/types.hpp>
#include <uavcan/transport/transfer_buffer.hpp>