Page 1 of 1

A Unit System Rate Topic: -----

#1 AdamSpeight2008  Icon User is offline

  • MrCupOfT
  • member icon


Reputation: 2271
  • View blog
  • Posts: 9,498
  • Joined: 29-May 08

Posted 15 April 2014 - 06:34 PM

A Unit System

Having a System of Units encode the context of the value.
For example 10 10 what? Without context is useless.
Let's add a context Water Depth:= 10 Ok, we now know the water depth is 10 but we still don't know 10 of what? 10 cats, 10 feet, 10 metres.

For Example Water Depth:= 10(metres)

Also by using a System of Units you can rather refine the parameters required for a function.

''  TaxiFare : ICurrency = <Distance : IUnitOfDistance> * <Price : ICurrency>
  Function TaxiFare( Distance : IUnitOfDistance, Price As IUnitOfCurrency ) : ICurrency
''
End Function






The Base Interface
All of my units in my system will have a Value and Symbol associated with it.
Public Interface IUnitOfMeasure
  ReadOnly Property Value As Double
  ReadOnly Property Symbol As String
End Interface



Let's define an Interface that describes a unit of distance. Metres, Inches, etc
Public Interface IUnitOfDistance
  Inherits IUnitOfMeasure 
  Function ToMetres() As Metres 
  Function FromMetres(m As Metres) As IUnitOfDistance 
  Function CreateInstance(v As Double) As IUnitOfDistance 
End Interface






Now let's create so concrete instance of those interface, so out units can inherit from them.

Public MustInherit Class UnitOfMeasure
  Implements IUnitOfMeasure 
  Private _Value As Double 
  Private _PostFix As Boolean 
  Private _Symbol As String 

  Friend Sub New(Value As Double, Postfix As Boolean, Symbol As String )
    _Value   = Value 
    _PostFix = Postfix
    _Symbol  = Symbol
  End Sub

  Public ReadOnly Property Value As Double Implements IUnitOfMeasure.Value 
    Get
      Return _Value 
    End Get
  End Property

  Public ReadOnly Property PostFix As Boolean
    Get
      Return _PostFix  
    End Get
  End Property

  Public ReadOnly Property Symbol As String Implements IUnitOfMeasure.Symbol 
    Get
      Return _Symbol 
    End Get
  End Property

  Public Overrides Function ToString() As String
    If PostFix Then Return String.Format("{0}{1}",Value,Symbol)
    Return String.Format("{0}{1}",Symbol,Value)
  End Function

End Class



Public  MustInherit Class UnitOfDistance
  Inherits UnitOfMeasure
  Implements IUnitOfDistance

  Friend  Sub New(Value As Double, Symbol As string)
    MyBase.New(Value ,True,Symbol )
  End Sub
  MustOverride Function ToMetres() As Metres Implements IUnitOfDistance.ToMetres
  MustOverride Function CreateInstance(Value As Double) As  IUnitOfDistance Implements IUnitOfDistance.CreateInstance
  MustOverride Function FromMetres(m As metres) As  IUnitOfDistance Implements IUnitOfDistance.FromMetres
 
  Public Shared Operator +(ByVal x As UnitOfDistance, y As UnitOfDistance) As IUnitOfDistance 
    If x.GetType = y.GetType then Return x.CreateInstance(x.Value + y.Value)
    Return x.FromMetres( x.ToMetres + y.ToMetres)
  End Operator

  Public  Shared Operator -(ByVal x As UnitOfDistance, y As UnitOfDistance) As IUnitOfDistance 
    If x.GetType = y.GetType then Return x.CreateInstance(x.Value - y.Value)
    Return x.FromMetres( x.ToMetres - y.ToMetres)
  End Operator

  Public Shared Function Convert(Of T1 As UnitOfDistance,
                                    T2 As {New, UnitOfDistance})(value As T1) As T2
    Dim p1 = value.ToMetres
    Dim tmp As New T2
    Return DirectCast(tmp + p1, T2)
  End Function

End Class






Now we finally ready to implement a couple of UnitOfDistances.

