Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitc05c9c6

Browse files
committed
Add financial functions (EFFECT, NOMINAL, and DB)
1 parent2ac6ed6 commitc05c9c6

File tree

7 files changed

+190
-49
lines changed

7 files changed

+190
-49
lines changed

‎README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ Note: for current users of *TinyExpr++*, please see the [compatibility advisory]
4444
- Simple and fast.
4545
- Implements standard operator precedence.
4646
- Implements logical and comparison operators.
47-
- Exposes standard C math functions (`sin`,`sqrt`,`ln`, etc.), as well as some*Excel*-like functions (e.g.,`AVERAGE()` and`IF()`).
47+
- Includes standard C math functions (`sin`,`sqrt`,`ln`, etc.).
48+
- Includes some*Excel*-like statistical, logical, and financial functions (e.g.,`AVERAGE()` and`IF()`).
4849
- Can add custom functions and variables easily.
4950
- Can bind constants at eval-time.
5051
- Supports variadic functions (taking between 1-24 arguments).

‎TinyExprChanges.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ The following are changes from the original TinyExpr C library:
4646
-`cot`: returns the cotangent of an angle.
4747
-`combin`: alias for`ncr()`, like the*Excel* function.
4848
-`clamp`: constrains a value to a range.
49+
-`db`: returns the depreciation of an asset for a specified period using the fixed-declining balance method.
50+
-`effect`: returns the effective annual interest rate, provided the nominal annual interest rate and the number of compounding periods per year.
4951
-`even`: returns a value rounded up to the nearest even integer.
5052
-`fact`: alias for`fac()`, like the*Excel* function.
5153
-`false`: returns`false` (i.e.,`0`) in a boolean expression.
@@ -58,6 +60,7 @@ The following are changes from the original TinyExpr C library:
5860
-`min`: returns the minimum of a range of values (accepts 1-24 arguments).
5961
-`mod`: returns remainder from a division.
6062
-`nan`: returns`NaN` (i.e., Not-a-Number) in a boolean expression.
63+
-`nominal`: returns the nominal annual interest rate, provided the effective rate and the number of compounding periods per year.
6164
-`odd`: returns a value rounded up to the nearest odd integer.
6265
-`or`: returns true (i.e., non-zero) if any condition is true (accepts 1-24 arguments).
6366
-`not`: returns logical negation of value.

‎docs/manual/embedded-programming.qmd‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ case insensitivity, and bookkeeping operations.
2121

2222
Refer to[compile-time options](#compile-time-options) for flags that can provide optimization.
2323

24-
##Device Compatibility
24+
##Device Compatibility {-}
2525

2626
By default,*TinyExpr++* uses`std::random_device` to seed its random number generator, which may cause compatibility issues with some microcontrollers.
2727
To instead seed it with the current time, compile with`TE_RAND_SEED_TIME`.

‎docs/manual/functions.qmd‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,16 @@ The first argument to any logic function must be valid (i.e., not NaN). If the f
110110
Any subsequent arguments that evaluate to NaN will be ignored.
111111
:::
112112

113+
::: {.minipage data-latex="{\textwidth}"}
114+
| Function| Description|
115+
| :--| :--|
116+
| DB(Cost, Salvage, Lifetime, Period, Month)| Returns the depreciation of an asset for a specified period using the fixed-declining balance method.|
117+
| 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.|
118+
| 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.|
119+
120+
Table: Financial Functions\index{functions!financial}
121+
:::
122+
113123
##Compatibility Note {-}
114124

115125
`BITNOT` will call either`BITNOT32` or`BITNOT64`, depending on whether 64-bit integers\index{data types!\texttt{64-bit integer}} are supported.

‎docs/manual/index.qmd‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ It's open-source, free, easy-to-use, and self-contained in a single source and h
3535
- Simple and fast.
3636
- Implements standard operator precedence.
3737
- Implements logical and comparison operators.
38-
- Exposes standard C math functions (`sin`,`sqrt`,`ln`, etc.), as well as some*Excel*-like functions (e.g.,`AVERAGE()` and`IF()`).
38+
- Includes standard C math functions (`sin`,`sqrt`,`ln`, etc.).
39+
- Includes some*Excel*-like statistical, logical, and financial functions (e.g.,`AVERAGE()` and`IF()`).
3940
- Can add custom functions and variables easily.
4041
- Can add a custom handler to resolve unknown variables.
4142
- Can bind constants at eval-time.

‎tests/tetests.cpp‎

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4010,6 +4010,56 @@ COMBIN(15,
40104010
}
40114011
}
40124012

