2

I have several classes that follow the same basic concept for dealing with units.

Here is a simple example of one being used in a unit test:

    [Test()]
    public void Angle_MathOperatorTest()
    {
        Angle a1 = new Angle(AngleType.Degree, 360);
        Angle a2 = new Angle(AngleType.Radian, Math.PI * 2);

        Angle addedAngle = a1 + a2;
        addedAngle.Degrees.ShouldBeEquivalentTo(720);

        Angle subtractedAngle = a1 - a2;
        subtractedAngle.Radians.ShouldBeEquivalentTo(0);
    }

I have made several classes like this demonstrated Angle Class, covering other basic unit types.

The specific class that revealed to me that I had a precision problem was the use of the class that handles units of length: Dimension

I have helped build a basic Geometry Library utilizing this Dimension class as its basic unit type. For Example, here is the Point class:

public class Point
{

    public Dimension X;

    public Dimension Y;

    public Dimension Z;
}

Lines and other shapes have properties like Length that are represented by Dimensions and endpoints that are built using this Point Class.

The problem becomes evident when I attempt to tell if those lines are all parallel. As in this function:

    /// <summary>
    /// checks to see whether every line is parallel
    /// </summary>
    /// <param name="passedLines">passed List of Lines</param>
    /// <returns></returns>
    public static bool AreAllParallel(this List<Line> passedLines)
    {

        for (int i = 0; i < passedLines.Count - 1; i++)
        {
            if (!passedLines[i].IsParallelTo(passedLines[i + 1]))
            {
                return false;
            }
        }

        return true;
    }

It will often return false because it is checking for too high of a precision. After doing Rotations and Translations with Points and lines, the rounding adds up to just enough to make this function return false when I would like for it to return true.

So:

Which set of the following choices is the correct/better choice?

  • just doing a check in functions like IsParallelTo for the numbers being relatively close (e.g. Within .0001 inches)

If(Math.Abs( thing.x - thing2.x) < .0001)

  • improving upon the previous idea with a variable constant that is pulled from a configuration file, therefore allowing the user to choose the desired acceptable deviation

If(Math.Abs( thing.x - thing2.x) < Properties.AcceptedDeviationConstant)

  • or reducing the problem at the root level in the Dimension Class:

I can either use the same strategy

//inside Dimension Equals
public override bool Equals(object obj)
{
    return (Math.Abs(this.Inches - ((Dimension)(obj)).Inches)) < Constants.AcceptedEqualityDeviationConstant;
}

It would really look like this but the above is easier to understand

return (Math.Abs(this.GetValue(this.InternalUnitType) - ((Dimension)(obj)).GetValue(this.InternalUnitType))) < Constants.AcceptedEqualityDeviationConstant;

Or finally, my last idea is to replace everything in my unit classes to the base unit of Decimal instead of Double (Dimension, Angle, Etc.) to Decimal and somehow (After studying up on it) figure out if that will help.


How and where should I improve the consistency of precision in my classes' equality operations?

p.s. The libraries are open-source and can be found (Units here and Geometry here)

jth41
  • 3,808
  • 9
  • 59
  • 109
  • All of the issues you're observing are due to floating point arithmetic. – Brian Driscoll Oct 03 '14 at 19:14
  • Which would be elegantly handled by implementing the well known pattern of: XXXXX see [here for more details](http://media.giphy.com/media/y65VoOlimZaus/giphy.gif).? – jth41 Oct 03 '14 at 19:16
  • I would advise against using decimals if you don't need that much precision. – brz Oct 03 '14 at 19:17
  • @jth41 If I had an answer I would have submitted an answer, not a comment. :) – Brian Driscoll Oct 03 '14 at 19:18
  • 2
    I would let the caller decide the level of precision required. In the IsParallelTo and AreAllParallel methods, include a tolerance parameter that gives the maximum angle allowed. – Dave Mackersie Oct 03 '14 at 19:18
  • PS. Looking at the geometry library, I see a lot of redundant values in the definition of plane, line, segment and vectors. This may lead to an inconsistent state (like end points not on a line for a segment) given the precision problems. May I suggest reading on homogeneous coordinates for points and planes and for pluecker coordinates for lines. – John Alexiou Oct 03 '14 at 22:24
  • Also you should _always_ wrap your angle values to -180° to 180°, or 0 to 360° (and 0 to π) to avoid equality comparisons failing since angles have the property of `-90°=270°` – John Alexiou Oct 03 '14 at 22:26

1 Answers1

0

If e1 is the direction of line one and e2 the direction of line two then to check if they are parallel within a slope tolerance of θ (radians) do the following (pseudo code):

bool is_parallel = |Cross(e1,e2)| <= |e1|*|e2|*Cos(θ)

where |v| is the vector magnitude and Cross() is the vector cross product.

John Alexiou
  • 28,472
  • 11
  • 77
  • 133