Restrict types of template functions

  • Thread starter Jens Thoms Toerring
  • Start date
J

Jens Thoms Toerring

Hi,

I need a class that behaves in most respects like a normal
double. The only differences are in the comparison operators
(implementing "fuzzy" comparisons instead of exact ones) and
a small number of extra functions as well. As far as I can
see operators only make sense when they apply to other in-
stances of the class and to arithmetic types. So set out with
something like this:

------------8<-----------------------------------------------
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits/is_arithmetic.hpp>

using boost::enable_if_c;
using boost::is_arithmetic;

class FuzzyDouble {
private:
double m_value;
static double s_epsilon;

public:
FuzzyDouble( double value = 0.0 ) : m_value( value ) { }

bool operator == ( FuzzyDouble const & rhs ) const
{
return m_value > rhs.m_value - s_epsilon
&& m_value < rhs.m_value + s_epsilon;
}

template< typename T > bool
operator == ( typename enable_if_c< is_arithmetic< T >::value, T >::type
const & rhs ) const
{
return *this == FuzzyDouble( rhs );
}

// Further comparison operators left out for brevitities sake...

FuzzyDouble operator + ( FuzzyDouble const & rhs ) const
{
return FuzzyDouble( m_value ) += rhs;
}

template< typename T> FuzzyDouble
operator + ( typename enable_if_c< is_arithmetic< T >::value, T >::type
const & rhs ) const
{
return FuzzyDouble( m_value ) += rhs;
}

FuzzyDouble & operator += ( FuzzyDouble const & rhs )
{
m_value += rhs.m_value;
return *this;
}

template< typename T > FuzzyDouble &
operator += ( typename enable_if_c< is_arithmetic< T >::value, T >::type
const & rhs ) const
{
m_value += rhs.value;
return *this;
}

// Further arithmetic operators left out for brevities sake...

double value( ) const { return m_value; }
};

double FuzzyDouble::s_epsilon = 1.0e-8;
------------8<-----------------------------------------------

Beside this I obviously also need functions like this:

------------8<-----------------------------------------------
template< typename T > bool
operator == ( T const & lhs, FuzzyDouble const & rhs )
{
return rhs == lhs;
}

template< typename T > FuzzyDouble
operator + ( T const & lhs, FuzzyDouble const & rhs )
{
return rhs + lhs;
}
------------8<-----------------------------------------------

Now I also would like to restrict the types that can be used for
these functions to arithmetic types (if I don't the compiler
complains at a few places in my program I want to use this for
about ambigious overloads). But while this works nicely for the
member functions I didn't fin any way to do that yet for the non-
member functions. If I for example try as above

template< typename T > bool
operator == ( typename enable_if_c< is_arithmetic< T >::value, T >::type
const & lhs, FuzzyDouble const & rhs )
{
return rhs == lhs;
}

the compiler doesn't complain but never considers them at all.
Thus at the moment it looks as I if would need at least five
functions with identical bodies for the arithmetic types int,
long and their unsigned counterparts as well as for double. This
looks extremely ugly to me (and increases the risk for mistakes
considerably). Does someone has an idea how I can do it correctly?

Another question is of course if the whole idea is completely
stupid. Are there better ways to achieve what I want?

Best regards, Jens
 
L

LR

Jens said:
Now I also would like to restrict the types that can be used for
these functions to arithmetic types (if I don't the compiler
complains at a few places in my program I want to use this for
about ambigious overloads). But while this works nicely for the
member functions I didn't fin any way to do that yet for the non-
member functions. If I for example try as above

template< typename T > bool
operator == ( typename enable_if_c< is_arithmetic< T >::value, T >::type
const & lhs, FuzzyDouble const & rhs )
{
return rhs == lhs;
}

the compiler doesn't complain but never considers them at all.
Thus at the moment it looks as I if would need at least five
functions with identical bodies for the arithmetic types int,
long and their unsigned counterparts as well as for double. This
looks extremely ugly to me (and increases the risk for mistakes
considerably). Does someone has an idea how I can do it correctly?