Metres
Public Class Metres
  Inherits UnitOfDistance
  Public Sub New()
    MyBase.New(0, "m")
  End Sub

  Public sub new(Value As Double)
    MyBase.New(Value,"m")
  End Sub 

  Public Overrides Function CreateInstance(Value As Double) As IUnitOfDistance
    Return New Metres(Value)
  End Function

  Public Overrides Function FromMetres(m As Metres) As IUnitOfDistance
    Return New Metres(Me.Value)
  End Function

  Public Overrides Function ToMetres() As Metres
    Return New Metres(Me.Value)
    End Function 

  Public Shadows Shared Operator +(x As Metres, y As Metres) As Metres
    Return New Metres(x.Value + y.Value) 
  End Operator
    Public Shadows Shared Operator -(x As Metres, y As Metres) As Metres
    Return New Metres(x.Value + y.Value) 
  End Operator
    Public Shared Widening Operator CType (ByVal v As Double) As Metres
    Return New Metres(v)
    End Operator

 End Class



Inches
Public Class Inches
  Inherits UnitOfDistance

  Public Sub New()
    MyBase.New(0, """")
  End Sub

  Public Sub New(Value As Double)
    MyBase.New(Value, """")
  End Sub
  Public Overrides Function CreateInstance(Value As Double) As IUnitOfDistance
    Return New Inches(value)
  End Function

  Public Overrides Function ToMetres() As Metres
    Return New Metres((Value * 2.54) / 100)
  End Function

  Public Overrides Function FromMetres(m As Metres) As IUnitOfDistance
    Return New Inches((m.Value * 100) / 2.54)
  End Function

  Public Shared Widening Operator CType(ByVal v As Double) As Inches
    Return New Inches(v)
  End Operator
End Class




Usage Example

Module Module1

    Sub Main()
    Dim m0 = New Metres(1)
    Dim i0 = New Inches(12)
    Dim m1 = (1.0).ToUnitOfDistance(Of Metres)
    Dim i1 = (12.0).ToUnitOfDistance(Of Inches)
    Dim m2 As Metres = 1
    Dim i2 As Inches = 12

    Dim r0_mi = m0 + i0
    Dim r0_im = i0 + m0
    Dim r1_mi = m1 + i1
    Dim r1_im = i1 + m1
    Dim r2_mi = m2 + i2
    Dim r2_im = i2 + m2
    Dim r3 = UnitOfDistance.Convert(Of inches,Metres)(i0)
    Dim r4 = UnitOfDistance.Convert(Of Metres,Inches)(m0)
        Console.ReadKey()
    End Sub

  <Extension> Function ToUnitOfDistance(Of T As {New, UnitOfDistance})(value As Double) As T
     Return DirectCast( (New T).CreateInstance(value),T)
  End Function
  end Module



Did you notice something surprising? Even though in derived units (metres and inches) we didn't implement any operators for performing adding and subtracting values in those units? We can still perform those operations. How?

Spoiler





Exercises

  • Add the ability to compare UnitsOfDistace
  • Add Additional Distance Units.
  • Add a new UnitOfMeasure (eg Temperature, Time, Energy etc) (SI Units)

    For the more adventurous

  • Add some unit of measure that are a function two of different units of measure systems. Eg
    Speed = IUnitOfDistance / ITime  (aka IRate(Of Ta, Tb )
    Area = IUnitOfDistance * IUnitOfDistance
    
    


Is This A Good Question/Topic? 0
  • +

Replies To: A Unit System

#2 Curtis Rutland  Icon User is offline

  • (╯□)╯︵ (~ .o.)~
  • member icon


Reputation: 4576
  • View blog
  • Posts: 8,018
  • Joined: 08-June 10

Posted 17 April 2014 - 11:31 AM

Very neat concept. I wonder if you could also do implicit casting, so assigning one unit to another (of the same "kind") would handle the conversion automatically?
Was This Post Helpful? 0
  • +
  • -

#3 AdamSpeight2008  Icon User is offline

  • MrCupOfT
  • member icon


Reputation: 2271
  • View blog
  • Posts: 9,498
  • Joined: 29-May 08

Posted 17 April 2014 - 07:36 PM

Curtis Rutland You probably could implement a implicit cast, but I'm not a fan of implicit casts.
I like to make the cast explicit.
For example if you where using the above classes in a C# project.
var x =  12.0(inches);
var d = 100.0(metres);


It doesn't make the code look much worse, if anything rather neat looking.
Not far away from the syntax use in F#
1.0<cm>
55.0<miles/hour>


Was This Post Helpful? 0
  • +
  • -

#4 Curtis Rutland  Icon User is offline

  • (╯□)╯︵ (~ .o.)~
  • member icon


Reputation: 4576
  • View blog
  • Posts: 8,018
  • Joined: 08-June 10

Posted 18 April 2014 - 07:27 AM

Agreed, I usually prefer explicit casting as well. I feel that implicit is occasionally appropriate, but it does make the code more readable with the cast operator being explicit.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1