lmi-commits
[Top][All Lists]
Advanced

[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;
+        }
 }



reply via email to

[Prev in Thread] Current Thread [Next in Thread]