Welcome to MSDN Blogs Sign in | Join | Help

Syndication

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:

image

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.

Published Thursday, April 10, 2008 3:22 PM by hoongfai

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# Rounding mechanism exercise, how to mitigate the impact on ISV solutions? : Casino News @ Thursday, April 10, 2008 4:05 AM

PingBack from http://newips.info/?p=2218

Rounding mechanism exercise, how to mitigate the impact on ISV solutions? : Casino News

# re: Rounding mechanism exercise, how to mitigate the impact on ISV solutions? @ Friday, April 11, 2008 4:14 AM

Cool! This blog entry was filed under Casino News! ha ha. I wonder is it because I mentioned Crown Casino.

hoongfai

# re: Rounding mechanism exercise, how to mitigate the impact on ISV solutions? @ Sunday, May 04, 2008 12:49 AM

Hi Hong Fai,

There are better alternative, pass in the value 1234.23 and round digit as 2 will solve your problem. This algorithm make user definable rounding possible. In fact, i just show you one of my rounding mechanism in my application, which all rounding mechanism in my application are user definable.

User must always use decimal in term of dollar and cent calculation. Even though decimal is not primitive type, but it handles the calculation more appropriate compare to the others. This is due to how .net handle primitive type figures.

public static decimal RoundAmount(decimal roundValue, decimal roundDigit)

{

decimal totalValue = 0;

decimal powerValue = 0;

powerValue = (decimal)(System.Math.Pow(10, (double)roundDigit));

decimal tempDecimalValue = 5 / powerValue;

decimal rndFigure = roundValue / tempDecimalValue;

totalValue =(decimal)(System.Math.Round(rndFigure, 0)) * tempDecimalValue;

return totalValue;

}

gohsianghwee

# A better alternative to solving the rounding mechanism in your apps @ Tuesday, June 10, 2008 11:45 PM

Awhile back, I wrote a blog post on the rounding mechanism , little did I know that a reader posted a

Say what? Microsoft Malaysia One ISV Technology Blog

Leave a Comment

(required) 
required 
(required) 

  
Enter Code Here: Required
© 2009 Microsoft Corporation. All rights reserved. Terms of Use  |  Trademarks  |  Privacy Statement  
Page view tracker