4013+
// Financial functions
4014+
TEST_CASE("Nominal","[finance]")
4015+
{
4016+
te_parser tep;
4017+
4018+
CHECK_THAT(4,Catch::Matchers::WithinRel(WITHIN_TYPE_CAST(tep.evaluate("NOMINAL(8, 2)"))));
4019+
CHECK_THAT(0.05250032,Catch::Matchers::WithinRel(tep.evaluate("NOMINAL(0.053543, 4)"),0.000001));
4020+
CHECK_THAT(0.00995132100969354,Catch::Matchers::WithinRel(tep.evaluate("=NOMINAL(0.01,50)"),0.000001));
4021+
CHECK_THAT(0.20744331009791,Catch::Matchers::WithinRel(tep.evaluate("=NOMINAL(0.23,50)"),0.000001));
4022+
4023+
CHECK(std::isnan(tep.evaluate("NOMINAL(8, 0)")));
4024+
CHECK(std::isnan(tep.evaluate("NOMINAL(0, 4)")));
4025+
CHECK(std::isnan(tep.evaluate("NOMINAL(-.1, 4)")));
4026+
}
4027+
4028+
TEST_CASE("Effect","[finance]")
4029+
{
4030+
te_parser tep;
4031+
4032+
CHECK_THAT(0.0535427,Catch::Matchers::WithinRel(tep.evaluate("EFFECT(0.0525, 4)"),0.000001));
4033+
CHECK_THAT(0.127340987166906,Catch::Matchers::WithinRel(tep.evaluate("EFFECT(0.12, 52)"),0.000001));
4034+
4035+
CHECK(std::isnan(tep.evaluate("EFFECT(8, 0)")));
4036+
CHECK(std::isnan(tep.evaluate("EFFECT(0, 4)")));
4037+
CHECK(std::isnan(tep.evaluate("EFFECT(-.1, 4)")));
4038+
}
4039+
4040+
TEST_CASE("DB","[finance]")
4041+
{
4042+
te_parser tep;
4043+
4044+
CHECK_THAT(186083.33,Catch::Matchers::WithinRel(tep.evaluate("DB(1000000,100000,6,1,7)"),0.000001));
4045+
CHECK_THAT(259639.42,Catch::Matchers::WithinRel(tep.evaluate("DB(1000000,100000,6,2,7)"),0.000001));
4046+
CHECK_THAT(176814.44,Catch::Matchers::WithinRel(tep.evaluate("DB(1000000,100000,6,3,7)"),0.000001));
4047+
CHECK_THAT(120410.64,Catch::Matchers::WithinRel(tep.evaluate("DB(1000000,100000,6,4,7)"),0.000001));
4048+
CHECK_THAT(81999.64,Catch::Matchers::WithinRel(tep.evaluate("DB(1000000,100000,6,5,7)"),0.000001));
4049+
CHECK_THAT(55841.76,Catch::Matchers::WithinRel(tep.evaluate("DB(1000000,100000,6,6,7)"),0.000001));
4050+
CHECK_THAT(15845.10 ,Catch::Matchers::WithinRel(tep.evaluate("DB(1000000,100000,6,7,7)"),0.000001));
4051+
CHECK_THAT(23632.18,Catch::Matchers::WithinRel(tep.evaluate("DB(1000000,100000,6,7,5)"),0.000001));
4052+
CHECK_THAT(23632.18,Catch::Matchers::WithinRel(tep.evaluate("DB(1000000,100000,6,7,5.9)"),0.000001));
4053+
4054+
CHECK(std::isnan(tep.evaluate("DB(1000000,100000,6,1,0)")));
4055+
CHECK(std::isnan(tep.evaluate("DB(1000000,100000,6,1,13)")));
4056+
CHECK(std::isnan(tep.evaluate("DB(0,100000,6,1,7)")));
4057+
CHECK(std::isnan(tep.evaluate("DB(1000000,0,6,1,13)")));
4058+
CHECK(std::isnan(tep.evaluate("DB(1000000,100000,0,1,13)")));
4059+
CHECK(std::isnan(tep.evaluate("DB(1000000,100000,6,7.5,5)")));
4060+
CHECK(std::isnan(tep.evaluate("DB(1000000,100000,6.5,7,5)")));
4061+
}
4062+
40134063
TEST_CASE("Benchmarks","[!benchmark]")
40144064
{
40154065
te_type benchmarkVar{9 };
@@ -4051,7 +4101,4 @@ TEST_CASE("Benchmarks", "[!benchmark]")
40514101
BENCHMARK("(1/(a+1)+2/(a+2)+3/(a+3)) Native")
40524102
{returnbench_al(benchmarkVar); };
40534103
}
4054-
}// namespace TETesting
4055-
4056-
// NOLINTEND
4057-
// clang-format on
4104+
}

