Skip to content

Commit cd15080

Browse files
committed
Improve financial functions under edge cases
Better matches Excel
1 parent 10733f3 commit cd15080

File tree

2 files changed

+57
-29
lines changed

2 files changed

+57
-29
lines changed

tests/tetests.cpp

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4244,14 +4244,15 @@ TEST_CASE("PV", "[finance]")
42444244
CHECK_THAT(WITHIN_TYPE_CAST(tep.evaluate("PV(-0.5, 5, -100)")),
42454245
Catch::Matchers::WithinRel(WITHIN_TYPE_CAST(6200), WITHIN_TYPE_CAST(0.0001)));
42464246

4247-
// Excel: =PV(-1, 10, -100) -> #NUM!
4248-
CHECK(std::isnan(WITHIN_TYPE_CAST(tep.evaluate("PV(-1, 10, -100)"))));
4247+
CHECK_THAT(
4248+
WITHIN_TYPE_CAST((tep.evaluate("PV(0.05, 0, -100)"))),
4249+
Catch::Matchers::WithinRel(WITHIN_TYPE_CAST(0), WITHIN_TYPE_CAST(0.0001)));
42494250

4250-
// Excel: =PV(0.05, 0, -100) -> #NUM!
4251-
CHECK(std::isnan(WITHIN_TYPE_CAST(tep.evaluate("PV(0.05, 0, -100)"))));
4251+
CHECK_THAT(WITHIN_TYPE_CAST(tep.evaluate("PV(0.05, -10, -100)")),
4252+
Catch::Matchers::WithinRel(WITHIN_TYPE_CAST(-1257.78925355488), WITHIN_TYPE_CAST(0.0001)));
42524253

4253-
// Excel: =PV(0.05, -10, -100) -> #NUM!
4254-
CHECK(std::isnan(WITHIN_TYPE_CAST(tep.evaluate("PV(0.05, -10, -100)"))));
4254+
// Excel: =PV(-1, 10, -100) -> #NUM!
4255+
CHECK(std::isnan(WITHIN_TYPE_CAST(tep.evaluate("PV(-1, 10, -100)"))));
42554256

42564257
// Excel: =PV(NaN, 10, -100) -> #NUM!
42574258
CHECK(std::isnan(WITHIN_TYPE_CAST(tep.evaluate("PV(NaN, 10, -100)"))));
@@ -4307,7 +4308,7 @@ TEST_CASE("PMT", "[finance]")
43074308
WITHIN_TYPE_CAST(tep.evaluate("PMT(0.05/12, 60, 10000, 0, 1)")),
43084309
Catch::Matchers::WithinRel(
43094310
WITHIN_TYPE_CAST(-187.93),
4310-
WITHIN_TYPE_CAST(0.00001)));
4311+
WITHIN_TYPE_CAST(0.001)));
43114312

43124313
// Excel: =PMT(0, 60, 12000)
43134314
// Zero interest: straight-line repayment
@@ -4317,6 +4318,11 @@ TEST_CASE("PMT", "[finance]")
43174318
Catch::Matchers::WithinRel(
43184319
WITHIN_TYPE_CAST(-200)));
43194320

4321+
CHECK_THAT(
4322+
WITHIN_TYPE_CAST(tep.evaluate("PMT(-1, 60, 12000)")),
4323+
Catch::Matchers::WithinRel(
4324+
WITHIN_TYPE_CAST(0)));
4325+
43204326
// Excel: =PMT(0.1, 1, 100)
43214327
// One-period loan at 10%
43224328
// Excel result = -110
@@ -4333,7 +4339,7 @@ TEST_CASE("PMT", "[finance]")
43334339
WITHIN_TYPE_CAST(tep.evaluate("PMT(0.05/12, 60, 10000, 1000)")),
43344340
Catch::Matchers::WithinRel(
43354341
WITHIN_TYPE_CAST(-203.42),
4336-
WITHIN_TYPE_CAST(0.00002)));
4342+
WITHIN_TYPE_CAST(0.001)));
43374343

43384344
// Excel data:
43394345
// Annual interest rate: 8%
@@ -4372,13 +4378,17 @@ TEST_CASE("PMT", "[finance]")
43724378
WITHIN_TYPE_CAST(-129.08),
43734379
WITHIN_TYPE_CAST(0.00005)));
43744380

4381+
CHECK_THAT(
4382+
WITHIN_TYPE_CAST(tep.evaluate("PMT(0.05, -10, 1000)")),
4383+
Catch::Matchers::WithinRel(
4384+
WITHIN_TYPE_CAST(79.5045749654567),
4385+
WITHIN_TYPE_CAST(0.00001)));
4386+
43754387
// Excel: rate <= -1 -> #NUM!
4376-
CHECK(std::isnan(WITHIN_TYPE_CAST(tep.evaluate("PMT(-1, 10, 1000)"))));
43774388
CHECK(std::isnan(WITHIN_TYPE_CAST(tep.evaluate("PMT(-1.5, 10, 1000)"))));
43784389

