Files
calculation/ts/calculation.classes.interest.ts
Juergen Kunz d63339cb71
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
feat(core): initial release of financial calculation package with decimal precision
2025-07-29 09:20:06 +00:00

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);
}
}