[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[lmi-commits] [lmi] odd/eraseme_error ee0dc1c 2/9: Fix the problem in a
From: |
Greg Chicares |
Subject: |
[lmi-commits] [lmi] odd/eraseme_error ee0dc1c 2/9: Fix the problem in a slapdash manner |
Date: |
Thu, 8 Jul 2021 16:26:47 -0400 (EDT) |
branch: odd/eraseme_error
commit ee0dc1c7f63b4da98bc32924231ad023525550e9
Author: Gregory W. Chicares <gchicares@sbcglobal.net>
Commit: Gregory W. Chicares <gchicares@sbcglobal.net>
Fix the problem in a slapdash manner
At an interest rate of i, standardly defining v=1/(1+i),
calculate future value of a stream of payments P1...Pn as
Pn * (1+i)^1 + Pn-1 * (1+i)^2 ... + P1 * (1+i)^n
instead of
(Pn * v^n + Pn-1 * v^(n-1) ... + P1 * v^1) / v^(n+1)
pointedly avoiding the NaNs that result from using the former
formula at i = -100% --> v = 1/(1-1).
The unit test demonstrates a worthwhile speed improvement:
Speed test: vector of irrs, length 100, 5 decimals
[before this commit]
i686 4.902e-03 s mean; 4521 us least of 100 runs
x86_64 3.128e-03 s mean; 3116 us least of 100 runs
[after this commit]
i686 1.488e-03 s mean; 1113 us least of 100 runs
x86_64 1.491e-03 s mean; 1476 us least of 100 runs
---
financial.hpp | 50 +++++++++++++++++++++++++++++++++++++++++++++-----
financial_test.cpp | 44 ++++++++++++++++++++++++++++++++++++--------
2 files changed, 81 insertions(+), 13 deletions(-)
diff --git a/financial.hpp b/financial.hpp
index 70b8938..5d5bc89 100644
--- a/financial.hpp
+++ b/financial.hpp
@@ -24,8 +24,10 @@
#include "config.hpp"
+#include "alert.hpp"
#include "assert_lmi.hpp"
#include "bourn_cast.hpp"
+#include "materially_equal.hpp"
#include "mc_enum_type_enums.hpp" // mcenum_mode
#include "miscellany.hpp" // ios_out_app_binary()
#include "zero.hpp"
@@ -72,6 +74,39 @@ long double fv
}
template<typename InputIterator>
+long double future_value
+ (InputIterator first
+ ,InputIterator last
+ ,long double i
+ )
+{
+ if(first == last)
+ {
+ return 0.0L;
+ }
+ // v = 1/(1+i) is standard; u = 1+i should be, too
+ // For i=-100%, this deliberately returns zero!
+ long double const u = 1.0L + i;
+ long double un = 1.0L;
+ long double z = 0.0L;
+ for(InputIterator j = last; j != first;)
+ {
+ --j;
+ un *= u;
+ z += *j * un;
+ }
+ warning().precision(21);
+#if 0
+ auto y = fv(first, last, i);
+ if(1.0e-10 < std::fabs(z - y) || !materially_equal(z, y))
+ warning() << "UNEQUAL:\n " << z << "\n " << y << LMI_FLUSH;
+// LMI_ASSERT(std::fabs(z - y) < 1.0e-13);
+ LMI_ASSERT(materially_equal(z, y));
+#endif // 0
+ return z;
+}
+
+template<typename InputIterator>
long double npv
(InputIterator first
,InputIterator last
@@ -112,19 +147,19 @@ class irr_helper
long double operator()(long double i)
{
- return fv(first_, last_, i) - x_;
+ return future_value(first_, last_, i) - x_;
}
long double operator()()
{
std::ofstream ofs_trace;
- ofs_trace.open("trace_irr.txt", ios_out_app_binary());
+////ofs_trace.open("trace_irr.txt", ios_out_app_binary());
std::ostream os_trace(ofs_trace.rdbuf());
root_type const z = decimal_root
(*this
,-1.0 // A priori lower bound.
,1000.0 // Assumed upper bound.
- ,bias_lower // Return the final bound with the lower fv.
+ ,bias_lower // Return the final bound with the lower FV.
,decimals_
,os_trace
);
@@ -134,7 +169,12 @@ class irr_helper
{return z.root;}
// Return -100% if NPVs of a priori bounds have same sign.
case root_not_bracketed:
- {return -1.0L;}
+// {return -1.0L;}
+ {
+ os_trace << "ROOT NOT BRACKETED\n" << std::endl;
+// return z.root;
+ return 1234.5678;
+ }
case improper_bounds:
{throw "IRR: improper bounds.";}
}
@@ -166,7 +206,7 @@ long double irr
,int decimals
)
{
- return irr_helper<InputIterator>(first, last, 0.0L, decimals)();
+ return irr_helper<InputIterator>(first, last - 1, -*(last - 1),
decimals)();
}
template
diff --git a/financial_test.cpp b/financial_test.cpp
index 0a5630d..e4a1a63 100644
--- a/financial_test.cpp
+++ b/financial_test.cpp
@@ -39,28 +39,56 @@ int test_main(int, char*[])
double pmts[3] = {100.0, 200.0, 300.0};
double bfts[3] = {300.0, 1500.0, 5400.0};
+ std::cout << fv(pmts + 0, pmts + 1, 0.04) << std::endl;
+ std::cout << future_value(pmts + 0, pmts + 1, 0.04) << std::endl;
+
+ std::cout << fv(pmts + 0, pmts + 2, 0.04) << std::endl;
+ std::cout << future_value(pmts + 0, pmts + 2, 0.04) << std::endl;
+
+ std::cout << fv(pmts + 0, pmts + 3, 0.04) << std::endl;
+ std::cout << future_value(pmts + 0, pmts + 3, 0.04) << std::endl;
+
// The next few tests compare floating-point quantities for exact
// equality. Often that's inappropriate; however, the quantities
// are integer-valued and the algorithm is designed to round them
// exactly.
irr_helper<double*> xxx(pmts, pmts + 1, bfts[0], 5);
- LMI_TEST(2.0 == xxx());
+ LMI_TEST_EQUAL(2.0, xxx());
- LMI_TEST(( 2.0 == irr_helper<double*>(pmts, pmts + 1, bfts[0], 5)()));
+ LMI_TEST_EQUAL( 2.0, irr_helper<double*>(pmts, pmts + 1, bfts[0], 5)());
- LMI_TEST(( 2.0 == irr_helper<double*>(pmts, pmts + 3, bfts[2], 5)()));
+ LMI_TEST_EQUAL( 2.0, irr_helper<double*>(pmts, pmts + 3, bfts[2], 5)());
- LMI_TEST((-1.0 == irr_helper<double*>(pmts, pmts + 3, 0.0 , 5)()));
+ LMI_TEST_EQUAL(-1.0, irr_helper<double*>(pmts, pmts + 3, 0.0 , 5)());
// Test with arrays.
double cash_flows[4] = {pmts[0], pmts[1], pmts[2], -bfts[2]};
- LMI_TEST(2.0 == irr(cash_flows, 4 + cash_flows, 5));
+ LMI_TEST_EQUAL(2.0, irr(cash_flows, 4 + cash_flows, 5));
+ std::cout
+ << cash_flows[0] << "\n"
+ << cash_flows[1] << "\n"
+ << cash_flows[2] << "\n"
+ << cash_flows[3] << "\n"
+ << std::endl
+ ;
+ std::cout << fv(cash_flows + 0, cash_flows + 4, 0.04) << std::endl;
+ std::cout << future_value(cash_flows + 0, cash_flows + 4, 0.04) <<
std::endl;
+ std::cout << "fv of 'cash_flows' at i = -1, 0, 1:" << std::endl;
+ std::cout << future_value(cash_flows + 0, cash_flows + 4, -1.0 ) <<
std::endl;
+ std::cout << future_value(cash_flows + 0, cash_flows + 4, 0.0 ) <<
std::endl;
+ std::cout << future_value(cash_flows + 0, cash_flows + 4, 1.0 ) <<
std::endl;
+//return 0;
// Test with vectors.
std::vector<double> v(cash_flows, 4 + cash_flows);
- LMI_TEST(2.0 == irr(v.begin(), v.end(), 0.0, 5));
- LMI_TEST(2.0 == irr(v.begin(), v.end(), 5));
+ // The next test is begging to fail. It was designed so that 200%
+ // is a root; but appending a zero at the end of the stream makes
+ // -100% a root, and arguably a preferable one. Reason: any stream
+ // of payments accumulates to a future value of zero at a rate of
+ // -100%.
+ LMI_TEST_EQUAL(-1.0, irr(v.begin(), v.end(), 0.0, 5));
+ LMI_TEST_EQUAL(2.0, irr(v.begin(), v.end(), 5));
std::vector<double> p; // Payments.
std::vector<double> b; // Benefits.
@@ -184,7 +212,7 @@ int test_main(int, char*[])
// but, given that we must return only one, wouldn't some
// value like 0% or -100% be more suitable?
irr(p0, b0, r0, p0.size(), p0.size(), decimals);
- LMI_TEST_EQUAL(r0[3], 1000);
+ LMI_TEST_EQUAL(r0[3], -1);
// Test fv().
- [lmi-commits] [lmi] odd/eraseme_error updated (69d06e9 -> 58863bf), Greg Chicares, 2021/07/08
- [lmi-commits] [lmi] odd/eraseme_error 35f8824 1/9: Trace IRR calculations, Greg Chicares, 2021/07/08
- [lmi-commits] [lmi] odd/eraseme_error 7b5c07f 6/9: Move and rename a function, Greg Chicares, 2021/07/08
- [lmi-commits] [lmi] odd/eraseme_error 6c0d108 4/9: Add a couple obvious tests, Greg Chicares, 2021/07/08
- [lmi-commits] [lmi] odd/eraseme_error 95690a5 5/9: Remove a confusing overload, Greg Chicares, 2021/07/08
- [lmi-commits] [lmi] odd/eraseme_error cf5cced 7/9: Re-break; fail better, Greg Chicares, 2021/07/08
- [lmi-commits] [lmi] odd/eraseme_error 273f15b 8/9: Restore un-breakage, Greg Chicares, 2021/07/08
- [lmi-commits] [lmi] odd/eraseme_error 58863bf 9/9: Improve speed measurements, Greg Chicares, 2021/07/08
- [lmi-commits] [lmi] odd/eraseme_error 05e74a5 3/9: Clean up, Greg Chicares, 2021/07/08
- [lmi-commits] [lmi] odd/eraseme_error ee0dc1c 2/9: Fix the problem in a slapdash manner,
Greg Chicares <=