Wednesday, May 27, 2009

Rounding, Mid-points and the U.S. Currency

While working on a credit card processing application, I had the following question:

When it comes to mid-point rounding, what is the standard that should be used in U.S. monetary transactions?

By default, .Net uses the “To Even” standard – where the digit at the rounding position is converted to the next higher even number if it is an odd number. For eg:

3.555 rounded to 2 decimal places = 3.56
3.545 rounded to 2 decimal places = 3.54

But .Net also provides an overloaded Round method, which allows you to specify another method of midpoint rounding – “Away from Zero”. Using away from zero, the above examples result in:

3.555 rounded to 2 decimal places = 3.56
3.545 rounded to 2 decimal places = 3.55

So, for financial transactions which one do you use?

MSDN has this to say about the 2 types of rounding:

  • MidpointRounding.ToEven. If the digit in the decimals position is odd, it is changed to an even digit. Otherwise, it is left unchanged. This behavior follows IEEE Standard 754, section 4. It is sometimes called rounding to nearest, or banker's rounding. It minimizes rounding errors that result from consistently rounding a midpoint value in a single direction.

  • MidpointRounding.AwayFromZero. The digit in the decimals position is always rounded up to the next digit. This is the most commonly known rounding method. It is known as symmetric arithmetic rounding.

    It is tempting to use the “To Even” method because it is called “bankers rounding”, but not so fast! A quick Google for Banker’s rounding, turn up the fact that the term banker’s rounding is not a standard (not at least one that is used by the banker type). The only people that seem to use it frequently are the computer type.

    The reason that the “To Even” rounding type is used often is that over a large number of transactions, as it would have rounded some numbers down and other up, the sum of all the rounding shouldn’t be biased in any one direction. But if you used the “Away from zero”, then over a large number of transactions, you would end up with a sum that is biased away from zero (hence the name). This is demonstrated by the following code, which exaggerates the problem by using a number with an even digit in the rounding location (causing ToEven to do nothing, but AwayFromZero rounds up each time).

    static void TestRounding(decimal startNum, int numDecimals, int iter, decimal add)
        {
            decimal currentNum = startNum;
            
            decimal sumTE = 0;
            decimal sumAZ = 0;
            
            for (int i = 0; i < iter; i++)
            {
                sumAZ += Math.Round(currentNum, numDecimals, MidpointRounding.AwayFromZero);
                sumTE += Math.Round(currentNum, numDecimals, MidpointRounding.ToEven);
                currentNum += add;
            }
            Console.WriteLine("Sum using away from zero:" + sumAZ);
            Console.WriteLine("Sum using to even:" + sumTE);
            Console.WriteLine("Difference: " + (sumAZ-sumTE));
        }

    The results of running the code with the following arguments TestRounding(0.045M,2,10000, 1.00M) is:

    Sum using away from zero:49995500.00
    Sum using to even:49995400.00
    Difference: 100.00

    As you can see – in just 10,000 iterations, the difference between the 2 types of rounding got up to a 100. If you had the ability to divert the difference into a personal account – then the choice is not difficult!

    But then, what is the standard?

    For the EU, as seen from the following document “Converting rates and rules”, the standard is to use the “Away from zero” rounding:

    Once the conversion from the national currency has been made, then the euro amount can be rounded up or down to the nearest euro cent: if the number in the third decimal place is less than 5, the second decimal remains unchanged (for example, €1.264 becomes €1.26); but if the third decimal is 5 or above, then the second decimal must be rounded up, for example €1.265 becomes €1.27.

    Article 5
    (pg 27 – COUNCIL REGULATION (EC)No 1103/97 (Introduction of the Euro))

    Monetary amounts to be paid or accounted for when a rounding takes place after a
    conversion into the euro unit pursuant to Article 4 shall be rounded up or down to the
    nearest cent
    . Monetary amounts to be paid or accounted for which are converted into a
    national currency unit shall be rounded up or down to the nearest sub-unit or in the
    absence of a sub-unit to the nearest unit, or according to national law or practice to a
    multiple or fraction of the sub-unit or unit of the national currency unit. If the application of the conversion rate gives a result which is exactly half-way, the sum shall be rounded up.


    The IRS too suggests using the “Away from zero” rounding when it comes to reporting amounts on tax returns (http://www.irs.gov/instructions/i5227/ch01.html & http://www.irs.gov/instructions/i7004/ch01.html):

    Rounding Off to Whole Dollars

    You may round off cents to whole dollars on your return and schedules. If you do round dollars, you must round all amounts. To round, drop amounts under 50 cents and increase amounts from 50 to 99 cents to the next dollar. For example, $1.39 becomes $1 and $2.50 becomes $3.

    If you have to add two or more amounts to figure the amount to enter on a line, include cents when adding the amounts and round off only the total.

    Apart from the above documents, I have not found any other documents that discuss the standard way in which rounding should be implemented in applications that work with monetary values.

    Conclusion:

    So what is the standard – there doesn't seem to be any! But I would use the Away from zero because that is what the EU and IRS seem to be using. Caveat – realize this makes sense only when you are rounding to the nearest sub-unit (cents in the case of dollars). Do not use Away from Zero, if you are rounding up to the nearest whole dollar, unless it makes sense to do so in your case or you have explicit requirements that say that you need to do that.

    running the code sample with the arguments “TestRounding(2.5M,0,10000, 1.00M);” results in a difference of 5000!

    BTW:

    If you are working with values that represent money – remember that doubles can introduce their own set of problems and for the purpose of accuracy you should always use the decimal data type. (read more about this at Quick what is the output of (0.1 + 0.2) == 0.3)

    Other resources:

    .Net Midpoint rounding
    http://msdn.microsoft.com/en-us/library/system.midpointrounding.aspx

    IEEE 754 (Rounding Algorithms)
    http://en.wikipedia.org/wiki/IEEE_754#Rounding_algorithms

    Guidelines for the conversion of past data related to the Euro
    http://www.euro.gov.mt/pdf/guideline_en_02.pdf

  • 1 comment:

    joel correia said...

    Thanks for the post! i was using the .net rdlc reports and the default round type. Now i have changed.