HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpModerate

Type system for different representations of angle value

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
systemvaluetypedifferentrepresentationsforangle

Problem

I want to implement a Type system for different representations of an angle value. Motivation to implement this as a type system comes from this question.

Angle can be represented using the following types:

  • Degrees (45.5)



  • Degrees Minutes Seconds, DMS (45*30'00")



  • Radians (0.7941)



It should support conversions between types (like Convert.ToInt32(someDouble)), and from system types (to/from double).

  • What properties should this type system have?



  • Should Degrees types be equal (implemented internally) if we convert from Degrees to Radians and back to Degrees? Or it should be compared as doubles with Epsilon value outside?



  • In what cases should explicit and implicit conversion operators be used, keeping in mind value precision loss on conversion?



Here is the work in progress:

```
// in current implementation this test will fail
[Test]
public void DegreesToRadiansToDegreesEquivalence()
{
Degree initialDegree = 0;

for (int i = 0; initialDegree Double
public static implicit operator Degree(Double value)
{
return new Degree(value);
}

// Implicitly Degree -> Double
public static implicit operator Double(Degree d)
{
return d.ToDouble();
}

// Explicitly Degree -> Radian
public static explicit operator Degree(Radian r)
{
return ConvertAngle.ToDegree(r);
}

// Explicitly Degree -> Radian
public static explicit operator Radian(Degree d)
{
return ConvertAngle.ToRadian(d);
}
}

// todo better to preserve sign in all fields because otherwise it would be impossible to represent 0°00'-20"
// store sign separate ? easy to implement operators - +
public struct DMS : IEquatable
{
public readonly double Degrees;
public readonly double Minutes;
public readonly double Seconds;

public DMS(double degree, double minute, double second)
{
Degrees = Math.Floor(degree);
Minutes = Mat

Solution

I don't think you should treat the units as disjoint entities between which you convert. You have started your post by saying


Angle can be represented using following types

But actually what you meant is "units".

So the entity you are trying to measure is an angle. And code using it should, for most parts, not care what unit it represents - just that it represents a specific angle. Expressing it as a value in a specific unit is only really necessary for calculations in algorithms which require the angle to be of a specific unit, serialization, display, etc.

Therefore I suggest a different design: You have an Angle type which represents a specific angle. Choose whichever unit you like best as internal representation of it. The type then exposes methods to create an Angle from various value in specific units and convert them to such. Something along these lines:

struct Angle
{
    private double _Degrees; // I chose degrees as internal representation for an angle but as other have pointed out Radians might be better

    private Angle(double degrees)
    {
        _Degrees = degrees;
    }

    public static Angle FromDegrees(double degrees)
    {
        return new Angle(Normalize(degrees)); // Ensure angles are in [0-360[
    }

    public static Angle FromRadians(double radians)
    {
        return new Angle(RadiansToDegrees(radians));
    }

    public static Angle FromDegreesMinutesSeconds(DegreesMinutesSeconds dms)
    {
        return new Angle(DegreesMinutesSecondsToDegrees(dms));
    }

    public double AsDegrees()
    {
        return _Degrees;
    }

    public double AsRadians()
    {
        return DegreesToRadians(_Degrees);
    }

    public DegreesMinutesSeconds AsDegreesMinutesSeconds()
    {
        return DegreesToDegreesMinutesSeconds(_Degrees);
    }
}


Consider overloading ==, !=. You could probably also overload = and >. Although you might have to consider questions like: Is 359 degrees greater or smaller than 5 degrees?

Override Equals and GetHashCode is you overload == (check Microsoft guidelines).

Overloading + and - (Angle + Angle, Angle + scalar, Angle - Angle, Angle - scalar) as well as and / (Angle scalar, Angle / scalar) makes for nicer calculations.

Code Snippets

struct Angle
{
    private double _Degrees; // I chose degrees as internal representation for an angle but as other have pointed out Radians might be better

    private Angle(double degrees)
    {
        _Degrees = degrees;
    }

    public static Angle FromDegrees(double degrees)
    {
        return new Angle(Normalize(degrees)); // Ensure angles are in [0-360[
    }

    public static Angle FromRadians(double radians)
    {
        return new Angle(RadiansToDegrees(radians));
    }

    public static Angle FromDegreesMinutesSeconds(DegreesMinutesSeconds dms)
    {
        return new Angle(DegreesMinutesSecondsToDegrees(dms));
    }

    public double AsDegrees()
    {
        return _Degrees;
    }

    public double AsRadians()
    {
        return DegreesToRadians(_Degrees);
    }

    public DegreesMinutesSeconds AsDegreesMinutesSeconds()
    {
        return DegreesToDegreesMinutesSeconds(_Degrees);
    }
}

Context

StackExchange Code Review Q#39360, answer score: 13

Revisions (0)

No revisions yet.