From a very quick skim of the docs, (Here's a link to an old version
http://www.boost.org/doc/libs/1_31_0/libs/utility/enable_if.html) it
seems that you can't do that.

Perhaps introducing a second function inside the operator would work?

Something like:

namespace HiddenAway {
template<typename T>
bool compareEqual(
const T &lhs,
const FuzzyDouble &rhs,
typename const
enable_if_c<is_arithmetic<T>::value,T>::type *dummy=0
) {
return rhs == lhs;
}
}

template<typename T>
bool operator==(const T &lhs, const FuzzyDouble &rhs) {
return HiddenAway::compareEqual(lhs,rhs);
}

I agree that it's not pretty. Maybe there is a better way, but the docs
suggest that you have to have an argument with the actual type of the
object that you want to pass as an argument or the type can't be resolved.

LR
 
L

LR

LR said:
From a very quick skim of the docs, (Here's a link to an old version
http://www.boost.org/doc/libs/1_31_0/libs/utility/enable_if.html) it
seems that you can't do that.

Perhaps introducing a second function inside the operator would work?

Something like:

namespace HiddenAway {
template<typename T>
bool compareEqual(
const T &lhs,
const FuzzyDouble &rhs,
typename const
enable_if_c<is_arithmetic<T>::value,T>::type *dummy=0
) {
return rhs == lhs;
}
}

template<typename T>
bool operator==(const T &lhs, const FuzzyDouble &rhs) {
return HiddenAway::compareEqual(lhs,rhs);
}

I agree that it's not pretty. Maybe there is a better way, but the docs
suggest that you have to have an argument with the actual type of the
object that you want to pass as an argument or the type can't be resolved.

Replying to my own post here.

I think you can also use a compile time assertion,
template<typename T>
bool operator==(const T &lhs, const FuzzyDouble &rhs) {
BOOST_STATIC_ASSERT(is_arithmetic<T>::value);
return rhs == lhs;
}

LR
 
J

Jens Thoms Toerring

Paavo Helde said:
(e-mail address removed) (Jens Thoms Toerring) wrote in
I need a class that behaves in most respects like a normal
double. The only differences are in the comparison operators
(implementing "fuzzy" comparisons instead of exact ones) and [...]
Another question is of course if the whole idea is completely
stupid. Are there better ways to achieve what I want?
What it is what you want? You have not stated it.
The "fuzzy" double comparison in your example suffers from obvious
drawbacks:
1. The comparison is not transitive.
2. The epsilon used in comparisons is a global constant, which just does
not work for a general library class you seem to be aiming. Maybe it
would work better if it could be made a template parameter of the class
... but it can't as doubles are not allowed as template parameters in C++
03.

Actually, it's not meant at all for a general case but for a rather
special case where I have measured values that have a "granularity"
of the order of 1e-3, so a (fixed) 'epsilon' of 1e-8 would be far
below that but differences as small as that would thus be insigni-
ficant but still far above all rounding errors I expect from com-
putations. Thus in this special case the non-transitivity as well
as the lack of being able to set an arbitrary value for 'epsilon'
hopefully aren't too serious drawbacks.
Such shortcomings have restrained other people from introducing such a
notion of "fuzzy" doubles, including even MS Excel, despite of the hords
of complaints along the lines why my 0.1 != 1.0/10. I think especially
the lack of transitivity is a major show-stopper here.

Agreed, a general "fuzzy" double class would have to be much more
complicated, which is assuming that it would be possible at all;-)

Thanks and best regards, Jens
 
J

Jens Thoms Toerring

Replying to my own post here.
I think you can also use a compile time assertion,
template<typename T>
bool operator==(const T &lhs, const FuzzyDouble &rhs) {
BOOST_STATIC_ASSERT(is_arithmetic<T>::value);
return rhs == lhs;
}

That's one of the things I actually tried but unfortunately it
doesn't seem to keep the compiler from considering the function
as a potential candidate for an overloaded '==' operator which
in turn leads to complaints about "ambigious overloads". It only
seems to catch cases where there are no other candidates but
the use is wrong anyway. And I fear that also your other so-
lution has the same drawback since the compiler would have to
actually look into the function in order to find it's unsuitable
when it makes up its list of candidates for a comarison function.
My guess is that one would need some kind of mechanism that would
keep the function from being instantiated at all (unless suitable)
like it seems to happen when using the

typename enable_if_c<is_arithmetic<T>::value,T>::type

construct in the member functions (at least as far as I can
tell from my "experiments"). But then I think all this can of
worms I opened here goes quite a bit beyond my still rather
limited understanding of C++ ;-)

Thank you and best regards, Jens
 
M

Marc

Jens said:
template< typename T > bool
operator == ( typename enable_if_c< is_arithmetic< T >::value, T >::type
const & rhs ) const
{
return *this == FuzzyDouble( rhs );
}

