[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[lmi-commits] [lmi] master f97235a: Work around a corridor-specamt round
From: |
Greg Chicares |
Subject: |
[lmi-commits] [lmi] master f97235a: Work around a corridor-specamt rounding issue |
Date: |
Tue, 4 Aug 2020 13:12:44 -0400 (EDT) |
branch: master
commit f97235aed48a5217a456690be1b4ae127792c4e9
Author: Gregory W. Chicares <gchicares@sbcglobal.net>
Commit: Gregory W. Chicares <gchicares@sbcglobal.net>
Work around a corridor-specamt rounding issue
This alternative [lines split to fit in git log]:
+ // Multiplying by one minus long double epsilon doesn't
+ // have the desired effect.
+ static double const z = \
1.0 - std::numeric_limits<double>::epsilon();
+ return round_min_specamt() \
(z * annualized_pmt * GetCorridorFactor()[0]);
- return round_min_specamt() \
(annualized_pmt * GetCorridorFactor()[0]);
would produce the same desired outcome in the (proprietary) testcase
used for acceptance testing, but the committed method is better because
it addresses the root cause instead of introducing a countervailing
adjustment.
---
ihs_basicval.cpp | 37 ++++++++++++++++++++++++++++++++++++-
1 file changed, 36 insertions(+), 1 deletion(-)
diff --git a/ihs_basicval.cpp b/ihs_basicval.cpp
index 35992ff..29ae765 100644
--- a/ihs_basicval.cpp
+++ b/ihs_basicval.cpp
@@ -49,6 +49,7 @@
#include "outlay.hpp"
#include "premium_tax.hpp"
#include "rounding_rules.hpp"
+#include "stl_extensions.hpp" // nonstd::power()
#include "stratified_charges.hpp"
#include "value_cast.hpp"
@@ -1434,10 +1435,44 @@ double BasicValues::GetModalSpecAmtGSP(double
annualized_pmt) const
/// Only the initial corridor factor is used here, because this
/// strategy makes sense only at issue. Thus, arguments should
/// represent initial premium and mode.
+///
+/// Corridor factors are stored as double-precision values, but in
+/// practice they usually represent whole-number percentages, which
+/// are typically printed for each year on policy schedule pages.
+/// For GPT, they're defined that way by statute; for CVAT, by
+/// convention. Accordingly, round_corridor_factor() rounds to the
+/// nearest two decimals for all known products, although it would
+/// not be inconceivable to round to three, for example.
+///
+/// Extra care must be taken in this function, because the specified
+/// amount is often rounded to whole dollars (a finer granularity such
+/// as cents being used for all other values). Consider this case:
+/// $100000 initial payment
+/// 490% corridor
+/// Using 'gnumeric' to view lmi's monthly detail, 4.9 is stored as:
+/// 4.9000000000000003552713679
+/// 0 0000000011111111
+/// 1 2345678901234567 <-- differs in seventeenth significant digit
+/// which is presumably the closest representable value. Multiplying
+/// that value by 100000 and rounding up to the next dollar gives
+/// 490001, but 490000 is desired. To that end, the corridor factor is
+/// first scaled by the appropriate power of ten (by one hundred in
+/// typical practice) and rounded to integer to recapture the intended
+/// value.
+///
+/// Of course, lmi would allow a table of corridor factors to have,
+/// say, seven decimal digits, so rounding might give an undesired
+/// answer even with a payment that exceeds the above example's 10^5
+/// by a factor of one plus epsilon. Until such amounts are stored as
+/// integral cents, this implementation cannot guarantee to give the
+/// desired answer in every case.
double BasicValues::GetModalSpecAmtCorridor(double annualized_pmt) const
{
- return round_min_specamt()(annualized_pmt * GetCorridorFactor()[0]);
+ int const k = round_corridor_factor().decimals();
+ double const s = nonstd::power(10, k);
+ double const z = std::round(s * GetCorridorFactor()[0]);
+ return round_min_specamt()((z * annualized_pmt) / s);
}
/// Calculate specified amount based on salary.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [lmi-commits] [lmi] master f97235a: Work around a corridor-specamt rounding issue,
Greg Chicares <=