FinancialFunctions.Net 0.2.0

dotnet add package FinancialFunctions.Net --version 0.2.0
                    
NuGet\Install-Package FinancialFunctions.Net -Version 0.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="FinancialFunctions.Net" Version="0.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FinancialFunctions.Net" Version="0.2.0" />
                    
Directory.Packages.props
<PackageReference Include="FinancialFunctions.Net" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add FinancialFunctions.Net --version 0.2.0
                    
#r "nuget: FinancialFunctions.Net, 0.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package FinancialFunctions.Net@0.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=FinancialFunctions.Net&version=0.2.0
                    
Install as a Cake Addin
#tool nuget:?package=FinancialFunctions.Net&version=0.2.0
                    
Install as a Cake Tool

FinancialFunctions.NET

Time-value-of-money and cashflow math for .NET: PV, FV, PMT, IPMT, PPMT, RATE, NPER, NPV, IRR, MIRR, date-aware XNPV/XIRR, and loan amortization schedules. Zero dependencies.

Excel and LibreOffice Calc ship a full set of financial functions. The .NET base class library ships none of them. The one .NET incumbent, Microsoft.VisualBasic.Financial, is Windows-only (it depends on COM interop types), VB-flavored, undocumented in its edge-case behavior, and has had no functional change in over a decade. FinancialFunctions.NET is a modern, cross-platform, fully documented replacement: decimal-precise where money is at stake, table-driven-tested against independently computed reference values, and dependency-free.

Install

dotnet add package FinancialFunctions.Net

Usage

Mortgage payment and amortization schedule

using FinancialFunctions;

decimal principal = 200_000m;
decimal monthlyRate = 0.06m / 12m;
int termInMonths = 360;

decimal monthlyPayment = Financial.Payment(monthlyRate, termInMonths, principal);
// -1199.10 (an outflow, by the PV/FV/PMT sign convention: negative means paid out.
// principal is passed positive here because it is money received by the borrower.)

var schedule = AmortizationScheduler.GenerateSchedule(principal, monthlyRate, termInMonths);
Console.WriteLine(schedule[0]);
// PeriodNumber: 1, PaymentAmount: 1199.10, PrincipalPaid: 199.10, InterestPaid: 1000.00, RemainingBalance: 199800.90

decimal totalPrincipalPaid = schedule.Sum(period => period.PrincipalPaid);
// exactly 200000.00 - the schedule always sums to the original principal, no minor units lost to rounding

Interest and principal split for a single payment (IPMT / PPMT)

using FinancialFunctions;

decimal principal = 8_000m;
decimal monthlyRate = 0.10m / 12m;
int termInMonths = 36;

decimal payment = Financial.Payment(monthlyRate, termInMonths, principal);
// -258.14 (the level monthly payment, an outflow)

decimal firstInterest = Financial.InterestPayment(monthlyRate, 1, termInMonths, principal);
// -66.67 (interest portion of payment 1)

decimal firstPrincipal = Financial.PrincipalPayment(monthlyRate, 1, termInMonths, principal);
// -191.47 (principal portion of payment 1)

// InterestPayment + PrincipalPayment always reconstitutes Payment for the same period:
// -66.67 + -191.47 == -258.14

Investment return on irregular, dated cashflows (XIRR)

using FinancialFunctions;

var cashflows = new[] { -10_000m, 2_750m, 4_250m, 3_250m, 2_750m };
var dates = new[]
{
    new DateTime(2024, 1, 1),
    new DateTime(2024, 3, 3),
    new DateTime(2024, 6, 7),
    new DateTime(2024, 9, 6),
    new DateTime(2024, 12, 6),
};

double annualReturn = Financial.InternalRateOfReturn(cashflows, dates);
// 0.6441... (64.41% annualized, using an ACT/365 day-count basis)

Project appraisal with NPV and IRR

using FinancialFunctions;

var projectCashflows = new[] { -50_000m, 15_000m, 18_000m, 21_000m, 17_000m };

decimal netPresentValue = Financial.NetPresentValue(0.1m, projectCashflows);
double internalRateOfReturn = Financial.InternalRateOfReturn(projectCashflows);

if (netPresentValue > 0)
{
    Console.WriteLine($"Accept: NPV is {netPresentValue:C}, IRR is {internalRateOfReturn:P2}");
}

API

