patterncsharpModerate
Type system for different representations of angle value
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:
It should support conversions between types (like
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
Angle can be represented using the following types:
Degrees(45.5)
DegreesMinutesSeconds,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
Degreestypes be equal (implemented internally) if we convert fromDegreestoRadiansand back toDegrees? 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
Consider overloading
Override
Overloading
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.