Don't do that. If you want the compiler to guess what T is, you need to
make it easy for it (T, T const&, Thing<T>, but not Thing<T>::mem). On
the other hand, you can use as much meta-stuff as you want on the return
type (untested):

template< typename T >
typename enable_if_c< is_arithmetic< T >::value, bool >::type
operator == ( T const & rhs ) const
{
return *this == FuzzyDouble( rhs );
}

(I am not commenting on the general approach, just on a technical point)
 
J

Jens Thoms Toerring

Marc said:
Jens Thoms Toerring wrote:
Don't do that. If you want the compiler to guess what T is, you need to
make it easy for it (T, T const&, Thing<T>, but not Thing<T>::mem). On
the other hand, you can use as much meta-stuff as you want on the return
type (untested):
template< typename T >
typename enable_if_c< is_arithmetic< T >::value, bool >::type
operator == ( T const & rhs ) const
{
return *this == FuzzyDouble( rhs );
}

Yes, that's it! What I thought to be working for the member functions
actually wasn't since I misunderstood the way the enable_if stuff is
meant to be used. And, with the correction you suggest (i.e. doing
it on the return type instead of fiddling with the arguments), it's
now working exactly as I hoped for;-)
(I am not commenting on the general approach, just on a technical point)

Feel free to comment also on that - I'm not sure that it's
actually the best way to deal with the situation.

Thank you very much and best regards, Jens
 
J

Juha Nieminen

Jens Thoms Toerring said:
return m_value > rhs.m_value - s_epsilon
&& m_value < rhs.m_value + s_epsilon;

Unrelated to your question per se, but you should be aware that it's
not possible to make a "fuzzy compare" of floating point values which
will work equally well with all possible value ranges.

You are using the naive comparison in the code above, but that
approach has the flaw that the accuracy of the comparison is related
to the magnitude of the values being compared.

For example, let's assume that your epsilon is 1e-10. If m_value and
rhs.m_value are somewhere around 5.0, then you will be comparing the ten
most significant digits of the values. This is usually what you want with
such an epsilon.

However, assume that m_value and rhs.m_value are somewhere around
50000.0 instead. Now with the code above you will be comparing the 14
most significant digits of the values, making the comparison significantly
stricter (in other words, less rounding error is allowed).

Likewise, if the values were somewhere around 0.00005, you will be
comparing only the 5 most significant digits of the values, making the
comparison much more lenient (in other words, more rounding error is
allowed).

This is usually not what you want because the accuracy of floating
point numbers is always related to the amount of significant digits
in the value, not the amount of digits after the decimal (which is
where the term "floating point" kind of comes from; you can think of
it as internally the decimal digit always being at the most significant
digit of the number). It means that if you are making calculations
somewhere around 50000.0, the comparison will be a lot stricter than
if you were making calculations around 0.00005, in which case the
comparison is much more lenient and allows much more rounding error.

If you wanted to always compare the n most significant digits of
the values, it could be done like this:

abs((value1 - value2) / value1) < epsilon

However, there are many caveats with that method as well. Most
significantly it cannot be used as-is because there's the possibility
of a division by zero, which will cause mayhem (and wrong results of
the comparison). Also, it's not commutative ("a<b" and "!(b<a)" might
actually give different results in some pathological cases).

More subtly, this method starts misbehaving when the values get very
close to zero in a situation where the overall range of values is far
from it (in other words, as the values get closer and closer to zero,
the comparison above gets stricter and stricter, even though the
calculations resulting in those values do not become more and more
accurate accordingly).
 
J

Jens Thoms Toerring

Unrelated to your question per se, but you should be aware that it's
not possible to make a "fuzzy compare" of floating point values which
will work equally well with all possible value ranges.

Yes, I'm fully aware of that. I probably shouldn't have renamed
my class from the original name to FuzzyDouble since it results
in wrong ideas what it's meant for. In reality the values are all
from a rather limited range (from a maximum of around 10 and a
minimum in the order of 10^-3). And for the kind of things
these values are going to be used for an absolute (in contrast
to the proppsed relative) epsilon is (IMHO) the correct choice
(all the (relatively) simple calculations done with them should
not introduce rounding errors in the order of epsilon but far
smaller). So I try to make it not more complicated then neces-
sary;-)

If this would be about "real" doubles then, of course, all your
concerns would be very valid. I doubt very much that I would be
able to come up with a suitable class for real "fuzzy" doubles
when generations of more competen programmers didn't;-)

Best regards, Jens
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top