Rounding mechanism exercise, how to mitigate the impact on ISV solutions?
In my previous blog post, I mentioned about BNM's rounding mechanism exercise in Malaysia. There are certain implications on any Malaysian ISV solutions which process over-the-counter (OTC) payment transactions, settled in cash or non-cash. These type of transactions will be subjected to the Rounding Mechanism. However all online payments, such as payments via Internet, are NOT subjected to the Rounding Mechanism, e.g., paying utility bills via Internet. The rounding mechanism only applied to the total amount of a bill and not on individual items.
The rounding exercise only applies to business applications that process OTC transactions, such as POS terminal. However I reckon that this may also affect your line-of-business (LOB) applications, and I could see that the Microsoft Dynamics line of products like CRM, Navision, AX already allows rounding to be configured within itself.
For your custom-developed solution, you would have to make changes in your application/code to accommodate to rounding of currency. Essentially only the total amount needs to be rounded up or down. If you are making code changes, only class members such as a TotalAmount property needs to be rounded off. If you don't have too many of such properties lying around, this is only involves minimal effort. However if you have such properties appearing all over your code, it may be wise to create your own custom class that represents the TotalAmount. You should use System.Decimal structure to represent the amount. The following code example performs the rounding mechanism but only for positive values. As a recap, this is how the rounding mechanism works in Malaysia:
namespace RoundingMechanism
{
/// <summary>
/// Keeping my total amount in Decimals to allow the rounding mechanism.
/// This class only rounds positive values.
/// </summary>
class RoundedAmount
{
const decimal lowDown = 0.02M;
const decimal highDown = 0.07M;
const decimal fiveCents = 0.05M;
const decimal lowUp = 0.04M;
const string dataFmt = "{0,43} {1} = {2}";
protected decimal Amount;
public decimal Value
{
get
{
return Amount;
}
set
{
decimal cents = value % Decimal.Round(value, 1, MidpointRounding.ToEven);
if (cents > 0.09M)
{
decimal newcents = Decimal.Round(value, 1, MidpointRounding.ToEven) - value;
if (newcents == 0.05M)
Amount = value;
else if (newcents <= 0.02M)
Amount = Decimal.Round(value, 1, MidpointRounding.ToEven);
else
Amount = Decimal.Round(value, 1, MidpointRounding.ToEven) - 0.05M;
}
else
{
if (cents <= lowDown)
Amount = Decimal.Round(value, 1, MidpointRounding.ToEven);
else
Amount = Decimal.Round(value, 1, MidpointRounding.ToEven) + 0.05M;
}
}
}
public override string ToString()
{
return "RM" + Amount.ToString("C");
}
static void Main()
{
RoundedAmount amount = new RoundedAmount();
Console.WriteLine(
"This example demonstrates the rounding mechanism exercise.\n");
Console.WriteLine(dataFmt,
"Amount: Rounding ( 123.31 )", amount.Value = 123.31M, amount.ToString());
Console.WriteLine(dataFmt,
"Amount: Rounding ( 123.32 )", amount.Value = 123.32M, amount.ToString());
Console.WriteLine(dataFmt,
"Amount: Rounding ( 123.33 )", amount.Value = 123.33M, amount.ToString());
Console.WriteLine(dataFmt,
"Amount: Rounding ( 123.34 )", amount.Value = 123.34M, amount.ToString());
Console.WriteLine(dataFmt,
"Amount: Rounding ( 123.35 )", amount.Value = 123.35M, amount.ToString());
Console.WriteLine(dataFmt,
"Amount: Rounding ( 123.36 )", amount.Value = 123.36M, amount.ToString());
Console.WriteLine(dataFmt,
"Amount: Rounding ( 123.37 )", amount.Value = 123.37M, amount.ToString());
Console.WriteLine(dataFmt,
"Amount: Rounding ( 123.38 )", amount.Value = 123.38M, amount.ToString());
Console.WriteLine(dataFmt,
"Amount: Rounding ( 123.39 )", amount.Value = 123.39M, amount.ToString());
Console.WriteLine(dataFmt,
"Amount: Rounding ( 123.70 )", amount.Value = 123.70M, amount.ToString());
Console.ReadLine();
}
}
}
While working on this sample application, I discovered a weird behavior of Decimal.Round(Decimal, Int32, MidpointRounding). System.MidpointRounding.ToEven is supposed to round 3.45 to 3.4, as shown in this code example. However, when I ran my application, rounding 3.45 returns a result of 3.5. If anyone knows why it is behaving as such, please leave a comment on this blog post. I did a quick hack and ended up with another block of if-else statement to handle decimals that are from 0.05 to 0.09.
Another option is to do this at SQL Server level, I saw this mentioned in this article: http://support.microsoft.com/kb/196652
Another website suggested using your own custom/private APIs for doing rounding of currency, not just specifically for Malaysia's case, but for any countries that have introduced rounding mechanism. http://www.xencraft.com/resources/multi-currency.html. But this would incur additional API method invocations.
On a side note, I could recall a case of bank fraud during my days as a student in Melbourne, Australia (not committed by me of course). The fraud was a result of an illegal opportunity to capitalize on the rounding exercise in Australia. The "opportunist" worked as a developer in a bank in Australia. He inserted an "easter egg" (except that it is not for fun, but for illegal gains) into the application whereby the amount which were rounded off (the amount would just be in terms of a couple of cents), would be collected and deposited into his own bank account. The fraud went undetected for years. The fraud was discovered when he gambled away and lost hundreds of thousands of dollars at Crown Casino in Melbourne, and that pretty much raised the alarm. I sure hope that Malaysian developers are a lot more ethical than that.