‎tinyexpr.cpp‎

Lines changed: 121 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,124 @@ namespace te_builtins
193193
te_parser::te_nan;
194194
}
195195

196+
/// @warning This version of round emulates Excel's behavior of supporting
197+
/// negative decimal places (e.g., ROUND(21.5, -1) = 20). Be aware
198+
/// of that if using this function outside of TinyExpr++.
199+
[[nodiscard]]
200+
static te_typete_round(te_type val, te_type decimalPlaces)// NOLINT
201+
{
202+
constbool useNegativeRound{ decimalPlaces <0 };
203+
constsize_t adjustedDecimalPlaces{ !std::isfinite(decimalPlaces) ?
204+
0 :
205+
static_cast<size_t>(std::abs(decimalPlaces)) };
206+
207+
constauto decimalPostition =static_cast<te_type>(std::pow(10, adjustedDecimalPlaces));
208+
if (!std::isfinite(decimalPostition))
209+
{
210+
return te_parser::te_nan;
211+
}
212+
constexpr te_type ROUND_EPSILON{0.5 };// NOLINT
213+
214+
if (!useNegativeRound)
215+
{
216+
if (val <0)
217+
{
218+
return (decimalPostition ==0) ?
219+
std::ceil(val - ROUND_EPSILON) :
220+
std::ceil(static_cast<te_type>(val * decimalPostition) - ROUND_EPSILON) /
221+
decimalPostition;
222+
}
223+
return (decimalPostition ==0) ?
224+
std::floor(val + ROUND_EPSILON) :
225+
std::floor(static_cast<te_type>(val * decimalPostition) + ROUND_EPSILON) /
226+
decimalPostition;
227+
}
228+
// ROUND(21.5, -1) = 20
229+
if (val <0)
230+
{
231+
returnstd::ceil(static_cast<te_type>(val / decimalPostition) - ROUND_EPSILON) *
232+
decimalPostition;
233+
}
234+
returnstd::floor(static_cast<te_type>(val / decimalPostition) + ROUND_EPSILON) *
235+
decimalPostition;
236+
}
237+
238+
[[nodiscard]]
239+
static te_typete_nominal(te_type effectiveRate, te_type periods)
240+
{
241+
if (periods <1 || effectiveRate <=0)
242+
{
243+
return te_parser::te_nan;
244+
}
245+
return periods * (std::pow(1 + effectiveRate, (1 / periods)) -1);
246+
}
247+
248+
[[nodiscard]]
249+
static te_typete_effect(te_type nomicalRate, te_type periods)
250+
{
251+
if (periods <1 || nomicalRate <=0)
252+
{
253+
return te_parser::te_nan;
254+
}
255+
returnstd::pow(1 + (nomicalRate / periods), periods) -1;
256+
}
257+
258+
[[nodiscard]]
259+
static te_typete_asset_depreciation(te_type cost, te_type salvage,
260+
te_type life, te_type period,
261+
te_type month)
262+
{
263+
if (!std::isfinite(month))
264+
{
265+
month =12;
266+
}
267+
elseif (month <1 || month >12 || life <=0 || cost <=0 || period <1)
268+
{
269+
return te_parser::te_nan;
270+
}
271+
272+
te_type intPrefix;
273+
te_type mantissa =std::modf(life, &intPrefix) *100;
274+
if (mantissa >0)
275+
{
276+
return te_parser::te_nan;
277+
}
278+
mantissa =std::modf(period, &intPrefix) *100;
279+
if (mantissa >0)
280+
{
281+
return te_parser::te_nan;
282+
}
283+
284+
// month gets rounded down in spreadsheet programs
285+
month =std::floor(static_cast<te_type>(month));
286+
287+
// we just verified that this are integral, but round down to fully ensure that
288+
life =std::floor(static_cast<te_type>(life));
289+
period =std::floor(static_cast<te_type>(period));
290+
291+
// rate gets clipped to three-decimal precision according to Excel docs
292+
constauto rate =te_round(1 - (std::pow((salvage / cost), (1 / life))),3);
293+
if (period ==1)
294+
{
295+
return cost * rate * (month /12);
296+
}
297+
else
298+
{
299+
te_type priorDepreciation{0.0 };
300+
te_type costAfterDepreciation{ cost };
301+
for (uint64_t i =1; i <static_cast<uint64_t>(period) -1; ++i)
302+
{
303+
auto depreciation = (costAfterDepreciation * rate);
304+
priorDepreciation += depreciation;
305+
costAfterDepreciation -= depreciation;
306+
}
307+
priorDepreciation += costAfterDepreciation * rate * (month /12);
308+
return (period == life +1) ?
309+
((cost - priorDepreciation) * rate * (12 - month)) /12 :
310+
(cost - priorDepreciation) * rate;
311+
}
312+
}
313+
196314
[[nodiscard]]
197315
constexprstatic te_typete_pi()noexcept
198316
{
@@ -437,48 +555,6 @@ namespace te_builtins
437555
returnte_divide(total,static_cast<te_type>(validN));
438556
}
439557

