# Type system for different representations of angle value

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]
{
Degree initialDegree = 0;

for (int i = 0; initialDegree < 360; initialDegree += 1, i++)
{

Assert.AreEqual(initialDegree, resultDegree, Double.Epsilon, String.Format("{0} {1}", i, (double)convertedToRadian));
}
}

public struct Degree
{

// Construct Degree from Double
public Degree(Double value)
{
_value = value;
}

// Converts Degree to Double
private Double ToDouble()
{
return _value;
}

// Implicitly Degree -> 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();
}

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

public static explicit operator Radian(Degree 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<DMS>
{

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

public bool Equals(DMS other)
{
return (Math.Abs(other.Degrees - this.Degrees) < double.Epsilon) &&
(Math.Abs(other.Minutes - this.Minutes) < double.Epsilon) &&
(Math.Abs(other.Seconds - this.Seconds) < double.Epsilon);
}
}

{

// Construct Degree from Double
{
_value = value;
}

// Converts Degree to Double
private Double ToDouble()
{
return _value;
}

public static implicit operator Radian(Double value)
{
}

public static implicit operator Double(Radian d)
{
return d.ToDouble();
}
}

public enum AngleFormat
{
Degrees,
DegreesMinutes,
DegreesMinutesSeconds,
}

public static class ConvertAngle
{
{
return radians * 180.0 / Math.PI;
}

public static Degree ToDegree(DMS dms)
{
// todo seems sign problem here
return dms.Degrees + dms.Minutes / 60 + dms.Seconds / 3600;
}

{
return degrees * Math.PI / 180.0;
}

{
}

{
}

public static DMS ToDMS(Degree degree)
{
double degrees = Math.Floor(degree);
double rem = (degree - degrees) * 60.0;
double minutes = Math.Floor(rem);
double seconds = (rem - minutes) * 60.0;

return new DMS(degrees, minutes, seconds);
}

{
{
string formStr = "{0:F" + precision + "}";

}
else if (format == AngleFormat.Degrees ||
format == AngleFormat.DegreesMinutes ||
format == AngleFormat.DegreesMinutesSeconds)
{
}

throw new NotImplementedException();
return "";
}

public static string ToString(Degree degree, AngleFormat format, int precision)
{
DMS dms = ToDMS(degree);
switch (format)
{

// todo use precision
case AngleFormat.Degrees:
case AngleFormat.DegreesMinutes:
case AngleFormat.DegreesMinutesSeconds:
}
throw new NotImplementedException();
return "";
}

public static string ToString(DMS dms, AngleFormat format, int precision)
{
switch (format)
{
// todo here do we need to combine min and sec ??? or we need diffrent format option
case AngleFormat.Degrees:
return String.Format("{0:D}°", dms.Degrees);
case AngleFormat.DegreesMinutes:
return String.Format("{0:D}°{1:D}'", dms.Degrees, dms.Minutes);
case AngleFormat.DegreesMinutesSeconds:
string secondsPrecisionFormat = "F" + Math.Abs(precision).ToString("D");

//string d = dms.Degrees.ToString("");

//CultureInfo currentCulture = CultureInfo.CurrentCulture;
string stringFormat = "{0:F0}° {1:F0}' {2:" + secondsPrecisionFormat + "}\"";
return String.Format(stringFormat, dms.Degrees, dms.Minutes, dms.Seconds);

// todo convert to radians ??
break;
}

throw new ArgumentOutOfRangeException("format");
}
}

• Are you asking us what your system should be able to do? Commented Jan 16, 2014 at 8:35
• given nature properties, conversion rules of angle representations. What constraints, properties should type system have ? What is wrong right now in implementation? Commented Jan 16, 2014 at 8:51

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 FromDegreesMinutesSeconds(DegreesMinutesSeconds dms)
{
return new Angle(DegreesMinutesSecondsToDegrees(dms));
}

public double AsDegrees()
{
return _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.

• How would angle + scalar behave? I think that doesn't make much sense. Commented Jan 16, 2014 at 11:53
• As @svick noted above, Angle + scalar does not make sense unless it is perfectly obvious that the "default" unit of an Angle is for example degrees. Also, you chose degrees as the internal representation unit for the Angle - I would chose to use radians instead (the preferred SI unit for generic angles). Otherwise, this is pretty much the approach that I would have used. Commented Jan 23, 2014 at 8:18
• @svick good point Commented Jan 23, 2014 at 8:57
• This is quite similar to TimeSpan in .NET. You can ask "how many minutes" or "how many seconds" it has, or conversely create a new one with TimeSpan.FromSeconds(), or TimeSpan.FromMinutes(). Internally, it is represented by Ticks, whatever that might be :o) Commented Dec 16, 2014 at 12:40
• Angle / Angle which returns a scalar is also another operator you may wish to implement. Commented Jan 18, 2016 at 17:14

I'd consider a simpler, single class:

class Angle
{
// The 'native' type, passed to System.Math methods
public double Radians { get; set;}
// Factory methods
public static Angle FromRadians(double d) { ... }
public static Angle FromDegrees(double d) { ... }
public static Angle FromDegrees(int degrees, int minutes, double seconds) { ... }
// Display methods
public string ToRadians(int precision) { ... }
public string ToDegrees(int precision) { ... }
}


I imagine that ToDegrees returns a string which you can display on a UI.

The difference between my answer and @ChrisWue's are:

• I use radians as the internal/native unit, because they're what are expected by System.Math methods
• I avoid returning degrees as a number (I return them as a string), to discourage users from performing calculations using degree -> radian -> degree conversions.

For your DMS type, perhaps "degrees" and "minutes" should be integer (not double) types.

• That awkward moment when one realizes that you and @ChrisWue are two different persons (conspiracy theories aside) :) Commented Jan 16, 2014 at 10:34

Your implementation will be really hard to maintain as you keep adding different operations. Also the fact that you have multiple classes to represent single entity is kind of weird.

For example, look at DateTime implementation. It represents time in various formats via exposed properties and formattable ToString and Parse methods. But when it comes to manipulating DateTime values - it essentially comes down to manipulating Ticks (which is just one of the possible representations--the easiest one to deal with). I think you should do the same. Create a single Angle class, expose different representation via properties (Radians, Minutes, Seconds, Degrees, etc.), but when it comes to calculations - deal with radians representation only.

I see only one reason for having different representation. And that's precision errors. Degrees and radians probably will have different offsets when represented in double. But since almost any trigonometry operations will use radians there is not so much gain. You can just keep degrees and other reps temporary and/or in storage to show same value user entered, but eventually they will be converted to radians.

Consider radians to be angle value without any annotation and assumed by default for double that comes as angle. And just create value types that will be converted whenever you need work.

struct Degree {
public double Value;
public Degree(double value) : Value(value) {}
public double AsRadians { get { return /* to radians */; } }
public static Degree Radians(double d) { return /* from radians */; }
public static implicit operator double(Degree d) { return d.AsRadians; }
public static implicit operator Degree(double d) { return Radians(d); }
public static explicit operator Degree(double d) { return Radians(d); }
/* any operation require conversion to radians */
}