[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[lmi-commits] [lmi] master bcd3f13 5/6: Improve i7702 trace
From: |
Greg Chicares |
Subject: |
[lmi-commits] [lmi] master bcd3f13 5/6: Improve i7702 trace |
Date: |
Tue, 16 Mar 2021 20:20:11 -0400 (EDT) |
branch: master
commit bcd3f13063e9cf2b2da1266f544844cf991cd113
Author: Gregory W. Chicares <gchicares@sbcglobal.net>
Commit: Gregory W. Chicares <gchicares@sbcglobal.net>
Improve i7702 trace
Soon the NAAR discount for DCV will change, causing DCV to change in
ways that pose a bit of a regression-testing challenge. To lighten that
burden, display more details.
Show 'ic' on both an annual basis (where ten decimals more than suffice)
and a monthly basis (where many decimals are wanted).
Show 'ig' on a monthly basis only, because that's the only way it makes
sense to calculate it (see the inline documentation).
Show 'Em_' next to 'ig_usual_' because those are the monthly interest
rates used for discounting NAAR (for account values, and for DCV,
respectively). Note that this "discount" is not 'd' = i/(1+i), the rate
of discount described in compound-interest textbooks; instead, it's in
the nature of 'v' = 1/(1+i), on a monthly basis:
mort_chg = q12 * (1/(1+i)^(1/12) * db - av)
A factor closer to one means higher mortality charges and lower values,
which is generally desirable for DCV; 'v' is closer to one when 'i' is
closer to zero.
For example, interest rates of
{4%, 3%, 2.5%, 1.5%, 1%, 0%}
with DB_NaarDiscount values of
0.0032737, {0.0024663, 0.00246627}, 0.0020598, 0.0012415, 0.0008295, 0
(note two different rounded values for 3%) produce the following factors
for {account values; DCV; their difference} for a representative group
of real-world contracts:
0.9967369821 0.9967369426 0.0000000395 4%, 4%
0.9975397677 0.9967369426 0.0008028251 3%, 4% [0.0024663]
0.9975397975 0.9967369426 0.0008028549 3%, 4% [0.00246627]
0.9975397975 0.9975397975 0.0000000000 3%, 3%
0.9979444341 0.9967369426 0.0012074914 2.5%, 4%
0.9987600394 0.9967369426 0.0020230968 1.5%, 4%
0.9991711875 0.9967369426 0.0024342449 1%, 4%
0.9991711875 0.9983511419 0.0008200456 1%, 2%
1.0000000000 1.0000000000 0.0000000000 [no discount]
where of course Em_ and ig_usual_ are often based on different interest
rates (statutory vs. contractual) and Em_ is typically rounded whereas
lmi never rounds ig_usual_ .
For the two rows with zero difference, no regression will be seen. It
is interesting that this occurs in the fourth row, where the statutory
rate is actually 2%; that's 'sample2xyz', for which the trace shows
0.00246627000000000 Em_[0]
0.00246627000000000 ig_usual_[0]
so the rate for discounting DCV NAAR is the 3% guaranteed rate.
The first row is interesting because its "difference" is so small. Here,
i = 4% and its monthly equivalent is 0.0032737397821989145..., which was
rounded down to 0.0032737 in the contract. As the inline documentation
observes:
/// E: NAAR discount (given here as i, the annual rate of interest)
/// often specified in contract as Bgen upper 12 / 12
/// if monthly contract factor rounded down, Bgen governs instead
/// (slightly better 7702 outcome in that case)
The DCV calculation uses the higher unrounded rate, which makes DCV
mortality charges lower, and DCV itself higher, compared to using the
rounded-down contractual rate. Doesn't that contradict the discussion
above, which said that a lower DCV would be preferable? No. Actually,
the choice is among these monthly rates:
(X) 0.0032737 (not kosher)
(Y) 0.0032737397821989145
(Z) 0.0032738 [if contract rounded up]
If the contract rounded up, lmi would use the higher rate (Z), making
the DCV higher, which is less desirable. If the contract rounds down,
or if it doesn't specify a rounded value, then lmi uses rate (Y), with
a slightly more desirable outcome than (Z). And (Y) is an impeccable
choice in this instance: the contract guarantees 4%, the law mandates
4%, and (Y) is the closest representable floating-point value for the
twelfth root of 1.04 . It is possible to construct a rationale for using
(X), but it corresponds to i = 0.0399995056587203, which the revenue
service could claim is less than the statutory 4% minimum; why take any
risk for such a small gain?
---
i7702.cpp | 44 ++++++++++++++++++++++++++++++++++++--------
1 file changed, 36 insertions(+), 8 deletions(-)
diff --git a/i7702.cpp b/i7702.cpp
index 13fd513..41068e5 100644
--- a/i7702.cpp
+++ b/i7702.cpp
@@ -29,7 +29,9 @@
#include "miscellany.hpp" // each_equal()
#include "ssize_lmi.hpp"
+#include <ios> // fixed, ios_base::precision() 7702
!! pyx
#include <iostream> // 7702 !! pyx
+#include <sstream> // 7702 !! pyx
/// Here's how lmi determines §7702 and §7702A interest rates.
///
@@ -280,35 +282,43 @@ void i7702::initialize()
);
// 7702 !! temporary--for acceptance testing
+ // Use a temporary stream to avoid changing std::cout's flags.
if(trace_)
{
- std::cout
+ std::ostringstream oss;
+ oss.precision(10);
+ oss << std::fixed;
+ oss
<< "statutory rates {GLP,GSP}\n"
<< A0_ << " A0_\n"
<< A1_ << " A1_\n"
+ << "first-year {B,C,D} with row conditions\n"
+ << " "
<< Bgen_[0] << "\t"
<< Cgen_[0] << "\t"
<< Dgen_[0] << "\tif "
- << use_gen_[0] << " general account\n"
+ << static_cast<bool>(use_gen_[0]) << " general account\n"
+ << " "
<< Bsep_[0] << "\t"
<< Csep_[0] << "\t"
<< Dsep_[0] << "\tif "
- << use_sep_[0] << " separate account\n"
+ << static_cast<bool>(use_sep_[0]) << " separate account\n"
+ << " "
<< Bflr_[0] << "\t"
<< Cflr_[0] << "\t"
<< Dflr_[0] << "\tif "
- << use_flr_[0] << " fixed loan rate\n"
+ << static_cast<bool>(use_flr_[0]) << " fixed loan rate\n"
+ << " "
<< Bvlr_[0] << "\t"
<< Cvlr_[0] << "\t"
<< Dvlr_[0] << "\tif "
- << use_vlr_[0] << " variable loan rate\n"
- << "monthly NAAR discount\n"
- << Em_[0] << " Em_[0]\n"
+ << static_cast<bool>(use_vlr_[0]) << " variable loan rate\n"
+ << "annual rates\n"
<< ic_usual_[0] << " ic_usual_[0]\n"
<< ic_glp_ [0] << " ic_glp_ [0]\n"
<< ic_gsp_ [0] << " ic_gsp_ [0]\n"
- << std::endl
;
+ std::cout << oss.str() << std::endl;
}
// Convert all to monthly.
@@ -322,4 +332,22 @@ void i7702::initialize()
ig_glp_ += max(ic_glp_ , Em_);
ig_gsp_ += max(ic_gsp_ , Em_);
}
+
+ if(trace_)
+ {
+ std::ostringstream oss;
+ oss.precision(17);
+ oss << std::fixed;
+ oss
+ << "monthly rates\n"
+ << ic_usual_[0] << " ic_usual_[0]\n"
+ << ic_glp_ [0] << " ic_glp_ [0]\n"
+ << ic_gsp_ [0] << " ic_gsp_ [0]\n"
+ << Em_[0] << " Em_[0]\n"
+ << ig_usual_[0] << " ig_usual_[0]\n"
+ << ig_glp_ [0] << " ig_glp_ [0]\n"
+ << ig_gsp_ [0] << " ig_gsp_ [0]\n"
+ ;
+ std::cout << oss.str() << std::endl;
+ }
}
- [lmi-commits] [lmi] master updated (60d683a -> f2a0fce), Greg Chicares, 2021/03/16
- [lmi-commits] [lmi] master e52df17 1/6: Establish a data member to control i7702 trace, Greg Chicares, 2021/03/16
- [lmi-commits] [lmi] master 468d06c 3/6: Record speed measurements, Greg Chicares, 2021/03/16
- [lmi-commits] [lmi] master bcd3f13 5/6: Improve i7702 trace,
Greg Chicares <=
- [lmi-commits] [lmi] master f2a0fce 6/6: Fix defect introduced 20050114T1947Z: wrong DCV NAAR discount, Greg Chicares, 2021/03/16
- [lmi-commits] [lmi] master 790cfc3 4/6: Expunge DB_BonusInt, Greg Chicares, 2021/03/16
- [lmi-commits] [lmi] master b677016 2/6: Add a 7702i unit test, Greg Chicares, 2021/03/16