3

I have a template class for graphs that takes a parameter for the weight type (could be unsigned, int or double). Also, for comparing doubles I use inline functions of the following type:

inline bool EpsLess(double x, double y, double epsilon = 1.e-10)
{
    return x < y - epsilon;
}
inline bool EpsEqual(double x, double y, double epsilon = 1.e-10)
{
    return !EpsLess(x,y) && !EpsLess(y,x);
}

Is the comparator in the following class skeleton safe ?

template <typename Weight>
class Graph
{
    struct Edge
    {
        std::string from;
        std::string to;
        Weight      weight;
    };

    struct LargestWeight
    {
        bool operator() (const Edge& e1, const Edge& e2) const
        {
            if (EpsEqual(e1.weight == e2.weight))
                if (e1.from == e2.from)
                    return e1.to < e2.to;
                else
                    return e1.from < e2.from;
            return EpsLess(e2.weight, e1.weight);
        }
    };

    // .. other stuff
};

Can I encounter unforeseen consequences when Weight type is unsigned or int ? Or is there a better way to implement the double comparison ?

Kirill Kobelev
  • 10,252
  • 6
  • 30
  • 51
mr_T
  • 2,571
  • 3
  • 22
  • 39
  • @user2079303: I use the epsilons to make a difference between < (x < y - epsilon) and <= (x < y + epsilon). Is there a better way to implement this difference ? – mr_T Feb 04 '16 at 12:06
  • [Most effective way for float and double comparison](http://stackoverflow.com/q/17333/995714), [How to correctly and standardly compare floats?](http://stackoverflow.com/q/4548004/995714), http://floating-point-gui.de/errors/comparison/ – phuclv Feb 04 '16 at 12:08
  • Are you using this as the comparison operator in a sort operation or container? If so, that's undefined behavior because it isn't a strict weak ordering. – interjay Feb 04 '16 at 12:10
  • @mr_T never mind what I said about, I think I got it wrong. But the other part, that constant epsilon is bad if the inputs are very big is true. – eerorika Feb 04 '16 at 12:13
  • @interjay : how could you solve it then ? – mr_T Feb 04 '16 at 13:28
  • @mr_T By not using such an operator for sorting or sorted containers. Your operator has the problem that `x==y` and `y==z` does not imply `x==z`, which could cause some implementations of containers/sorts to produce incorrect results. – interjay Feb 04 '16 at 15:34

2 Answers2

3

This is exactly what templates are for.

I would suggest that you implement EpsLess() inside a template class that uses just the < and == operators. Something like:

template<typename Type> Compare {

public:

    template<typename Ignore>
    inline bool EpsLess(Type x, Type y, Ignore epsilon = Ignore())
    {
        return x < y;
    }
};

Then specialize it for a double:

template<> Compare<double> {

public:
    inline bool EpsLess(double x, double y, double epsilon = 1.e-10)
    {
        return x < y - epsilon;
    }
};

You would invoke it like this:

if (Compare<Weight>::EpsEqual(e1.weight, e2.weight))

This will avoid a bunch of useless work for non-double cases, and get devolved to just an ordinary < operator.

Your homework assignment, then, is to reimplement EpsEqual() as a template function itself, in terms of the new EpsLess().

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
0

No, you can't trust the integer to double conversion in all cases.

Conversion from integer to double can't always be done without loss of precision. Consequenltly, you can get problems if Weight is an integer type that can hold large values, e.g. a size_t

All 32 bit integers can be converted without problems (aka loss of precision).

Support Ukraine
  • 42,271
  • 4
  • 38
  • 63