amuz.es/src/go/misc@v1.0.1/monetary/installment.go (about)

     1  package monetary
     2  
     3  import (
     4  	"github.com/ericlagergren/decimal"
     5  	"math/big"
     6  )
     7  
     8  // MonthlyInstallmentInterestAmount returns monthly interest amount from annual interest ratio.
     9  func MonthlyInstallmentInterestAmount(balance *decimal.Big, annualInterest *big.Rat) (fee *decimal.Big) {
    10  
    11  	var (
    12  		monthly big.Rat
    13  		feeFrac big.Rat
    14  	)
    15  	monthly.SetFrac64(1, 12)
    16  	balance.Rat(&feeFrac)
    17  	feeFrac.Mul(&feeFrac, annualInterest)
    18  	feeFrac.Mul(&feeFrac, &monthly)
    19  	fee = decimal.New(0, 0)
    20  	fee.Context.RoundingMode = decimal.ToZero
    21  	fee.SetRat(&feeFrac)
    22  	fee.Quantize(0)
    23  	fee.Reduce()
    24  	return fee
    25  }
    26  
    27  // MonthlyInstallmentInfo returns monthly repayment plan.
    28  func MonthlyInstallmentInfo(totalBalance *decimal.Big, modDigit, division int) (installment, installmentFractional, installmentExtra *decimal.Big) {
    29  	var (
    30  		monthlyFee   big.Rat
    31  		frac         big.Rat
    32  		divisionDeci decimal.Big
    33  	)
    34  
    35  	divisionDeci.SetMantScale(int64(division), 0)
    36  	totalBalance.Rat(&monthlyFee)
    37  	frac.SetFrac64(1, int64(division))
    38  	monthlyFee.Mul(&monthlyFee, &frac)
    39  
    40  	installment = decimal.New(0, 0)
    41  	installment.Context.RoundingMode = decimal.ToZero
    42  	installment.SetRat(&monthlyFee)
    43  	installment.Quantize(-modDigit)
    44  	installment.Reduce()
    45  
    46  	installmentFractional = decimal.New(0, 0)
    47  	installmentFractional.Mul(installment, &divisionDeci)
    48  	installmentFractional.Sub(totalBalance, installmentFractional)
    49  	installmentFractional.Reduce()
    50  
    51  	installmentExtra = decimal.New(0, 0)
    52  	installmentExtra.Add(installment, installmentFractional)
    53  	installmentExtra.Reduce()
    54  
    55  	installmentTest := decimal.New(0, 0)
    56  	installmentTest.Mul(installment, &divisionDeci)
    57  	installmentTest.Add(installmentTest, installmentFractional)
    58  	return
    59  }
    60  
    61  // MonthlyInstallmentSchedule returns detailed monthly repayment plan
    62  func MonthlyInstallmentSchedule(
    63  	totalBalance *decimal.Big,
    64  	annualInterest *decimal.Big,
    65  	modDigit, division int,
    66  	payExtraAmountEarlier bool) (
    67  	installment, installmentExtra, totalInterests *decimal.Big,
    68  	principleBalanceBeforePayments,
    69  	principleBalanceAfterPayments,
    70  	installments,
    71  	interests,
    72  	schedules []*decimal.Big,
    73  ) {
    74  	if division < 1 {
    75  		return
    76  	} else if totalBalance == nil {
    77  		return
    78  	}
    79  	var (
    80  		leftBalance        decimal.Big
    81  		annualInterestFrac big.Rat
    82  	)
    83  
    84  	if annualInterest != nil {
    85  		annualInterest.Rat(&annualInterestFrac)
    86  	}
    87  
    88  	totalInterests = decimal.New(0, 0)
    89  	leftBalance.Copy(totalBalance)
    90  	installmentAmount, _, installmentAmountExtra := MonthlyInstallmentInfo(totalBalance, modDigit, division)
    91  	principleBalanceBeforePayments, principleBalanceAfterPayments, installments, interests, schedules = make([]*decimal.Big, 0, division), make([]*decimal.Big, 0, division), make([]*decimal.Big, 0, division), make([]*decimal.Big, 0, division), make([]*decimal.Big, 0, division)
    92  	for i := 0; i < division; i++ {
    93  		var (
    94  			interestAmount                 = MonthlyInstallmentInterestAmount(&leftBalance, &annualInterestFrac)
    95  			monthlyInstallmentWithInterest decimal.Big
    96  			principleBalanceBeforePayment  decimal.Big
    97  			principleBalanceAfterPayment   decimal.Big
    98  			paymentAmount                  *decimal.Big
    99  		)
   100  		if payExtraAmountEarlier {
   101  			if i == 0 {
   102  				paymentAmount = installmentAmountExtra
   103  			} else {
   104  				paymentAmount = installmentAmount
   105  			}
   106  		} else {
   107  			if i+1 == division {
   108  				paymentAmount = installmentAmountExtra
   109  			} else {
   110  				paymentAmount = installmentAmount
   111  			}
   112  		}
   113  		installments = append(installments, paymentAmount)
   114  		interests = append(interests, interestAmount)
   115  		totalInterests.Add(totalInterests, interestAmount)
   116  		principleBalanceBeforePayment.Copy(&leftBalance)
   117  		principleBalanceBeforePayments = append(principleBalanceBeforePayments, &principleBalanceBeforePayment)
   118  		monthlyInstallmentWithInterest.Add(paymentAmount, interestAmount)
   119  		leftBalance.Sub(&leftBalance, paymentAmount)
   120  		principleBalanceAfterPayment.Copy(&leftBalance)
   121  		principleBalanceAfterPayments = append(principleBalanceAfterPayments, &principleBalanceAfterPayment)
   122  		schedules = append(schedules, &monthlyInstallmentWithInterest)
   123  	}
   124  	return
   125  }