Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"For money, always decimal"?

Well, the rule "For money, always decimal" isn't applied inside the Microsoft development team, because if it was:

Namespace: Microsoft.VisualBasic
Assembly:  Microsoft.VisualBasic (in Microsoft.VisualBasic.dll)

Financial.IPmt and all the other methods would receive/return decimal and not double as it is.

Now I wonder if I can use these methods without worry with round mistakes?

Should I use some other libraries to work with finances? If yes, could you point me some good ones (for C# use) ?

like image 856
Dryadwoods Avatar asked Apr 08 '10 22:04

Dryadwoods


3 Answers

You can use this class:

public class Financial
{
    #region Methods

    public static decimal IPmt(decimal Rate, decimal Per, decimal NPer, decimal PV, decimal FV, FinancialEnumDueDate Due)
    {
        decimal num;
        if (Due != FinancialEnumDueDate.EndOfPeriod)
        {
            num = 2;
        }
        else
        {
            num = 1;
        }
        if ((Per <= 0) || (Per >= (NPer + 1)))
        {
            //Argument_InvalidValue1=

            throw new ArgumentException("Argument 'Per' is not a valid value.");
        }
        if ((Due != FinancialEnumDueDate.EndOfPeriod) && (Per == 1))
        {
            return 0;
        }
        decimal pmt = Pmt(Rate, NPer, PV, FV, Due);
        if (Due != FinancialEnumDueDate.EndOfPeriod)
        {
            PV += pmt;
        }
        return (FV_Internal(Rate, Per - num, pmt, PV, FinancialEnumDueDate.EndOfPeriod) * Rate);
    }

    public static decimal PPmt(decimal Rate, decimal Per, decimal NPer, decimal PV, decimal FV, FinancialEnumDueDate Due)
    {
        if ((Per <= 0) || (Per >= (NPer + 1)))
        {
            throw new ArgumentException("Argument 'Per' is not valid.");
        }
        decimal num2 = Pmt(Rate, NPer, PV, FV, Due);
        decimal num = IPmt(Rate, Per, NPer, PV, FV, Due);
        return (num2 - num);
    }

    static decimal FV_Internal(decimal Rate, decimal NPer, decimal Pmt, decimal PV, FinancialEnumDueDate Due)
    {
        decimal num;
        if (Rate == 0)
        {
            return (-PV - (Pmt * NPer));
        }
        if (Due != FinancialEnumDueDate.EndOfPeriod)
        {
            num = 1 + Rate;
        }
        else
        {
            num = 1;
        }
        decimal x = 1 + Rate;
        decimal num2 = (decimal)Math.Pow((double)x, (double)NPer);
        return ((-PV * num2) - (((Pmt / Rate) * num) * (num2 - 1)));
    }

    static decimal Pmt(decimal Rate, decimal NPer, decimal PV, decimal FV, FinancialEnumDueDate Due)
    {
        decimal num;
        if (NPer == 0)
        {
            throw new ArgumentException("Argument NPer is not a valid value.");
        }
        if (Rate == 0)
        {
            return ((-FV - PV) / NPer);
        }
        if (Due != FinancialEnumDueDate.EndOfPeriod)
        {
            num = 1 + Rate;
        }
        else
        {
            num = 1;
        }
        decimal x = Rate + 1;
        decimal num2 = (decimal)Math.Pow((double)x, (double)NPer);
        return (((-FV - (PV * num2)) / (num * (num2 - 1))) * Rate);
    }

    #endregion Methods
}
like image 163
Survival Avatar answered Sep 25 '22 00:09

Survival


The rule to use decimal for money is helpful because most currencies have decimal units. By using decimal arithmetic, you avoid introducing and accumulating round-off error.

Financial Class functions use floating-point for a few reasons:

  • They don't internally accumulate -- they are based on a closed-form exponential/logarithmic computation, not iteration and summation over periods.
  • They tend not to use or produce exact decimal values. For example, an exact decimal annual interest rate divided by 12 monthly payments becomes a repeating decimal.
  • They are intended primarily for decision support, and in the end have little applicability to actual bookkeeping.

Pmt and rounding may determine the nominal monthly payment, but once that amount is determined, balance accumulation -- payments made, interest charges applied, etc. -- happens in decimal. Also, late or advance payments, payment holidays, and other such non-uniformities would invalidate the projected amortization provided by the financial functions.

like image 45
Jeffrey Hantin Avatar answered Sep 26 '22 00:09

Jeffrey Hantin


Here's an interesting discussion regarding exactly this topic: http://www.vbforums.com/showthread.php?t=524101

About 1/3 of the way down someone explains that it uses Double because the VB.NET functions were implemented to work exactly the same as VB6. VB6 doesn't have a decimal type, which is why it uses double.

So, it seems that if accuracy is important, you should not use these functions.

The answers to this question have some promising alternatives - just ignore the accepted answer that suggests using the VB library.

The previously linked question has been deleted, so here are some of the suggestions I was referencing (note: I have not tried these, YMMV)

  • Implementations of excel financial functions in F# - can be used from any .NET language, does not require F# reference

  • QuantLib may be able to be exported/generated for C#. See this SO question/answer for more info

like image 21
Daniel Schaffer Avatar answered Sep 22 '22 00:09

Daniel Schaffer