I'm trying to start writing tests for all of my functions as per Test Driven Development. My question is, say I construct a test for "rotate vector". It would be something like this:

int TestRotateVector(const Vector  &V)
{
  Vector Original(1.0, 2.0);
  Vector Rotated = Original.Rotate(10); //rotate 10 degrees
  if(Rotated == Vector(2.34, 5.67)) //...whatever the correct answer should be
    return 0; //pass
  else
  return -1; //fail
}

So my question is, do I have to use epsilon tests to test this kind of equality - i.e. if( fabs(Rotated.x - 2.34) < epsilon && fabs(Rotated.y - 5.67) < epsilon) ? Or is there a better way to write test functions?

Thanks,

Dave

Recommended Answers

All 7 Replies

So the answer is, in general, just use epsilon tests?

I normally think it is better to wrap these kind of fabs() type test
in a testing namespace , that provides a template form like this

namespace Approximate
{
template<typename T>
 bool equal(const T& A, const T& B,const double precision = 1e-4);

template<typename T>
bool notEqual(const T& A,const T& B,const double precision = 1e-4);

// less / greater etc.

}

Then specialize each type specifically. (that allows a 2/3 value comparison for vector, allows stuff like angles to remove the 360 degree phase invarient [e.g. 361 degrees is often the same as 1 degree]. Also for surfaces, it allows opposite normals to be considered the same (if sense doesn't matter).

This namespace then fits nicely into most testing framework, e.g. CppUnit.http://sourceforge.net/projects/cppunit/ and can be extended if you need to test base/derived classes etc.

Then the framework deals with about 95% of the stuff and you don't have to worry about writing another fabs(a-b)>epsilon and you have only done it once. Which was one of Strustrup's original tenants.

Note back: Normally you implement notEqual as !Equal BUT there are exceptions....

Epsilon test is a way to satisfy ourself to the incompetency of the computer system to process real numbers. It should be [can be] applied to every domain involving real numbers and real computer.
uVA Online Judge's website contains an excellent paper about Numerical Difficulties in Pre-University
Informatics Education and Competitions
which can be found on http://online-judge.uva.es/problemset/float-in-competition.pdf . Its a must read.
As far as your problem is concerned, ask the same old question to yourself, how much precision do you need?
I suggest you to code the epsilon test while you overload the operator== for your Vector.
One more thing: don't you think the Rotate function is too vague; shouldn't it take two arguments: the amount of rotation and about which axis should the rotation occur. In that case, Rotate should be Rotate(double m, Vector a) where m is the amount of rotation and a is the vector axis about which the rotation should occur.

Thanks guys.

Siddhant3s - yes of course Rotate is too vague - I just made up a dummy example for the sake of argument.

StuXYZ - what was one of the original tenants? Only write code once and then reuse it?

Some comments:

First yes, one of the original ideas was to only write code once. (it was actually discussed in terms of macros but it still holds)

(b) #include <limits> gives the absolute best possible accuracy. This is not normally very useful for these type of problems. For example, you apply some matrix and quaternion rotations to some vectors, and you want to know if any are "equal". This will include several rounding errors, ie several epsilons. Yes you can determine the number, but normally there is an acceptable tolerance, but for say an eigenvector determination, is not really an option to determine the expected epsilon error from first principles, it is better to set a tolerance.

(c) Don't encode your equals in operator== as a class member. The key thing to note is the operator== does not take an additional parameter. It can only be retType operator==(inputType) {const};
were retType is any return type you like and inputType is any
input type you like BUT you only get one!. (const is optional)

Then because equal takes a number of different levels, e.g. at 1% or 0.0001% etc. Dependent on the instance. That also means you don't encode the tolerance as a static class variable. You then have to change it each call (and will forget).

I like the boost form boost::test_tools::close_at_tolerance<TYPE> and
boost::test_tools::percent_tolerance type forms
which are described http://www.boost.org/doc/libs/1_38_0/libs/test/doc/html/utf/testing-tools/floating_point_comparison.html and http://www.boost.org/doc/libs/1_35_0/libs/test/doc/components/test_tools/floating_point_comparison.html. The second reference is the implementation and the first is why the simple fabs(a-b) is not a very good idea and how to do it properly. ( Its main reference is Knuth so it must be good ;) )

I think it is acceptable to have a rotation about the origin in the class as well as a rotation about a point/axis. It is domain specific, dependent on the useage. But it should return a Vector& since you often want to chain, rotations + translations together.

Finally, if you are using Vector for you vector class (in 2D), do not use using namespace std; in your code (something I think is alway a good idea to avoid), because you will type vector by mistake and it is a nightmare to find. (ideally, I would put your vector into a namespace e.g. Geometry, but then I use Vec3D and Vec2D etc as well as specialization to Point3D etc. )

---
Ok that was a bit of a diatribe... sorry. Some of the points are going to be valid and some not-so depending on your project. It is just that poor numerical error handling and numerical error build up, is a major day-to-day problem, which cost a lot of time (both CPU and human) and a significant amount of that is due to poor coding-practice. It (not CPU) is normally the main reason that limits most or my simulation runs.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.