"For money, always decimal"?
Asked Answered
S

3

10

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) ?

Semifinal answered 8/4, 2010 at 22:56 Comment(4)
My totally-not-a-VB-user guess would be that the Financial library probably handles storage correctly internally, but I wouldn't bet on it. You would want to check if internal handling is consistent or not before just condemning it on the basis of return type.Jefferey
My vote is for boolean - you either have money or you don't ;-)Overscrupulous
For money, always decimal is golden rule ? forever ?Harlequin
@Harlequin well, personally this is what I use based on all the things I have read in the past written by people theoretically with more knowledge in this matter than me.Semifinal
T
4

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
}
Townsend answered 20/7, 2010 at 13:21 Comment(2)
Hi there, what is the Per variable for?Greaves
@Greaves Please see the VB function for PPmt, which matches the PPmt function here (2nd function), which calls IPmt.Gopak
C
10

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)

Castalia answered 8/4, 2010 at 23:2 Comment(2)
VB6 does have a Currency type though, which is an Int64 with four implied decimal places.Arched
I decided to write my own finance methods with decimal values. Red Gate's.Net Reflector as a helpful tool :DSemifinal
A
10

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.

Arched answered 9/4, 2010 at 0:11 Comment(0)
T
4

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
}
Townsend answered 20/7, 2010 at 13:21 Comment(2)
Hi there, what is the Per variable for?Greaves
@Greaves Please see the VB function for PPmt, which matches the PPmt function here (2nd function), which calls IPmt.Gopak

© 2022 - 2024 — McMap. All rights reserved.