Thursday 17 January 2013

Hello? Operator?

Whilst experimenting with some new ideas for a calculation engine I have been having a challenging time, challenging in that I want to challenge the normal way that I would code up a piece of work. Whilst what you learn is that often you don’t need to challenge your normal working practice it is good to stretch the mind to think in a different way. Just such an incident occurred today and I felt compelled to share my experiences.

Telephone_operatorThe calculation engine in question is an engine to calculate the costs associated with buying and selling shares and as such we are immediately entrenched in a world where the pennies really matter. Share prices, certainly within the UK, are most often quoted in pence. The intended recipient however (that is if they are lucky with the stock markets today!) are not really that interested in how many pennies they have made, they are interested primarily in the pounds; sure, they want to know the small change but it’s the pounds that really interest them. Thus the calculation engine sits between two stools, it needs to talk about pennies when thinking about the share and pounds when reporting the actual amounts of revenue generated.

As an application developer, with a legacy product, this has resulted in the purchase information being stored in pennies and the costs associated with the deal being persisted in pounds.  This is of course not a problem although it does lead to lots of little pieces of code dotted all around the engine saying

x = y/100

or

x = y * 100

where y is a price in pounds or pennies being converted to pennies for a calculation… There is of course nothing wrong with this per se but I just got to thinking, there has to be a better way. I prefer code to not look like maths if it doesn’t have to. It turns out there is a neat solution and that part of the solution has been a feature of .NET since forever, a feature often overlooked!

The Thought Process

The obvious way to solve the issue would be to have a class object to store the Share Price, by default it would assume it would be in pennies as this is the normal way of working but if you wished provide a property to express it in pounds. You’ve just got rid of all of your y/100 expressions in place of something far more explicit, you also are utilising the same object for storage so no spurious bugs caused by forgetting to assign the two values together. The optimum solution be to override double but as this is a sealed class that’s not going to happen.

I could use extension methods of course and just add a new method, in a way this is a lot less code but I’m not particularly a fan of them. Some people say they are overused and shouldn’t be, I can just never remember which namespace they are in and so I end up trawling through the project looking for the original code just so that I can insert the appropriate include at the top of my class. I’d much prefer VS to recognise the Object is not available draw a squiggly and let me resolve it. Lay huh …

Instead I created a SharePrice Object which has two properties,


public class SharePrice{


public decimal ValueInPence { get; set; }

public decimal ValueInPounds {

get {return ValueInPence/100;}

set {ValueInPence = value * 100;}

}

}

This does for a start, now all of my logic is already there for any prices i wish to implement through the whole system. However, I want to be able to treat my class just like a decimal; I want to be able to say something like

SaleProceeds = SalePrice * Shares

I also want to be able to compare it and use it in calculations just like any other decimal can be.  This is not possible with the class as it stands. The missing element is of course operators! Now I know what you’re going to say, that’s hardly new is it, they have been in .NET pretty much since inception…. I know, and to my shame I’ve never used them, it never crossed my mind before. OK so first off I need some methods to allow me to assign directly to variables of the type SharePrice. I’d like to be able to write code like the following,  assuming that we are dealing with the price in pennies.

SaleProceeds = SalePrice * Shares

SalePrice = NormalPrice * Discount


So I create the following two static methods that allow me to directly assign to and from the SharePrice variable type

public static implicit operator SharePrice(decimal d) {


return new SharePrice() { ValueInPence = d };

}

public static explicit operator decimal(SharePrice price) {


return price.ValueInPence;

}

So far so good, however for mathematical equations still useless as I can only assign to this class, I cannot use it with any of the standard mathematical symbols. I’d like it to be able to used with all the usual symbols +, -, *,/ ; in addition I’d quite like the SharePrice variable to be compatible with itself and decimals, that is, I’d like to be able to say any of the statements below 

SharePrice a =5.5;

SharePrice b=6.7

Decimal c=9.9

Decimal Result=0;

Result = a * b;

Result=a * c;

Result = c * b;

So this means writing 3 methods for each operator type that I wish to support, as an example here is how I would  code up the multiply operator methods:-

public static decimal operator *(SharePrice d1, decimal d2) {


return d1.ValueInPence * d2;

}

