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