p1-stats

Comparisons

This tutorial covers comparing signed and unsigned integers, and comparing floating point numbers (double).

We’ll explain how to fix errors like this:

error: comparison between signed and unsigned integer expressions

Signed and unsigned integer comparisons

Example from stats.cpp:

double sum(vector<double> v) {
  double total = 0;
  for (int i = 0; i < v.size(); ++i) {
    total += v[i];
  }
  return total;
}

Compile and get this error:

$ make stats_tests.exe
g++ -Wall -Werror -pedantic -g --std=c++17 stats_tests.cpp stats.cpp -o stats_tests.exe
stats.cpp: In function 'double sum(std::vector<double>)':
stats.cpp:17:21: error: comparison between signed and unsigned integer expressions [-Werror=sign-compare]
   for (int i = 0; i < v.size(); ++i) {
                   ~~^~~~~~~~~~
cc1plus: all warnings being treated as errors
make: *** [stats_tests.exe] Error 1

The problem is v.size() returns a size_t type, which is an alias for an unsigned integer type. The loop variable i is an int type. The types don’t match.

Solution 1: size_t

Change int i to size_t i. Now, the types match and the compiler is happy.

double sum(vector<double> v) {
  double total = 0;
  // primer-spec-highlight-start
  for (size_t i = 0; i < v.size(); ++i) {
  // primer-spec-highlight-end
    total += v[i];
  }
  return total;
}

Solution 2: static_cast<>()

Cast v.size() to an int. Again, the types match and the compiler is happy.

double sum(vector<double> v) {
  double total = 0;
  // primer-spec-highlight-start
  for (int i = 0; i < static_cast<int>(v.size()); ++i) {
  // primer-spec-highlight-end
    total += v[i];
  }
  return total;
}

Floating point comparisons

Another comparison error you may encounter occurs when you compare two floating point numbers, like doubles. Floating point numbers have limited precision. Due to rounding errors, two floating point numbers we expect to be equal may be slightly different.

For example:

//test.cpp
#include <iostream>
using namespace std;

int main() {
  double x = 1.0 / 3.0;
  double y = 1.0 - (2.0 / 3.0);
  cout << "x=" << x << endl;
  cout << "y=" << y << endl;
  if (x == y) {
    cout << "equal" << endl;
  } else {
    cout << "not equal" << endl;
  }
}

Compile and run. The two numbers look the same, but when we compare them, they are not equal! Notice that x and y are rounded to 6 decimal places by default.

$ g++ test.cpp -o test.exe
$ ./test.exe
x=0.333333
y=0.333333
not equal

Let’s look at the full precision. Modify your program to look like this.

//test.cpp
#include <iostream>
#include <limits>
using namespace std;

int main() {
  double x = 1.0 / 3.0;
  double y = 1.0 - (2.0 / 3.0);
  // primer-spec-highlight-start
  cout.precision(std::numeric_limits<double>::max_digits10);
  // primer-spec-highlight-end
  cout << "x=" << x << endl;
  cout << "y=" << y << endl;
  if (x == y) {
    cout << "equal" << endl;
  } else {
    cout << "not equal" << endl;
  }
}

Compile and run. Notice that x and y are no longer rounded to 5 decimal places. We can see that they are slightly different.

$ g++ test.cpp -o test.exe
$ ./test.exe
x=0.33333333333333331
y=0.33333333333333337
not equal

Next, we’ll compare within a tolerance epsilon, instead of an exact comparison. Again, modify your program:

//test.cpp
#include <iostream>
// primer-spec-highlight-start
#include <cmath>
// primer-spec-highlight-end
#include <limits>
using namespace std;

// Precision for floating point comparison
const double epsilon = 0.00001;

int main() {
  double x = 1.0 / 3.0;
  double y = 1.0 - (2.0 / 3.0);
  cout.precision(std::numeric_limits<double>::max_digits10);
  cout << "x=" << x << endl;
  cout << "y=" << y << endl;
  // primer-spec-highlight-start
  if (abs(x - y) < epsilon) {
  // primer-spec-highlight-end
    cout << "equal" << endl;
  } else {
    cout << "not equal" << endl;
  }
}

Compile and run. Notice that the comparison now reports equal.

$ g++ test.cpp -o test.exe
$ ./test.exe
x=0.33333333333333331
y=0.33333333333333337
equal