public static decimal operator *(decimal d1, SharePrice d2) {


return d1 * d2.ValueInPence;

}

public static decimal operator *(SharePrice d1, SharePrice d2) {


return d1.ValueInPence * d2.ValueInPence;

}

Note that the methods all return a decimal and take the three configurations needed to support the calculations I wished to support.
  • Decimal * SharePrice
  • SharePrice * Decimal
  • SharePrice * SharePrice
The code within the methods themselves just performs the conversions required.

So I factor the entire class thus:-

public class SharePrice{


public decimal ValueInPence { get; set; }

public decimal ValueInPounds {

get {return ValueInPence/100;}

set {ValueInPence = value * 100;}

}

#region Shortcut Operators

public static implicit operator SharePrice(decimal d) {

return new SharePrice() { ValueInPence = d };

}

public static explicit operator decimal(SharePrice price) {

return price.ValueInPence;

}

#endregion

#region Less than Operator Comparisons

public static bool operator < (SharePrice d1, decimal d2) {

return d1.ValueInPence < d2;

}

public static bool operator <(decimal d1, SharePrice d2) {

return d1< d2.ValueInPence;

}

public static bool operator <(SharePrice d1, SharePrice d2) {

return d1.ValueInPence < d2.ValueInPence;

}

#endregion

#region Less than or Equal to Operator Comparisons

public static bool operator <=(SharePrice d1, decimal d2) {

return d1.ValueInPence <= d2;

}

public static bool operator <=(decimal d1, SharePrice d2) {

return d1 <= d2.ValueInPence;

}

public static bool operator <=(SharePrice d1, SharePrice d2) {

return d1.ValueInPence <= d2.ValueInPence;
}

#endregion

#region Greater than Operator Comparisons

public static bool operator >(SharePrice d1, decimal d2) {

return d1.ValueInPence > d2;

}

public static bool operator >(decimal d1, SharePrice d2) {

return d1 > d2.ValueInPence;

}

public static bool operator >(SharePrice d1, SharePrice d2) {

return d1.ValueInPence > d2.ValueInPence;

}

#endregion

#region Greater than or equal to Operator Comparisons

public static bool operator >=(SharePrice d1, decimal d2) {

return d1.ValueInPence >= d2;

}

public static bool operator >=(decimal d1, SharePrice d2) {

return d1 >= d2.ValueInPence;

}

public static bool operator >=(SharePrice d1, SharePrice d2) {

return d1.ValueInPence >= d2.ValueInPence;

}

#endregion

#region Multiply By Operator

public static decimal operator *(SharePrice d1, decimal d2) {

return d1.ValueInPence * d2;

}

public static decimal operator *(decimal d1, SharePrice d2) {

return d1 * d2.ValueInPence;

}

public static decimal operator *(SharePrice d1, SharePrice d2) {

return d1.ValueInPence * d2.ValueInPence;

}

#endregion

#region Divide By Operator

public static decimal operator /(SharePrice d1, decimal d2) {

return d1.ValueInPence / d2;

}

public static decimal operator /(decimal d1, SharePrice d2) {

return d1 / d2.ValueInPence;

}

public static decimal operator /(SharePrice d1, SharePrice d2) {

return d1.ValueInPence / d2.ValueInPence;

}

#endregion

#region Addition Operator

public static decimal operator +(SharePrice d1, decimal d2) {

return d1.ValueInPence + d2;

}
public static decimal operator +(decimal d1, SharePrice d2) {

return d1 + d2.ValueInPence;

}

public static decimal operator +(SharePrice d1, SharePrice d2) {

return d1.ValueInPence + d2.ValueInPence;

}

#endregion

}

I now have a class that behaves as I wish, I could also write a class the reverse of this which deals primarily with prices expressed in pounds but where a conversion to pennies is needed. For my purposes, at present this is not so useful but I will keep it in mind. My code is instantly more readable and readability in a complex application is crucial, in addition I will be mindful of this rather simplistic example when working on more complex scenarios in the future, in fact….. I have an idea right now!

Written by Conrad Rowlands, Senior Software Developer, DSCallards. 

For more information, visit www.dscallards.com.    

 


  

No comments:

Post a Comment