440-
/// @warning This version of round emulates Excel's behavior of supporting
441-
/// negative decimal places (e.g., ROUND(21.5, -1) = 20). Be aware
442-
/// of that if using this function outside of TinyExpr++.
443-
[[nodiscard]]
444-
static te_typete_round(te_type val, te_type decimalPlaces)// NOLINT
445-
{
446-
constbool useNegativeRound{ decimalPlaces <0 };
447-
constsize_t adjustedDecimalPlaces{ !std::isfinite(decimalPlaces) ?
448-
0 :
449-
static_cast<size_t>(std::abs(decimalPlaces)) };
450-
451-
constauto decimalPostition =static_cast<te_type>(std::pow(10, adjustedDecimalPlaces));
452-
if (!std::isfinite(decimalPostition))
453-
{
454-
return te_parser::te_nan;
455-
}
456-
constexpr te_type ROUND_EPSILON{0.5 };// NOLINT
457-
458-
if (!useNegativeRound)
459-
{
460-
if (val <0)
461-
{
462-
return (decimalPostition ==0) ?
463-
std::ceil(val - ROUND_EPSILON) :
464-
std::ceil(static_cast<te_type>(val * decimalPostition) - ROUND_EPSILON) /
465-
decimalPostition;
466-
}
467-
return (decimalPostition ==0) ?
468-
std::floor(val + ROUND_EPSILON) :
469-
std::floor(static_cast<te_type>(val * decimalPostition) + ROUND_EPSILON) /
470-
decimalPostition;
471-
}
472-
// ROUND(21.5, -1) = 20
473-
if (val <0)
474-
{
475-
returnstd::ceil(static_cast<te_type>(val / decimalPostition) - ROUND_EPSILON) *
476-
decimalPostition;
477-
}
478-
returnstd::floor(static_cast<te_type>(val / decimalPostition) + ROUND_EPSILON) *
479-
decimalPostition;
480-
}
481-
482558
// Combinations (without repetition)
483559
[[nodiscard]]
484560
static te_typete_ncr(te_type val1, te_type val2)noexcept
@@ -1368,7 +1444,9 @@ const std::set<te_variable> te_parser::m_functions = { // NOLINT
13681444
{"cos",static_cast<te_fun1>(te_builtins::te_cos), TE_PURE },
13691445
{"cosh",static_cast<te_fun1>(te_builtins::te_cosh), TE_PURE },
13701446
{"cot",static_cast<te_fun1>(te_builtins::te_cot), TE_PURE },
1447+
{"db",static_cast<te_fun5>(te_builtins::te_asset_depreciation), TE_PURE },
13711448
{"e",static_cast<te_fun0>(te_builtins::te_e), TE_PURE },
1449+
{"effect",static_cast<te_fun2>(te_builtins::te_effect), TE_PURE },
13721450
{"even",static_cast<te_fun1>(te_builtins::te_even), TE_PURE },
13731451
{"exp",static_cast<te_fun1>(te_builtins::te_exp), TE_PURE },
13741452
{"fac",static_cast<te_fun1>(te_builtins::te_fac), TE_PURE },
@@ -1390,6 +1468,7 @@ const std::set<te_variable> te_parser::m_functions = { // NOLINT
13901468
{"mod",static_cast<te_fun2>(te_builtins::te_modulus), TE_PURE },
13911469
{"nan",static_cast<te_fun0>(te_builtins::te_nan_value), TE_PURE },
13921470
{"ncr",static_cast<te_fun2>(te_builtins::te_ncr), TE_PURE },
1471+
{"nominal",static_cast<te_fun2>(te_builtins::te_nominal), TE_PURE },
13931472
{"not",static_cast<te_fun1>(te_builtins::te_not), TE_PURE },
13941473
{"npr",static_cast<te_fun2>(te_builtins::te_npr), TE_PURE },
13951474
{"odd",static_cast<te_fun1>(te_builtins::te_odd), TE_PURE },

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp