Some checks failed
CI Pipeline (nottags) / security (push) Successful in 17s
CI Pipeline (tags) / security (push) Successful in 17s
CI Pipeline (nottags) / test (push) Failing after 52s
CI Pipeline (tags) / test (push) Failing after 50s
CI Pipeline (tags) / release (push) Has been skipped
CI Pipeline (tags) / metadata (push) Has been skipped
297 lines
8.5 KiB
TypeScript
297 lines
8.5 KiB
TypeScript
import * as plugins from './calculation.plugins.js';
|
|
import { Calculator } from './calculation.classes.calculator.js';
|
|
|
|
export type CompoundingFrequency = 'annually' | 'semiannually' | 'quarterly' | 'monthly' | 'weekly' | 'daily' | 'continuous';
|
|
|
|
export interface IInterestOptions {
|
|
principal: plugins.Decimal.Value;
|
|
rate: plugins.Decimal.Value;
|
|
time: plugins.Decimal.Value;
|
|
compoundingFrequency?: CompoundingFrequency;
|
|
}
|
|
|
|
/**
|
|
* Interest calculations class providing various interest computation methods
|
|
*/
|
|
export class Interest extends Calculator {
|
|
private readonly frequencyMap: Record<CompoundingFrequency, number> = {
|
|
annually: 1,
|
|
semiannually: 2,
|
|
quarterly: 4,
|
|
monthly: 12,
|
|
weekly: 52,
|
|
daily: 365,
|
|
continuous: 0
|
|
};
|
|
|
|
constructor() {
|
|
super({ precision: 15 });
|
|
}
|
|
|
|
/**
|
|
* Calculate simple interest
|
|
* @param principal The principal amount
|
|
* @param rate The annual interest rate (as decimal, e.g., 0.05 for 5%)
|
|
* @param time The time period in years
|
|
*/
|
|
public simple(
|
|
principal: plugins.Decimal.Value,
|
|
rate: plugins.Decimal.Value,
|
|
time: plugins.Decimal.Value
|
|
): plugins.Decimal {
|
|
const p = this.decimal(principal);
|
|
const r = this.decimal(rate);
|
|
const t = this.decimal(time);
|
|
|
|
return this.multiply(p, r, t);
|
|
}
|
|
|
|
/**
|
|
* Calculate simple interest amount (principal + interest)
|
|
*/
|
|
public simpleAmount(
|
|
principal: plugins.Decimal.Value,
|
|
rate: plugins.Decimal.Value,
|
|
time: plugins.Decimal.Value
|
|
): plugins.Decimal {
|
|
const p = this.decimal(principal);
|
|
const interest = this.simple(principal, rate, time);
|
|
|
|
return this.add(p, interest);
|
|
}
|
|
|
|
/**
|
|
* Calculate compound interest
|
|
* @param principal The principal amount
|
|
* @param rate The annual interest rate (as decimal)
|
|
* @param time The time period in years
|
|
* @param frequency The compounding frequency (default: annually)
|
|
*/
|
|
public compound(
|
|
principal: plugins.Decimal.Value,
|
|
rate: plugins.Decimal.Value,
|
|
time: plugins.Decimal.Value,
|
|
frequency: CompoundingFrequency = 'annually'
|
|
): plugins.Decimal {
|
|
const p = this.decimal(principal);
|
|
const r = this.decimal(rate);
|
|
const t = this.decimal(time);
|
|
|
|
if (frequency === 'continuous') {
|
|
return this.continuousCompound(principal, rate, time);
|
|
}
|
|
|
|
const n = this.decimal(this.frequencyMap[frequency]);
|
|
const amount = this.compoundAmount(principal, rate, time, frequency);
|
|
|
|
return this.subtract(amount, p);
|
|
}
|
|
|
|
/**
|
|
* Calculate compound interest amount (principal + interest)
|
|
*/
|
|
public compoundAmount(
|
|
principal: plugins.Decimal.Value,
|
|
rate: plugins.Decimal.Value,
|
|
time: plugins.Decimal.Value,
|
|
frequency: CompoundingFrequency = 'annually'
|
|
): plugins.Decimal {
|
|
const p = this.decimal(principal);
|
|
const r = this.decimal(rate);
|
|
const t = this.decimal(time);
|
|
|
|
if (frequency === 'continuous') {
|
|
return this.continuousCompoundAmount(principal, rate, time);
|
|
}
|
|
|
|
const n = this.decimal(this.frequencyMap[frequency]);
|
|
const ratePerPeriod = this.divide(r, n);
|
|
const periods = this.multiply(n, t);
|
|
const multiplier = this.power(this.add(1, ratePerPeriod), periods);
|
|
|
|
return this.multiply(p, multiplier);
|
|
}
|
|
|
|
/**
|
|
* Calculate continuous compound interest
|
|
*/
|
|
private continuousCompound(
|
|
principal: plugins.Decimal.Value,
|
|
rate: plugins.Decimal.Value,
|
|
time: plugins.Decimal.Value
|
|
): plugins.Decimal {
|
|
const p = this.decimal(principal);
|
|
const amount = this.continuousCompoundAmount(principal, rate, time);
|
|
|
|
return this.subtract(amount, p);
|
|
}
|
|
|
|
/**
|
|
* Calculate continuous compound interest amount
|
|
*/
|
|
private continuousCompoundAmount(
|
|
principal: plugins.Decimal.Value,
|
|
rate: plugins.Decimal.Value,
|
|
time: plugins.Decimal.Value
|
|
): plugins.Decimal {
|
|
const p = this.decimal(principal);
|
|
const r = this.decimal(rate);
|
|
const t = this.decimal(time);
|
|
|
|
const exponent = this.multiply(r, t);
|
|
const multiplier = this.exp(exponent);
|
|
|
|
return this.multiply(p, multiplier);
|
|
}
|
|
|
|
/**
|
|
* Calculate effective annual rate (EAR) from nominal rate
|
|
* @param nominalRate The nominal annual interest rate
|
|
* @param frequency The compounding frequency
|
|
*/
|
|
public effectiveAnnualRate(
|
|
nominalRate: plugins.Decimal.Value,
|
|
frequency: CompoundingFrequency = 'annually'
|
|
): plugins.Decimal {
|
|
const r = this.decimal(nominalRate);
|
|
|
|
if (frequency === 'continuous') {
|
|
return this.subtract(this.exp(r), 1);
|
|
}
|
|
|
|
const n = this.decimal(this.frequencyMap[frequency]);
|
|
const ratePerPeriod = this.divide(r, n);
|
|
const onePlusRate = this.add(1, ratePerPeriod);
|
|
const compounded = this.power(onePlusRate, n);
|
|
|
|
return this.subtract(compounded, 1);
|
|
}
|
|
|
|
/**
|
|
* Calculate nominal rate from effective annual rate
|
|
* @param effectiveRate The effective annual rate
|
|
* @param frequency The compounding frequency
|
|
*/
|
|
public nominalRate(
|
|
effectiveRate: plugins.Decimal.Value,
|
|
frequency: CompoundingFrequency = 'annually'
|
|
): plugins.Decimal {
|
|
const ear = this.decimal(effectiveRate);
|
|
|
|
if (frequency === 'continuous') {
|
|
return this.ln(this.add(1, ear));
|
|
}
|
|
|
|
const n = this.decimal(this.frequencyMap[frequency]);
|
|
const onePlusEar = this.add(1, ear);
|
|
const exponent = this.divide(1, n);
|
|
const nthRoot = this.power(onePlusEar, exponent);
|
|
const ratePerPeriod = this.subtract(nthRoot, 1);
|
|
|
|
return this.multiply(ratePerPeriod, n);
|
|
}
|
|
|
|
/**
|
|
* Calculate the real interest rate adjusted for inflation
|
|
* @param nominalRate The nominal interest rate
|
|
* @param inflationRate The inflation rate
|
|
*/
|
|
public realRate(
|
|
nominalRate: plugins.Decimal.Value,
|
|
inflationRate: plugins.Decimal.Value
|
|
): plugins.Decimal {
|
|
const r = this.decimal(nominalRate);
|
|
const i = this.decimal(inflationRate);
|
|
|
|
const numerator = this.subtract(r, i);
|
|
const denominator = this.add(1, i);
|
|
|
|
return this.divide(numerator, denominator);
|
|
}
|
|
|
|
/**
|
|
* Calculate the time required to double an investment
|
|
* @param rate The interest rate
|
|
* @param frequency The compounding frequency
|
|
*/
|
|
public doubleTime(
|
|
rate: plugins.Decimal.Value,
|
|
frequency: CompoundingFrequency = 'annually'
|
|
): plugins.Decimal {
|
|
const r = this.decimal(rate);
|
|
|
|
if (r.isZero()) {
|
|
throw new Error('Cannot calculate doubling time with zero interest rate');
|
|
}
|
|
|
|
if (frequency === 'continuous') {
|
|
return this.divide(this.ln(2), r);
|
|
}
|
|
|
|
const n = this.decimal(this.frequencyMap[frequency]);
|
|
const ratePerPeriod = this.divide(r, n);
|
|
const numerator = this.ln(2);
|
|
const denominator = this.multiply(n, this.ln(this.add(1, ratePerPeriod)));
|
|
|
|
return this.divide(numerator, denominator);
|
|
}
|
|
|
|
/**
|
|
* Calculate the Rule of 72 approximation for doubling time
|
|
* @param rate The annual interest rate (as percentage, e.g., 5 for 5%)
|
|
*/
|
|
public ruleOf72(rate: plugins.Decimal.Value): plugins.Decimal {
|
|
const r = this.decimal(rate);
|
|
|
|
if (r.isZero()) {
|
|
throw new Error('Cannot calculate Rule of 72 with zero interest rate');
|
|
}
|
|
|
|
return this.divide(72, r);
|
|
}
|
|
|
|
/**
|
|
* Calculate periodic interest rate from annual rate
|
|
* @param annualRate The annual interest rate
|
|
* @param frequency The compounding frequency
|
|
*/
|
|
public periodicRate(
|
|
annualRate: plugins.Decimal.Value,
|
|
frequency: CompoundingFrequency
|
|
): plugins.Decimal {
|
|
if (frequency === 'continuous') {
|
|
throw new Error('Periodic rate not applicable for continuous compounding');
|
|
}
|
|
|
|
const r = this.decimal(annualRate);
|
|
const n = this.decimal(this.frequencyMap[frequency]);
|
|
|
|
return this.divide(r, n);
|
|
}
|
|
|
|
/**
|
|
* Calculate annual percentage yield (APY)
|
|
* Same as effective annual rate but commonly used in banking
|
|
*/
|
|
public apy(
|
|
nominalRate: plugins.Decimal.Value,
|
|
frequency: CompoundingFrequency = 'annually'
|
|
): plugins.Decimal {
|
|
return this.effectiveAnnualRate(nominalRate, frequency);
|
|
}
|
|
|
|
/**
|
|
* Calculate interest on interest (compound interest minus simple interest)
|
|
*/
|
|
public interestOnInterest(
|
|
principal: plugins.Decimal.Value,
|
|
rate: plugins.Decimal.Value,
|
|
time: plugins.Decimal.Value,
|
|
frequency: CompoundingFrequency = 'annually'
|
|
): plugins.Decimal {
|
|
const simpleInt = this.simple(principal, rate, time);
|
|
const compoundInt = this.compound(principal, rate, time, frequency);
|
|
|
|
return this.subtract(compoundInt, simpleInt);
|
|
}
|
|
} |