Skip to content

Commit 5cf7c86

Browse files
committed
Add IPMT function
1 parent cd15080 commit 5cf7c86

File tree

4 files changed

+203
-0
lines changed

4 files changed

+203
-0
lines changed

TinyExprChanges.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ The following are changes from the original TinyExpr C library:
5252
- `fact`: alias for `fac()`, like the *Excel* function.
5353
- `false`: returns `false` (i.e., `0`) in a boolean expression.
5454
- `fv`: returns the future value of an investment.
55+
- `ipmt`: returns the interest portion of a payment for a specified period of an investment.
5556
- `iserr`: returns true if an expression evaluates to NaN.
5657
- `iserror`: alias for `iserr`.
5758
- `iseven`: returns true if a number is even, false if odd.

docs/manual/functions.qmd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ Any subsequent arguments that evaluate to NaN will be ignored.
127127
| DB(Cost, Salvage, Lifetime, Period, Month) | Returns the depreciation of an asset for a specified period using the fixed-declining balance method. |
128128
| EFFECT(NominalRate, Periods) | Returns the effective annual interest rate, provided the nominal annual interest rate and the number of compounding periods per year.<br>\linebreak NaN will be returned if *Periods* is < 1 or if *NominalRate* <= 0. |
129129
| FV(Rate, Periods, Payment, [PresentValue], [Type]) | Returns the future value of an investment based on a constant interest rate, a fixed number of periods, and periodic payments.<br>\linebreak Note that *PresentValue* and *Type* are optional and default to 0.<br>\linebreak Also, *Type* specifies when payments are due: 0 = end of period, 1 = beginning of period.<br>\linebreak NaN will be returned if *Periods* <= 0 or if any required argument is not finite. |
130+
| IPMT(Rate, Period, Periods, PresentValue, [FutureValue], [Type]) | Returns the interest portion of a payment for a specified period of an investment.<br>\linebreak *FutureValue* and *Type* are optional and default to 0.<br>\linebreak *Type* specifies when payments are due: 0 = end of period, 1 = beginning of period. |
130131
| NOMINAL(EffectiveRate, Periods) | Returns the nominal annual interest rate, provided the effective rate and the number of compounding periods per year.<br>\linebreak NaN will be returned if *Periods* is < 1 or if *EffectiveRate* <= 0. |
131132
| NPER(Rate, Payment, PresentValue, [FutureValue], [Type]) | Returns the number of periods for an investment or loan based on a constant interest rate, periodic payments, and present and future values.<br>\linebreak Note that *FutureValue* and *Type* are optional and default to 0.<br>\linebreak Also, *Type* specifies when payments are due: 0 = end of period, 1 = beginning of period.<br>\linebreak NaN will be returned if any required argument is not finite or if the calculation is not defined. |
132133
| PMT(Rate, Periods, PresentValue, [FutureValue], [Type]) | Returns the periodic payment for an investment or loan based on a constant interest rate, a fixed number of periods, and a present value.<br>\linebreak Note that *FutureValue* and *Type* are optional and default to 0.<br>\linebreak Also, *Type* specifies when payments are due: 0 = end of period, 1 = beginning of period.<br>\linebreak NaN will be returned if *Periods* <= 0 or if any required argument is not finite. |

tests/tetests.cpp

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4193,6 +4193,133 @@ TEST_CASE("NaN Comparison", "[nan]")
41934193
}
41944194