43794390
// Excel: nper <= 0 -> #NUM!
43804391
CHECK(std::isnan(WITHIN_TYPE_CAST(tep.evaluate("PMT(0.05, 0, 1000)"))));
4381-
CHECK(std::isnan(WITHIN_TYPE_CAST(tep.evaluate("PMT(0.05, -10, 1000)"))));
43824392

43834393
// Non-finite args -> NaN
43844394
CHECK(std::isnan(WITHIN_TYPE_CAST(tep.evaluate("PMT(NaN, 10, 1000)"))));
@@ -4575,13 +4585,29 @@ TEST_CASE("FV", "[finance]")
45754585
WITHIN_TYPE_CAST(2301.40),
45764586
WITHIN_TYPE_CAST(0.00005)));
45774587

4578-
// Excel: rate <= -1 -> #NUM!
4579-
CHECK(std::isnan(WITHIN_TYPE_CAST(tep.evaluate("FV(-1, 10, -100)"))));
4580-
CHECK(std::isnan(WITHIN_TYPE_CAST(tep.evaluate("FV(-1.5, 10, -100)"))));
4588+
CHECK_THAT(
4589+
WITHIN_TYPE_CAST(tep.evaluate("FV(-1, 10, -100)")),
4590+
Catch::Matchers::WithinRel(
4591+
WITHIN_TYPE_CAST(100),
4592+
WITHIN_TYPE_CAST(0.00005)));
45814593

4582-
// Excel: nper <= 0 -> #NUM!
4583-
CHECK(std::isnan(WITHIN_TYPE_CAST(tep.evaluate("FV(0.05, 0, -100)"))));
4584-
CHECK(std::isnan(WITHIN_TYPE_CAST(tep.evaluate("FV(0.05, -10, -100)"))));
4594+
CHECK_THAT(
4595+
WITHIN_TYPE_CAST(tep.evaluate("FV(-1.5, 10, -100)")),
4596+
Catch::Matchers::WithinRel(
4597+
WITHIN_TYPE_CAST(66.6015625),
4598+
WITHIN_TYPE_CAST(0.00005)));
4599+
4600+
CHECK_THAT(
4601+
WITHIN_TYPE_CAST(tep.evaluate("FV(0.05, 0, -100)")),
4602+
Catch::Matchers::WithinRel(
4603+
WITHIN_TYPE_CAST(0),
4604+
WITHIN_TYPE_CAST(0.00005)));
4605+
4606+
CHECK_THAT(
4607+
WITHIN_TYPE_CAST(tep.evaluate("FV(0.05, -10, -100)")),
4608+
Catch::Matchers::WithinRel(
4609+
WITHIN_TYPE_CAST(-772.173492918482),
4610+
WITHIN_TYPE_CAST(0.00005)));
45854611

45864612
// Non-finite args -> NaN
45874613
CHECK(std::isnan(WITHIN_TYPE_CAST(tep.evaluate("FV(NaN, 10, -100)"))));

tinyexpr.cpp

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@ namespace te_builtins
312312
{
313313
return te_parser::te_nan;
314314
}
315+
315316
if (!std::isfinite(presentValue))
316317
{
317318
presentValue = 0;
@@ -320,16 +321,13 @@ namespace te_builtins
320321
{
321322
type = 0;
322323
}
323-
if (nper <= 0)
324-
{
325-
return te_parser::te_nan;
326-
}
327-
if (rate <= -1.0)
324+
325+
// Excel: nper == 0 is valid
326+
if (nper == 0)
328327
{
329-
return te_parser::te_nan;
328+
return -presentValue;
330329
}
331330

332-
// coerce type to 0 or 1
333331
type = (type != 0) ? 1 : 0;
334332

335333
if (rate == 0.0)
@@ -362,14 +360,18 @@ namespace te_builtins
362360
{
363361
type = 0;
364362
}
365-
if (nper <= 0)
363+
if (nper == 0)
366364
{
367365
return te_parser::te_nan;
368366
}
369-
if (rate <= -1.0)
367+
if (rate < -1.0)
370368
{
371369
return te_parser::te_nan;
372370
}
371+
if (rate == -1.0)
372+
{
373+
return 0.0;
374+
}
373375

374376
// coerce type to 0 or 1
375377
type = (type != 0) ? 1 : 0;
@@ -398,7 +400,6 @@ namespace te_builtins
398400
return te_parser::te_nan;
399401
}
400402

401-
// optional args default like Excel
402403
if (!std::isfinite(futureValue))
403404
{
404405
futureValue = 0;
@@ -408,9 +409,9 @@ namespace te_builtins
408409
type = 0;
409410
}
410411

411-
if (nper <= 0)
412+
if (nper == 0)
412413
{
413-
return te_parser::te_nan;
414+
return 0.0;
414415
}
415416

416417
// Excel: rate <= -1 -> #NUM!
@@ -2672,7 +2673,8 @@ te_expr* te_parser::te_compile(const std::string_view expression, std::set<te_va
26722673
{
26732674
optimize(root);
26742675
}
2675-
catch ([[maybe_unused]] const std::exception& exp)
2676+
catch ([[maybe_unused]]
2677+
const std::exception& exp)
26762678
{
26772679
// parsed OK, but there was an evaluation error;
26782680
// clean up and throw the message back up to compile()

0 commit comments

Comments
 (0)