123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440 |
- /*
- Copyright (c) 2017 James Ahlborn
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
-
- package com.healthmarketscience.jackcess.impl.expr;
-
-
- import com.healthmarketscience.jackcess.expr.EvalContext;
- import com.healthmarketscience.jackcess.expr.Function;
- import com.healthmarketscience.jackcess.expr.Value;
- import static com.healthmarketscience.jackcess.impl.expr.DefaultFunctions.*;
-
- /**
- *
- * @author James Ahlborn
- */
- public class DefaultFinancialFunctions
- {
- /** 0 - payment end of month (default) */
- private static final int PMT_END_MNTH = 0;
- /** 1 - payment start of month */
- private static final int PMT_BEG_MNTH = 1;
-
-
- private DefaultFinancialFunctions() {}
-
- static void init() {
- // dummy method to ensure this class is loaded
- }
-
-
- public static final Function NPER = registerFunc(new FuncVar("NPer", 3, 5) {
- @Override
- protected Value evalVar(EvalContext ctx, Value[] params) {
- double rate = params[0].getAsDouble();
- double pmt = params[1].getAsDouble();
- double pv = params[2].getAsDouble();
-
- double fv = 0d;
- if(params.length > 3) {
- fv = params[3].getAsDouble();
- }
-
- int pmtType = PMT_END_MNTH;
- if(params.length > 4) {
- pmtType = params[4].getAsLongInt();
- }
-
- double result = calculateLoanPaymentPeriods(rate, pmt, pv, pmtType);
-
- if(fv != 0d) {
- result += calculateAnnuityPaymentPeriods(rate, pmt, fv, pmtType);
- }
-
- return BuiltinOperators.toValue(result);
- }
- });
-
- public static final Function FV = registerFunc(new FuncVar("FV", 3, 5) {
- @Override
- protected Value evalVar(EvalContext ctx, Value[] params) {
- double rate = params[0].getAsDouble();
- double nper = params[1].getAsDouble();
- double pmt = params[2].getAsDouble();
-
- double pv = 0d;
- if(params.length > 3) {
- pv = params[3].getAsDouble();
- }
-
- int pmtType = PMT_END_MNTH;
- if(params.length > 4) {
- pmtType = params[4].getAsLongInt();
- }
-
- if(pv != 0d) {
- nper -= calculateLoanPaymentPeriods(rate, pmt, pv, pmtType);
- }
-
- double result = calculateFutureValue(rate, nper, pmt, pmtType);
-
- return BuiltinOperators.toValue(result);
- }
- });
-
- public static final Function PV = registerFunc(new FuncVar("PV", 3, 5) {
- @Override
- protected Value evalVar(EvalContext ctx, Value[] params) {
- double rate = params[0].getAsDouble();
- double nper = params[1].getAsDouble();
- double pmt = params[2].getAsDouble();
-
- double fv = 0d;
- if(params.length > 3) {
- fv = params[3].getAsDouble();
- }
-
- int pmtType = PMT_END_MNTH;
- if(params.length > 4) {
- pmtType = params[4].getAsLongInt();
- }
-
- if(fv != 0d) {
- nper -= calculateAnnuityPaymentPeriods(rate, pmt, fv, pmtType);
- }
-
- double result = calculatePresentValue(rate, nper, pmt, pmtType);
-
- return BuiltinOperators.toValue(result);
- }
- });
-
- public static final Function PMT = registerFunc(new FuncVar("Pmt", 3, 5) {
- @Override
- protected Value evalVar(EvalContext ctx, Value[] params) {
- double rate = params[0].getAsDouble();
- double nper = params[1].getAsDouble();
- double pv = params[2].getAsDouble();
-
- double fv = 0d;
- if(params.length > 3) {
- fv = params[3].getAsDouble();
- }
-
- int pmtType = PMT_END_MNTH;
- if(params.length > 4) {
- pmtType = params[4].getAsLongInt();
- }
-
- double result = calculateLoanPayment(rate, nper, pv, pmtType);
-
- if(fv != 0d) {
- result += calculateAnnuityPayment(rate, nper, fv, pmtType);
- }
-
- return BuiltinOperators.toValue(result);
- }
- });
-
- // FIXME not working for all param combos
- // public static final Function IPMT = registerFunc(new FuncVar("IPmt", 4, 6) {
- // @Override
- // protected Value evalVar(EvalContext ctx, Value[] params) {
- // double rate = params[0].getAsDouble();
- // double per = params[1].getAsDouble();
- // double nper = params[2].getAsDouble();
- // double pv = params[3].getAsDouble();
-
- // double fv = 0d;
- // if(params.length > 4) {
- // fv = params[4].getAsDouble();
- // }
-
- // int pmtType = PMT_END_MNTH;
- // if(params.length > 5) {
- // pmtType = params[5].getAsLongInt();
- // }
-
- // double pmt = calculateLoanPayment(rate, nper, pv, pmtType);
-
- // if(fv != 0d) {
- // pmt += calculateAnnuityPayment(rate, nper, fv, pmtType);
- // }
-
- // double result = calculateInterestPayment(pmt, rate, per, pv, pmtType);
-
- // return BuiltinOperators.toValue(result);
- // }
- // });
-
- // FIXME untested
- // public static final Function PPMT = registerFunc(new FuncVar("PPmt", 4, 6) {
- // @Override
- // protected Value evalVar(EvalContext ctx, Value[] params) {
- // double rate = params[0].getAsDouble();
- // double per = params[1].getAsDouble();
- // double nper = params[2].getAsDouble();
- // double pv = params[3].getAsDouble();
-
- // double fv = 0d;
- // if(params.length > 4) {
- // fv = params[4].getAsDouble();
- // }
-
- // int pmtType = PMT_END_MNTH;
- // if(params.length > 5) {
- // pmtType = params[5].getAsLongInt();
- // }
-
- // double pmt = calculateLoanPayment(rate, nper, pv, pmtType);
-
- // if(fv != 0d) {
- // pmt += calculateAnnuityPayment(rate, nper, fv, pmtType);
- // }
-
- // double result = pmt - calculateInterestPayment(pmt, rate, per, pv,
- // pmtType);
-
- // return BuiltinOperators.toValue(result);
- // }
- // });
-
- // FIXME, doesn't work for partial days
- // public static final Function DDB = registerFunc(new FuncVar("DDB", 4, 5) {
- // @Override
- // protected Value evalVar(EvalContext ctx, Value[] params) {
- // double cost = params[0].getAsDouble();
- // double salvage = params[1].getAsDouble();
- // double life = params[2].getAsDouble();
- // double period = params[3].getAsDouble();
-
- // double factor = 2d;
- // if(params.length > 4) {
- // factor = params[4].getAsDouble();
- // }
-
- // double result = 0d;
-
- // // fractional value always rounds up to one year
- // if(period < 1d) {
- // period = 1d;
- // }
-
- // // FIXME? apply partial period _first_
- // // double partPeriod = period % 1d;
- // // if(partPeriod != 0d) {
- // // result = calculateDoubleDecliningBalance(
- // // cost, salvage, life, factor) * partPeriod;
- // // period -= partPeriod;
- // // cost -= result;
- // // }
- // double prevResult = 0d;
- // while(period > 0d) {
- // prevResult = result;
- // double remPeriod = Math.min(period, 1d);
- // result = calculateDoubleDecliningBalance(
- // cost, salvage, life, factor);
- // if(remPeriod < 1d) {
- // result = (prevResult + result) / 2d;
- // }
- // period -= 1d;
- // cost -= result;
- // }
-
- // return BuiltinOperators.toValue(result);
- // }
- // });
-
- // FIXME, untested
- public static final Function SLN = registerFunc(new FuncVar("SLN", 3, 3) {
- @Override
- protected Value evalVar(EvalContext ctx, Value[] params) {
- double cost = params[0].getAsDouble();
- double salvage = params[1].getAsDouble();
- double life = params[2].getAsDouble();
-
- double result = calculateStraightLineDepreciation(cost, salvage, life);
-
- return BuiltinOperators.toValue(result);
- }
- });
-
- // FIXME, untested
- public static final Function SYD = registerFunc(new FuncVar("SYD", 4, 4) {
- @Override
- protected Value evalVar(EvalContext ctx, Value[] params) {
- double cost = params[0].getAsDouble();
- double salvage = params[1].getAsDouble();
- double life = params[2].getAsDouble();
- double period = params[3].getAsDouble();
-
- double result = calculateSumOfYearsDepreciation(
- cost, salvage, life, period);
-
- return BuiltinOperators.toValue(result);
- }
- });
-
-
- private static double calculateLoanPaymentPeriods(
- double rate, double pmt, double pv, int pmtType) {
-
- // https://brownmath.com/bsci/loan.htm
- // http://financeformulas.net/Number-of-Periods-of-Annuity-from-Present-Value.html
-
- if(pmtType == PMT_BEG_MNTH) {
- pv += pmt;
- }
-
- double v1 = Math.log(1d + (rate * pv / pmt));
-
- double v2 = Math.log(1d + rate);
-
- double result = -v1 / v2;
-
- if(pmtType == PMT_BEG_MNTH) {
- result += 1d;
- }
-
- return result;
- }
-
- private static double calculateAnnuityPaymentPeriods(
- double rate, double pmt, double fv, int pmtType) {
-
- // https://brownmath.com/bsci/loan.htm
- // http://financeformulas.net/Number-of-Periods-of-Annuity-from-Future-Value.html
- // https://accountingexplained.com/capital/tvm/fv-annuity
-
- if(pmtType == PMT_BEG_MNTH) {
- fv *= (1d + rate);
- }
-
- double v1 = Math.log(1d - (rate * fv / pmt));
-
- double v2 = Math.log(1d + rate);
-
- double result = v1 / v2;
-
- if(pmtType == PMT_BEG_MNTH) {
- result -= 1d;
- }
-
- return result;
- }
-
- private static double calculateFutureValue(
- double rate, double nper, double pmt, int pmtType) {
-
- double result = -pmt * ((Math.pow((1d + rate), nper) - 1d) / rate);
-
- if(pmtType == PMT_BEG_MNTH) {
- result *= (1d + rate);
- }
-
- return result;
- }
-
- private static double calculatePresentValue(
- double rate, double nper, double pmt, int pmtType) {
-
- if(pmtType == PMT_BEG_MNTH) {
- nper -= 1d;
- }
-
- double result = -pmt * ((1d - Math.pow((1d + rate), -nper)) / rate);
-
- if(pmtType == PMT_BEG_MNTH) {
- result -= pmt;
- }
-
- return result;
- }
-
- private static double calculateLoanPayment(
- double rate, double nper, double pv, int pmtType) {
-
- double result = -(rate * pv) / (1d - Math.pow((1d + rate), -nper));
-
- if(pmtType == PMT_BEG_MNTH) {
- result /= (1d + rate);
- }
-
- return result;
- }
-
- private static double calculateAnnuityPayment(
- double rate, double nper, double fv, int pmtType) {
-
- double result = -(fv * rate) / (Math.pow((1d + rate), nper) - 1d);
-
- if(pmtType == PMT_BEG_MNTH) {
- result /= (1d + rate);
- }
-
- return result;
- }
-
- private static double calculateInterestPayment(
- double pmt, double rate, double per, double pv, int pmtType) {
-
- // http://www.tvmcalcs.com/index.php/calculators/apps/excel_loan_amortization
- // http://financeformulas.net/Remaining_Balance_Formula.html
-
- double pvPer = per;
- double fvPer = per;
- if(pmtType == PMT_END_MNTH) {
- pvPer -= 1d;
- fvPer -= 1d;
- } else {
- pvPer -= 2d;
- fvPer -= 1d;
- }
-
- double remBalance = (pv * Math.pow((1d + rate), pvPer)) -
- // FIXME, always use pmtType of 0?
- calculateFutureValue(rate, fvPer, pmt, PMT_END_MNTH);
-
- double result = -(remBalance * rate);
-
- return result;
- }
-
- private static double calculateDoubleDecliningBalance(
- double cost, double salvage, double life, double factor) {
-
- double result1 = cost * (factor/life);
- double result2 = cost - salvage;
-
- return Math.min(result1, result2);
- }
-
- private static double calculateStraightLineDepreciation(
- double cost, double salvage, double life) {
- return ((cost - salvage) / life);
- }
-
- private static double calculateSumOfYearsDepreciation(
- double cost, double salvage, double life, double period) {
-
- double sumOfYears = (period * (period + 1)) / 2d;
- double result = ((cost - salvage) * ((life + 1 - period) / sumOfYears));
-
- return result;
- }
-
- }
|