41954195
// Financial functions
4196+
// --------------------------------------------------
4197+
// IPMT
4198+
// --------------------------------------------------
4199+
TEST_CASE("IPMT", "[finance]")
4200+
{
4201+
te_parser tep;
4202+
4203+
// --------------------------------------------------
4204+
// Excel Online Help example
4205+
//
4206+
// Data:
4207+
// Annual interest rate: 10%
4208+
// Number of years: 3
4209+
// Present value: 8000
4210+
//
4211+
// Excel formula:
4212+
// =IPMT(0.10/12, 1, 3*12, 8000)
4213+
//
4214+
// Excel result: -66.67
4215+
// --------------------------------------------------
4216+
CHECK_THAT(
4217+
WITHIN_TYPE_CAST(tep.evaluate("IPMT(0.10/12, 1, 3*12, 8000)")),
4218+
Catch::Matchers::WithinRel(
4219+
WITHIN_TYPE_CAST(-66.67),
4220+
WITHIN_TYPE_CAST(0.0001)));
4221+
4222+
// --------------------------------------------------
4223+
// Excel: =IPMT(0.10/12, 2, 3*12, 8000)
4224+
// Interest portion for second period
4225+
// Excel result: -65.0710764092997
4226+
// --------------------------------------------------
4227+
CHECK_THAT(
4228+
WITHIN_TYPE_CAST(tep.evaluate("IPMT(0.10/12, 2, 3*12, 8000)")),
4229+
Catch::Matchers::WithinRel(
4230+
WITHIN_TYPE_CAST(-65.0710764092997),
4231+
WITHIN_TYPE_CAST(0.0001)));
4232+
4233+
// --------------------------------------------------
4234+
// Excel: =IPMT(0.05/12, 1, 60, 10000)
4235+
// First month's interest on a 5-year loan
4236+
// Excel result: -41.67
4237+
// --------------------------------------------------
4238+
CHECK_THAT(
4239+
WITHIN_TYPE_CAST(tep.evaluate("IPMT(0.05/12, 1, 60, 10000)")),
4240+
Catch::Matchers::WithinRel(
4241+
WITHIN_TYPE_CAST(-41.67),
4242+
WITHIN_TYPE_CAST(0.0001)));
4243+
4244+
// --------------------------------------------------
4245+
// Excel: =IPMT(0.05/12, 12, 60, 10000)
4246+
// Interest portion after one year
4247+
// Excel result: -34.12
4248+
// --------------------------------------------------
4249+
CHECK_THAT(
4250+
WITHIN_TYPE_CAST(tep.evaluate("IPMT(0.05/12, 12, 60, 10000)")),
4251+
Catch::Matchers::WithinRel(
4252+
WITHIN_TYPE_CAST(-34.7848954631707),
4253+
WITHIN_TYPE_CAST(0.0001)));
4254+
4255+
// --------------------------------------------------
4256+
// Payments at beginning of period
4257+
//
4258+
// Excel: =IPMT(0.05/12, 1, 60, 10000, 0, 1)
4259+
// First period interest is zero
4260+
// --------------------------------------------------
4261+
CHECK_THAT(
4262+
WITHIN_TYPE_CAST(tep.evaluate("IPMT(0.05/12, 1, 60, 10000, 0, 1)")),
4263+
Catch::Matchers::WithinRel(
4264+
WITHIN_TYPE_CAST(0.0)));
4265+
4266+
// --------------------------------------------------
4267+
// Excel: =IPMT(0.05/12, 2, 60, 10000, 0, 1)
4268+
// Interest starts accruing in second period
4269+
// Excel result: -40.88
4270+
// --------------------------------------------------
4271+
CHECK_THAT(
4272+
WITHIN_TYPE_CAST(tep.evaluate("IPMT(0.05/12, 2, 60, 10000, 0, 1)")),
4273+
Catch::Matchers::WithinRel(
4274+
WITHIN_TYPE_CAST(-40.8836279262513),
4275+
WITHIN_TYPE_CAST(0.0000001)));
4276+
4277+
// --------------------------------------------------
4278+
// Zero interest rate
4279+
//
4280+
// Excel: =IPMT(0, 10, 60, 10000)
4281+
// Excel result: 0
4282+
// --------------------------------------------------
4283+
CHECK_THAT(
4284+
WITHIN_TYPE_CAST(tep.evaluate("IPMT(0, 10, 60, 10000)")),
4285+
Catch::Matchers::WithinRel(
4286+
WITHIN_TYPE_CAST(0.0)));
4287+
4288+
CHECK_THAT(
4289+
WITHIN_TYPE_CAST(tep.evaluate("=IPMT(0.05/12, 1.5, 60, 10000)")),
4290+
Catch::Matchers::WithinRel(
4291+
WITHIN_TYPE_CAST(-41.3606399677465),
4292+
WITHIN_TYPE_CAST(0.0000001)));
4293+
4294+
CHECK_THAT(
4295+
WITHIN_TYPE_CAST(tep.evaluate("=IPMT(-1, 1, 60, 10000)")),
4296+
Catch::Matchers::WithinRel(
4297+
WITHIN_TYPE_CAST(10000),
4298+
WITHIN_TYPE_CAST(0.0000001)));
4299+
4300+
CHECK_THAT(
4301+
WITHIN_TYPE_CAST(tep.evaluate("=IPMT(-1.1, 1, 60, 10000)")),
4302+
Catch::Matchers::WithinRel(
4303+
WITHIN_TYPE_CAST(11000),
4304+
WITHIN_TYPE_CAST(0.0000001)));
4305+
4306+
// --------------------------------------------------
4307+
// Invalid inputs
4308+
// --------------------------------------------------
4309+
4310+
// period < 1
4311+
CHECK(std::isnan(WITHIN_TYPE_CAST(tep.evaluate("IPMT(0.05/12, 0, 60, 10000)"))));
4312+
4313+
// period > periods
4314+
CHECK(std::isnan(WITHIN_TYPE_CAST(tep.evaluate("IPMT(0.05/12, 61, 60, 10000)"))));
4315+
4316+
// non-finite args
4317+
CHECK(std::isnan(WITHIN_TYPE_CAST(tep.evaluate("IPMT(NaN, 1, 60, 10000)"))));
4318+
CHECK(std::isnan(WITHIN_TYPE_CAST(tep.evaluate("IPMT(0.05/12, NaN, 60, 10000)"))));
4319+
CHECK(std::isnan(WITHIN_TYPE_CAST(tep.evaluate("IPMT(0.05/12, 1, NaN, 10000)"))));
4320+
CHECK(std::isnan(WITHIN_TYPE_CAST(tep.evaluate("IPMT(0.05/12, 1, 60, NaN)"))));
4321+
}
4322+
41964323
// --------------------------------------------------
41974324
// PV
41984325
// --------------------------------------------------