All functions live on one static class, FinancialFunctions.Financial, mirroring the Excel names as closely as .NET naming conventions allow. NetPresentValue and InternalRateOfReturn each have a date-aware overload (Excel's XNPV and XIRR) that takes a matching list of DateTime.

Method Excel equivalent Returns
Financial.PresentValue(rate, nper, pmt, fv, timing) PV decimal
Financial.FutureValue(rate, nper, pmt, pv, timing) FV decimal
Financial.Payment(rate, nper, pv, fv, timing) PMT decimal
Financial.InterestPayment(rate, period, nper, pv, fv, timing) IPMT decimal
Financial.PrincipalPayment(rate, period, nper, pv, fv, timing) PPMT decimal
Financial.NumberOfPeriods(rate, pmt, pv, fv, timing) NPER double
Financial.Rate(nper, pmt, pv, fv, timing, guess) RATE double
Financial.NetPresentValue(rate, cashflows) NPV decimal
Financial.NetPresentValue(rate, cashflows, dates) XNPV decimal
Financial.InternalRateOfReturn(cashflows, guess) IRR double
Financial.InternalRateOfReturn(cashflows, dates, guess) XIRR double
Financial.ModifiedInternalRateOfReturn(cashflows, financeRate, reinvestRate) MIRR double
AmortizationScheduler.GenerateSchedule(principal, periodicRate, numberOfPayments, roundingDecimals) (no direct equivalent) IReadOnlyList<AmortizationPeriod>

Sign convention: money paid out is negative, money received is positive, exactly as in Excel. A Payment result is negative because it represents an outflow from the borrower; the amounts inside an AmortizationPeriod are unsigned magnitudes (how much the borrower pays), since a schedule is conventionally displayed as positive columns.

Design notes

  • Decimal where it counts. PresentValue, FutureValue, Payment, NetPresentValue (NPV) and the amortization schedule all compute entirely in decimal, including the (1 + rate)^n growth factor (via an internal exponentiation-by-squaring helper), so money values never round-trip through floating point. NumberOfPeriods, Rate, InternalRateOfReturn (IRR/XIRR) and ModifiedInternalRateOfReturn return a rate rather than a money amount and use double for the parts of the computation that require a logarithm, an iterative solve, or a fractional exponent; this mirrors what Excel itself does internally for these functions.
  • XNPV/XIRR day-count basis. The date-aware overloads use ACT/365: the discount exponent for a cashflow is (date - date[0]).TotalDays / 365. This is the same convention Excel and LibreOffice Calc use, and it means the exponent is generally fractional, which is why those two overloads compute internally in double.
  • XNPV/XIRR date ordering. Matching Excel, dates[0] is the valuation date every other date is measured against, and no other date may fall before it; a date earlier than dates[0] throws ArgumentException (Excel returns #NUM! in this case). Dates after dates[0] may appear in any order.
  • NPER infeasible inputs. NumberOfPeriods throws ArgumentException when the payment does not cover the interest accruing on the present value at the given rate, so no finite number of periods reaches the target future value (Excel returns #NUM! in this case), rather than silently returning NaN.
  • Solver behavior. Rate, InternalRateOfReturn and its date-aware overload solve for a root using Newton's method with a numerically estimated derivative (central finite difference), shared by a single internal root finder rather than three separately hand-derived formulas. If Newton's method diverges, stalls, or steps outside the valid domain (rate > -1), the solver falls back to bisection: it scans outward from the domain floor for a sign change and bisects that bracket. If no such bracket exists, FinancialConvergenceException is thrown. The convergence tolerance (1e-7) and iteration cap (100) are documented, named constants on FinancialSolverDefaults.
  • Multiple roots. A cashflow series can have zero, one, or several mathematically valid internal rates of return, depending on how many times its cumulative balance changes sign (Descartes' rule of signs). InternalRateOfReturn returns whichever root Newton's method converges to from the supplied guess, or the first sign-changing root bisection finds while scanning outward. If your cashflows are not a simple invest-then-return pattern, pass a guess close to the root you expect, or use ModifiedInternalRateOfReturn (MIRR), which has a single closed-form answer by construction.
  • Amortization rounding. Each period's interest is Math.Round(balance * rate, roundingDecimals, MidpointRounding.AwayFromZero). The final period's principal is forced to exactly the remaining balance rather than the level payment amount, which guarantees the PrincipalPaid column sums to exactly the original principal and the final RemainingBalance is exactly zero, regardless of how many cents of rounding drift accumulated along the way.

Testing methodology

Every closed-form function (PV, FV, PMT, NPER, NPV, XNPV) and every iterative solver (RATE, IRR, XIRR, MIRR) is checked in a table-driven xUnit Theory against reference values computed independently, in a separate script and a separate language runtime (PowerShell), from the same published closed-form formulas that Excel and LibreOffice Calc document for these functions. This project does not have access to a licensed copy of Excel or LibreOffice in its build environment, so this independent-implementation cross-check is used in place of pasting numbers out of a spreadsheet; the formulas themselves (PV/FV/PMT/NPER/RATE/NPV/IRR/MIRR/XNPV/XIRR) are the ones Microsoft and LibreOffice publish for these exact functions. If you spot a published Excel or LibreOffice worked example that disagrees with a fixture here, please open an issue.

The amortization schedule is checked against hand-computed values for a standard 30-year, 6% mortgage on $200,000 (first-period payment $1,199.10, of which $1,000.00 is interest and $199.10 is principal), and, separately, an invariant test that the PrincipalPaid column sums to exactly the original principal for every schedule generated, across a range of principals, rates and terms.

Dependencies and AOT

Zero runtime NuGet dependencies. The only package reference is Microsoft.SourceLink.GitHub, a build-time-only reference used to embed source links in the package; it does not ship in your application. The library contains no reflection, no dynamic code generation, and no unmanaged interop, so it is fully compatible with Native AOT publishing and trimming.

License

MIT. See LICENSE.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.2.0 65 8/21/2026
0.1.0 93 8/12/2026