2
std::map<String, double> m1,m2;
m1["A"] = 20;
m2["A"] = 20.01;

if (m1 == m2)
    cout << "True";
else
    cout << "False";

The sample code prints False because 20 is not equal to 20.1. However in my application I want to treat these value as equal because of the difference between these values are with in allowable tolerance. So is there any way to provide a custom comparison function for data(not for Key)?

Any help is appreciated.

Edited : Sorry for the mistake in the code. I copied the code which I tried to find the solution for this problem. The keys must be equal for my scenario.

Premkumar U
  • 115
  • 2
  • 12
  • Do you care about whether or not one key is less than another? Or just equality? – Omnifarious Mar 29 '12 at 18:37
  • You can't define such a comparison in a way that gives you a strict weak ordering without making *all* values equal: You want 20 equal to 20.1, which is equal to 20.2, ..., which is equal to 99.9, which is equal to 100.0. The ordering is transitive! – Kerrek SB Mar 29 '12 at 18:59
  • @KerrekSB: Oh, you're right! If you use a data value that way, sort will have conniptions. So yes, a real *less than* relationship is impossible. I need to modify my revised answer than. – Omnifarious Mar 29 '12 at 19:31
  • @KerrekSB: There, I fixed my answer. I'm kind of proud of myself for thinking up `fuzzy_double`. :-) – Omnifarious Mar 29 '12 at 19:44

4 Answers4

3

If all you care about is equality for the whole container, I would recommend the ::std::equal algorithm. Here's how:

const double tolerance = 0.01;
bool equal = m1.size() == m2.size() &&
             ::std::equal(m1.begin(), m1.end(), m2.begin(),
                          [tolerance](const decltype(m1)::value_type &a,
                                      const decltype(m2)::value_type &b) {
    return (a.first == b.first) &&
           (::std::abs(a.second - b.second) < tolerance);
});

If you care about a 'less than' relationship, then ::std::lexicographical_compare is what you want. This requires the C++11 lambda feature to work.

If what you really want are data values that compare equal in a fuzzy way, I present to you a bit of a hack (and something that also requires a couple of C++11 features) fuzzy-double.cpp. I disable ordering comparisons because that would tempt you to stuff these things in ordering containers, and since (2.0 == 2.1) && (2.1 == 2.2), but (2.0 != 2.2), they are not suitable for this purpose.

#include <cmath>
#include <iostream>

template <const double &tolerance>
class fuzzy_double {
 public:
   fuzzy_double(double x) : x_(x) {
      static_assert(tolerance >= 0, "tolerance must be >= 0");
   }

   operator double() const { return x_; }
   const fuzzy_double &operator =(double x) { x_ = x; }

   bool equals(const fuzzy_double &b) const {
      return ::std::abs(x_ - b.x_) <= tolerance;
   }
   // This cannot be used as the basis of a 'less than' comparison operator for
   // the purposes of other algorithms because it's possible for a transitive
   // equality relationship to exit that equates all fuzzy_double's to
   // eachother. There is no strict ordering that makes sense.
   bool fuzzy_less(const fuzzy_double &b) const {
      return (b.x_ - x_) > tolerance;
   }

 private:
   double x_;
};

template <const double &tolerance>
bool operator ==(const fuzzy_double<tolerance> &a,
                 const fuzzy_double<tolerance> &b)
{
   return a.equals(b);
}

template <const double &tolerance>
bool operator !=(const fuzzy_double<tolerance> &a,
                 const fuzzy_double<tolerance> &b)
{
   return !a.equals(b);
}

template <const double &tolerance>
bool operator <(const fuzzy_double<tolerance> &a,
                const fuzzy_double<tolerance> &b)
{
   // tolerance < 0 should be an impossible condition and always be false, but
   // it's dependent on the template parameter and so only evaluated when the
   // template is instantiated.
   static_assert(tolerance < 0, "fuzzy_doubles cannot be ordered.");
   return false;
}

template <const double &tolerance>
bool operator >=(const fuzzy_double<tolerance> &a,
                const fuzzy_double<tolerance> &b)
{
   // tolerance < 0 should be an impossible condition and always be false, but
   // it's dependent on the template parameter and so only evaluated when the
   // template is instantiated.
   static_assert(tolerance < 0, "fuzzy_doubles cannot be ordered.");
   return false;
}

template <const double &tolerance>
bool operator >(const fuzzy_double<tolerance> &a,
                const fuzzy_double<tolerance> &b)
{
   // tolerance < 0 should be an impossible condition and always be false, but
   // it's dependent on the template parameter and so only evaluated when the
   // template is instantiated.
   static_assert(tolerance < 0, "fuzzy_doubles cannot be ordered.");
   return false;
}

template <const double &tolerance>
bool operator <=(const fuzzy_double<tolerance> &a,
                 const fuzzy_double<tolerance> &b)
{
   // tolerance < 0 should be an impossible condition and always be false, but
   // it's dependent on the template parameter and so only evaluated when the
   // template is instantiated.
   static_assert(tolerance < 0, "fuzzy_doubles cannot be ordered.");
   return false;
}

extern constexpr double ten_e_minus_2 = 0.01;

int main()
{
   fuzzy_double<ten_e_minus_2> a(3);
   fuzzy_double<ten_e_minus_2> b(3.009);
   fuzzy_double<ten_e_minus_2> c(2.991);
   fuzzy_double<ten_e_minus_2> d(3.011);
   fuzzy_double<ten_e_minus_2> e(2.989);

   using ::std::cout;

   cout << "a == a: " << (a == a) << '\n';
   cout << "a == b: " << (a == b) << '\n';
   cout << "a == c: " << (a == c) << '\n';
   cout << "a == d: " << (a == d) << '\n';
   cout << "a == e: " << (a == e) << '\n';
   return 0;
}

C++11 does not allow a double, not even const one, to be a template parameter. It does, OTOH, allow pointers and references to objects with external linkage to be template parameters. So if you declare your tolerance as an extern constexpr double you can than use the named tolerance as a template parameter.

gmas80
  • 1,218
  • 1
  • 14
  • 44
Omnifarious
  • 54,333
  • 19
  • 131
  • 194
1

You are comparing two entirely separate maps, with different keys and values inside. I am pretty sure this is not your intention.

You can make maps with custom comparison operations for the key, to answer what I think you're asking, but making one with variable tolerance is probably not going to work well. STL has strict requirements about what less-than and equals mean, and how they must behave; if you violate the rules, your map will just fail randomly. So I would not use a map for this. And in your case, you are interested in the values, not the keys, in which case STL simply does not care (it does not look at your values at all).

StilesCrisis
  • 15,972
  • 4
  • 39
  • 62
1

You can use lexicographical_compare with a custom comparator of pairs that ignores small differences in the values, like this:

bool mycomp (pair<string,double> lhs, pair<string,double> rhs) {
    if (lhs.first < rhs.first) {
        return true;
    }
    if (lhs.first > rhs.first) {
        return false;
    }
    // Here is the "check tolerance" part:
    if (abs(lhs.second-rhs.second) < 0.05) {
        return false;
    }
    return lhs.second < rhs.second;
}

int main() {
    std::map<string, double> m1,m2;
    m1["A"] = 20;
    m2["A"] = 20.01;
    bool res = lexicographical_compare(
        m1.begin(), m1.end(),
        m2.begin(), m2.end(),
        mycomp
    );
    cerr << res << endl;
    return 0;
}
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
0

When you compare M1 to M2 you are comparing the maps to each other, not the values in the maps. If you want to compare doubles, with some sort of tolarance see this post.

Community
  • 1
  • 1
David D
  • 1,571
  • 11
  • 12