tinyexpr.cpp

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,78 @@ namespace te_builtins
392392
((1 + (rate * type)) * (powVal - 1));
393393
}
394394

395+
[[nodiscard]]
396+
static te_type te_ipmt(te_type rate, te_type period, te_type periods, te_type presentValue,
397+
te_type futureValue, te_type type)
398+
{
399+
if (!std::isfinite(rate) || !std::isfinite(period) || !std::isfinite(periods) ||
400+
!std::isfinite(presentValue))
401+
{
402+
return te_parser::te_nan;
403+
}
404+
405+
if (!std::isfinite(futureValue))
406+
{
407+
futureValue = 0;
408+
}
409+
if (!std::isfinite(type))
410+
{
411+
type = 0;
412+
}
413+
414+
type = (type != 0) ? 1 : 0;
415+
416+
if (periods <= 0.0)
417+
{
418+
return te_parser::te_nan;
419+
}
420+
if (period < 1.0 || period > periods)
421+
{
422+
return te_parser::te_nan;
423+
}
424+
if (rate == 0.0)
425+
{
426+
return 0.0;
427+
}
428+
if (rate <= -1.0)
429+
{
430+
// Excel: linear interest regime for IPMT
431+
if (type != 0 && period == 1.0)
432+
{
433+
return 0.0;
434+
}
435+
436+
return -presentValue * rate;
437+
}
438+
439+
const te_type payment = te_pmt(rate, periods, presentValue, futureValue, type);
440+
441+
if (!std::isfinite(payment))
442+
{
443+
return te_parser::te_nan;
444+
}
445+
446+
te_type balance;
447+
448+
if (type == 1)
449+
{
450+
if (period == 1.0)
451+
{
452+
return 0.0;
453+
}
454+
455+
balance = -(presentValue * std::pow(1 + rate, period - 2) +
456+
payment * (std::pow(1 + rate, period - 2) - 1) / rate + payment);
457+
}
458+
else
459+
{
460+
balance = -(presentValue * std::pow(1 + rate, period - 1) +
461+
payment * (std::pow(1 + rate, period - 1) - 1) / rate);
462+
}
463+
464+
return balance * rate;
465+
}
466+
395467
[[nodiscard]]
396468
static te_type te_pv(te_type rate, te_type nper, te_type pmt, te_type futureValue, te_type type)
397469
{
@@ -1663,6 +1735,8 @@ const std::set<te_variable> te_parser::m_functions = { // NOLINT
16631735
{ "if", static_cast<te_fun3>(te_builtins::te_if), TE_PURE },
16641736
{ "ifs", static_cast<te_fun24>(te_builtins::te_ifs),
16651737
static_cast<te_variable_flags>(TE_PURE | TE_VARIADIC) },
1738+
{ "ipmt", static_cast<te_fun6>(te_builtins::te_ipmt),
1739+
static_cast<te_variable_flags>(TE_PURE | TE_VARIADIC) },
16661740
{ "ln", static_cast<te_fun1>(te_builtins::te_log), TE_PURE },
16671741
{ "log10", static_cast<te_fun1>(te_builtins::te_log10), TE_PURE },
16681742
{ "max", static_cast<te_fun24>(te_builtins::te_max),

0 commit comments

Comments
 (0)