Class with methods that depend on a variable argument, but this variable is constant throughout large parts of the application

I am looking for advice on how to design the following scenario:

I have a 3-dimensional vector class, the entries are of type double. I want two vectors to be considered equal if their corresponding elements differ by less than a given tolerance, e.g. 10^(-10).

(Code examples are in C# syntax mixed with pseudocode.)

public class Vector
{
    double x, y, z;

    public bool TolEquals(Vector other)
    {
        return ((|x-other.x| < 10^(-10))  // absolute value of difference
             && (|y-other.y| < 10^(-10))  // must be small enough
             && (|z-other.z| < 10^(-10)));
    }
}

I need a certain flexibility because other people might want to use another tolerance value than 10^(-10).

That means, I need to somehow incorporate the tolerance value into my equality checking method, e.g. so:

public bool TolEquals(Vector other, double tolerance)
{
    return ((|x-other.x| < tolerance)
         && (|y-other.y| < tolerance)
         && (|z-other.z| < tolerance);
}

But I know that throughout my application, the tolerance will be constant. And I do not want to write

v1.TolEquals(v2, tolerance)

every time I check for equality since it is not very readable and since typos can happen easily.

Another idea is to store tolerance as a member of the Vector class and set it in the constructor of Vector. Then the TolEquals method needs only one argument:

public class Vector
{
    double x, y, z;
    double tolerance;

    public Vector(double x, double y, double z, double tolerance)
    {
        this.x = x;
        this.y = y;
        this.z = z;
        this.tolerance = tolerance;
    }

    public bool TolEquals(Vector other)
    {
        return ((|x-other.x| < tolerance)
             && (|y-other.y| < tolerance)
             && (|z-other.z| < tolerance));
    }
}

But then someone using my code could accidentally create two vectors v and w with different tolerances and would get different answers from v.TolEquals(w) and w.TolEquals(v). That’s not supposed to happen either.

I will probably add some static methods to my Vector class sooner or later that take several vectors as input and do some calculation involving TolEquals with them. So I need to make sure that all vectors use the same tolerance value.

I am looking for a solution that allows me to define tolerance somewhere in the program, outside the Vector class, and then all vectors use that tolerance for their TolEquals. And I want to be able to change tolerance at run-time (don’t need it now but might need it later), resulting in all vectors using the new value instead.
BUT it might be that someone using my code needs two different tolerance values coexisting. He would have two distinct groups of vectors, using the different tolerance values, but the groups do not interact with each other.

So a static field tolerance is no option either.

I will also write a matrix class and maybe some others that will have an analogue of TolEquals, so the solution should not be limited to my Vector class.

Any ideas?

EDIT:

Inspired by the accepted answer I feel like presenting my final solution (in C#) and put in some comments. All information is given in the accepted answer, but it took me some time to process it so I’ll refine it a bit here. I decided to implement it with a static field for the tolerance in the generic vector class:

public interface ISpace { double TOL(); }

public class ZeroTolSpace : ISpace { public double TOL() => 0; }
public class Tol10Space : ISpace { public double TOL() => 1E-10; }
// Others can easily write new classes for other tolerance values.

public class Vector<T> where T: ISpace, new()
{
    private double x, y, z;
    public static double? tol = null; // Changeable at runtime (if really needed)!

     public Vector(double x, double y, double z)
     {
         this.x = x; this.y = y; this.z = z;
         tol = tol ?? (new T()).TOL(); // Since tol is static, the new-operator
                                       // gets called only once, minimzing overhead.
     }

    public bool TolEquals(Vector other)
    {
         return ((Math.Abs(x-other.x) < tol)
              && (Math.Abs(y-other.y) < tol)
              && (Math.Abs(z-other.z) < tol));
    }

}

Then in the Main method we can do:

        Vector<ZeroTolSpace> v1 = new Vector<ZeroTolSpace> (0, 0, 0);
        Vector<ZeroTolSpace> v2 = new Vector<ZeroTolSpace>(1E-11, 1E-11, 1E-11);
        v1.TolEquals(v2)); // false since _tol is 0

        Vector<Tol10Space> w1 = new Vector<Tol10Space> (0, 0, 0);
        Vector<Tol10Space> w2 = new Vector<Tol10Space> (1E-11, 1E-11, 1E-11);
        w1.TolEquals(w2)); // true since tol is 1E-10

        v1.TolEquals(w2)); // compile error: types do not match

Matrix and other classes depending on tolerance can be implemented in the same way.

3

But I know that throughout my application, the tolerance will be
constant.

This is a bit suspect because such tolerance is usually very algorithm specific. I’ve had to deal with much pain because someone thought that it they could get away with using a constant tolerance everywhere.

But assuming, that you really do want this, I might do:

public class Space {
    public Space(double tolerance);


    public Vector vector(double x, double y, double z);
    public Matrix matrix(...);
}

So you create a Space class which provides the only way to create a vector. You can’t combine vectors created by different space classes. Within a particular application, you simply create a Space constant which defines the tolerance you are using.

And I want to be able to change tolerance at run-time (don’t need it
now but might need it later), resulting in all vectors using the new
value instead.

No, you don’t. Changing the tolerance at runtime will lead to pain and suffering. If your code needs to have different tolerances in different circumstances, you really do want to pass a parameter around.

Now, really, what would be nice is if we could use generics:

Vector<MySpace>

Then have a static method on MySpace define the tolerance. Then the type system could verify that you never mix different types of vectors. Alas, this is beyond some programming languages so you may or may not be able to use it.

== EDIT ==

C++ can do this very nicely with templates:

struct MySpace {
   public static double tolerance = 0.000001;
}

template<typename T>
class Vector {
    Vector(double x, double y, double z);

    // We can refer to T::tolerance
}

// I can use Vector<MySpace> to refer to vectors with a particular
// space, and have the type system enforce that.

But this depends on the fact that a C++ template can freely refer to elements inside its type parameters. But other languages often lack this.

In C# you could do something like (I don’t do C#, but I believe this is valid)

interface Space {
   double tolerance();
}

public class MySpace extends Space {
   double tolerance() {
       return 0.000001;
   }
}

public class Vector<T> where T: Space, new() {
   public Vector(double x, double y, double z);

   // I can get my tolerance via (new T()).tolerance()
   // We hope that the optimizer can get rid of the overhead.
}

But in Java, that won’t work, because we can’t do new T. We could do something like:

class Vector<T extends Space> {
    Vector(T t, double x, double y, double z);

    // t.tolerance() will be the tolerance.    
}

And then follow a convention to always create instances of Space by a singleton subclass.

===

More detail on Java Solution

In library:

public interface Space {
    double tolerance();

    public static <T extends Space> double toleranceForSpace(Class<T> spaceClass) {
    return spaceClass.newInstance().tolerance();
}
}

public Vector<SpaceType> {
    Vector(Class<SpaceType> spaceType, double x, double y, double z);

    // call Space.toleranceFor(spaceType) to get the tolerance
}

In non-library code:

class MySpace extends Space {
   double tolerance() { return 0.1; }
}


Vector<MySpace> space = new Vector<>(MySpace.class, 2.3, 4.5, 6.7);

9

Optional parameters can work nicely here.

public class Vector {
  public static readonly double DefaultTolerance = 0.0001; // or whatever.

  public bool Equals(Vector other, double? tolerance = null) {
    tolerance = tolerance ?? DefaultTolerance;
    // go about your business
  }
}

// later
w.Equals(v);
w.Equals(v, 0.001);

And since the tolerance applies to the operation, and not the data, it’s fine for the vectors to not own it themselves.

3

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *