4
\$\begingroup\$

Link to a full functional solution.

I'm working on a physics based algorithm, and I find myself working a lot with functions of style

double GetSpeed(double acceleration, double angle, double resistance)

A lot of physical values are passed from function to function as double, and it is a nightmare. I fail a lot at giving the right value at the right parameter, and also at the good units. Sometimes speed is in m/s, sometimes in km/h.

So my idea was to create an abstract class for different physical values, like Speed, Angle, etc...
Since you can add doubles, you can also add speeds or angles, so the abstract class must support adding, soustracting, all basic operations you can do on double.

So here is my abstract class :

public interface ISIQuantity
{
    double SIValue { get; }
}

/// <summary>
/// Represent maths or physics quantities like angle, mass, speed
/// http://en.wikipedia.org/wiki/International_System_of_Units
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class SIQuantity<T> : ISIQuantity, IComparable, IComparable<T>, IEquatable<T>
    where T : ISIQuantity
{
    private readonly double _value;
    public double SIValue { get { return _value; } }

    public SIQuantity(double value)
    {
        this._value = value;
    }


    public int CompareTo(T other) { return this.SIValue.CompareTo(other.SIValue); }
    public int CompareTo(object other)
    {
        if (other is T)
        {
            return this.CompareTo(((T)other));
        }
        else
        {
            return 1;
        }
    }

    public bool Equals(T other) { return this.SIValue.Equals(other.SIValue); }
    public override bool Equals(object other)
    {
        if (other is T)
        {
            return this.Equals(((T)other));
        }
        else
        {
            return false;
        }
    }
    public override int GetHashCode()
    {
        return this._value.GetHashCode();
    }

    public static bool operator ==(SIQuantity<T> a, SIQuantity<T> b)
    {
        // If both are null, or both are same instance, return true.
        if (Object.ReferenceEquals(a, b))
        {
            return true;
        }

        // If one is null, but not both, return false.
        if (((object)a == null) || ((object)b == null))
        {
            return false;
        }

        return a.Equals(b);
    }
    public static bool operator !=(SIQuantity<T> r1, SIQuantity<T> r2)
    {
        return !(r1 == r2);
    }
    public static bool operator <(SIQuantity<T> r1, SIQuantity<T> r2)
    {
        return (r1.SIValue.CompareTo(r2.SIValue) < 0);
    }
    public static bool operator >(SIQuantity<T> r1, SIQuantity<T> r2)
    {
        return (r1.SIValue.CompareTo(r2.SIValue) > 0);
    }
    public static bool operator <=(SIQuantity<T> r1, SIQuantity<T> r2)
    {
        return (r1.SIValue.CompareTo(r2.SIValue) <= 0);
    }
    public static bool operator >=(SIQuantity<T> r1, SIQuantity<T> r2)
    {
        return (r1.SIValue.CompareTo(r2.SIValue) >= 0);
    }

    protected abstract T CreateFromSI(double siValue);

    public static T operator +(SIQuantity<T> r1, SIQuantity<T> r2)
    {
        return r1.CreateFromSI(r1.SIValue + r2.SIValue);
    }

    // all kind of operator
}

Basically, just a wrapper around a double.
The double is called SIValue, like Standard Unit value. The idea is to keep the value in the unit of the standard system. So Kilogram for a mass, or meter per seconds for speed.
Each of the derived class must override the CreateFromSI method for specify how to build a new unit based on a value in SI unit.

Here is how we use it :

public enum SpeedUnit { KilometerPerHour, MeterPerSecond }

public class Speed : SIQuantity<Speed>
{
    protected override Speed CreateFromSI(double value)
    {
        return new Speed(value, SpeedUnit.MeterPerSecond);
    }

    public Speed(double value, SpeedUnit unit)
        : base(unit == SpeedUnit.MeterPerSecond ? value : value / 3.6)
    {
    }

    public static Speed FromKilometerPerHour(double value)
    {
        return new Speed(value, SpeedUnit.KilometerPerHour);
    }
    public static Speed FromMeterPerSecond(double value)
    {
        return new Speed(value, SpeedUnit.MeterPerSecond);
    }

    public double KilometerPerHour
    {
        get
        {
            return this.SIValue * 3.6;
        }
    }

    public double MeterPerSecond
    {
        get
        {
            return this.SIValue;
        }
    }
}

A little extension to make life easier

public static Speed KilometersPerHour(this int speed)
{
    return Speed.FromKilometerPerHour(speed);
}

The tests

[TestMethod]
    public void Test_lt()
    {
        var speed1 = 36.KilometersPerHour();
        var speed2 = 37.KilometersPerHour();
        Assert.IsTrue(speed1 < speed2);
    }

    [TestMethod]
    public void Test_lte()
    {
        var speed1 = 36.KilometersPerHour();
        var speed2 = 37.KilometersPerHour();
        Assert.IsTrue(speed1 <= speed2);
    }
    [TestMethod]
    public void Test_lte_e()
    {
        var speed1 = 36.KilometersPerHour();
        var speed2 = 36.KilometersPerHour();
        Assert.IsTrue(speed1 <= speed2);
    }
    [TestMethod]
    public void Test_gt()
    {
        var speed1 = 37.KilometersPerHour();
        var speed2 = 36.KilometersPerHour();
        Assert.IsTrue(speed1 > speed2);
    }

    [TestMethod]
    public void Test_gte()
    {
        var speed1 = 37.KilometersPerHour();
        var speed2 = 36.KilometersPerHour();
        Assert.IsTrue(speed1 >= speed2);
    }
    [TestMethod]
    public void Test_Add()
    {
        Assert.AreEqual(5.KilometersPerHour(), 3.KilometersPerHour() + 2.KilometersPerHour());
    }

I have do the sames with the physicals value angle, force, acceleration, etc... So I can call my safe typed method like that :

Speed GetSpeed(Acceleration acceleration, Angle angle, Force resistance)
{
    // foo algorihtm for prouving the point
    var speed = Speed.FromMeterPerSeconds(acceleration.MeterPerSecondsMinus2 * angle.Degrees + resistance.Newton); 
    return speed;
}

Now, the questions :

First Point :

I have defined the SIQuantity as a generic type, so my abstract class contains all the logic for adding, substracting, compare the value, and return the good type. This way, it looks like a hack, since I have to define my generic type to extend an interface I have called ISIQuantity to access the SIValue. This looks weird.

Second Point : The mechanism to add two values is typically 'take two, unwrap, add, wrap, return'.

public static T operator +(SIQuantity<T> r1, SIQuantity<T> r2)
{
    var v1 = r1.SIValue; // unwrap
    var v2 = r2.SIValue;
    var sum = v1 + v2; // add
    var wrap = r1.CreateFromSI(sum); // wrap
    return wrap; // return
}

I find it ugly.
The first thing is that each of the based class must define the method CreateFromSI. I tried to find a way to do it all in the abstract class, but since I can't define a constructor or something like that in the abstract class, I have used the kind of factory pattern to build the object.
Can I do it in a better way?

In a more general way, have I reinvented the wheel? I search a lot on something similar already existing, but I find nothing.
I use this code a lot, lot, and lot. It makes my life sooooo much easier. I don't fight anymore, all my algorithm is type safe, I'm confident on the unit of the physical values I use, I unwrap the values when it's time to really use them, like this little method :

public static Power operator *(Force force, Speed speed)
{
    return Power.FromWatt(force.Newton * speed.MeterPerSecond);
}
[TestMethod]
public void TestMethod1()
{
    // 10000W = 1000N * 10m/s
    Assert.AreEqual(Power.FromKilowatt(10), Force.FromDecanewton(100) * 36.KilometersPerHour());
}

I use it so often that I must be sure it is perfect. So I definitely need a code review!

\$\endgroup\$
  • 3
    \$\begingroup\$ Have you looked at F#? It deals with things like units as well as values, and is ideal for physics type applications? \$\endgroup\$ – Jeff Vanzella Apr 2 '13 at 16:09
  • 1
    \$\begingroup\$ What stops you from mixing up the units? For example, it seems incorrect code like Speed.FromMeterPerSecond(force.Newton * speed.MeterPerSecond) would work. I think it's not terrible to allow something like this, but it should not be the normal way you do calculations, if you're striving for compiler-enforced safety. \$\endgroup\$ – svick Apr 2 '13 at 17:59
2
\$\begingroup\$

The main issue in your code is that you don't control proper combination of units in operations, e.g. you allow summing up speeds with kilograms.

I would rather create a class (or maybe better a struct?) that is aware of unit types as well, and create a number of static/extension methods/operator overloads to convert the value from different units...

Example out of my head (not tested since don't have VS at hand):

public struct Unit
{
    public sbyte Meters {get; private set}
    public sbyte Kilograms {get; private set}
    public sbyte Seconds {get; private set}
    public sbyte Amperes {get; private set}
    public sbyte Kelvins {get; private set}
    public sbyte Candelas {get; private set}
    public sbyte Moles {get; private set}

    public Unit(sbyte meters = 0, sbyte kilograms = 0, sbyte seconds = 0,
        sbyte amperes = 0, sbyte kelvins = 0, sbyte candelas = 0, sbyte moles = 0)
    {
        Meters = meters;
        Kilograms = kilograms;
        Seconds = seconds;
        Amperes = amperes;
        Kelvins = kelvins;
        Candelas = candelas;
        Moles = moles;
    }

    //TODO: add operators that combine different units by adding corresponding units. It can be implemented via sbyte[] but I find it more readable if implemented as separate properties. Since it has to be implemented once in this struct it should not be a big deal.

    //TODO: you can override ToString and output readable derived SI units like hertz/newton/joule/pascal instead of combination of base units...

    //TODO: Add static methods to define common base/derived units, e.g.:
    public static Unit Speed()
    {
        return new Unit(meters = 1, seconds = -1);
    }

    public static Unit Newton()
    {
        return new Unit(kilograms = 1, meters = 1, seconds = -2);
    }
}

public struct UnitValue
{
    public double Value {get; private set}
    public Unit Unit {get; private set}

    public UnitValue(double value, Unit unit)
    {
        Value = value;
        Unit = unit;
    }

    //TODO: overload operators so that they allow summing up unit values with equal units only, and "sum" units in case of multiplication.

    //TODO: add static methods to define unit values from non-standard units, e.g.:
    public static UnitValue FromKilometersPerHour(double value)
    {
        return new UnitValue(value / 3.6, Unit.Speed());
    }
}
\$\endgroup\$
  • \$\begingroup\$ Surely you're failing to take into account the type constraint when you say "e.g. you allow summing up speeds with kilograms"? Personally I'd make the f-bound explicit, but as long as in practice the subclasses use themselves as their type arguments the only way to add a speed to a mass is with explicit use of the SIValue property and a constructor. \$\endgroup\$ – Peter Taylor Apr 4 '13 at 12:58
0
\$\begingroup\$

This seems overly complicated. You should only use one unit system in all your code. If some input values use some different units, convert them as soon as you read them. (I assume those input values are taken from some outside source; if there is no input from an outside source, you should keep everything in the same unit system to start with.)

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.