lmi-commits
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[lmi-commits] [lmi] valyuta/002 a32e29b 53/65: Avoid implicit conversion


From: Greg Chicares
Subject: [lmi-commits] [lmi] valyuta/002 a32e29b 53/65: Avoid implicit conversions, whose (hidden) cost is very high
Date: Wed, 16 Sep 2020 16:55:21 -0400 (EDT)

branch: valyuta/002
commit a32e29bb3cb728eb63687ed81e54f33e5da4a428
Author: Gregory W. Chicares <gchicares@sbcglobal.net>
Commit: Gregory W. Chicares <gchicares@sbcglobal.net>

    Avoid implicit conversions, whose (hidden) cost is very high
---
 README.branch.patch       |  77 +++++++++++
 accountvalue.cpp          |  65 +++++-----
 basicvalues.cpp           |   4 +-
 currency.hpp              |   8 +-
 gpt_specamt.cpp           |   6 +-
 ihs_acctval.cpp           | 165 ++++++++++++------------
 ihs_avdebug.cpp           |   8 +-
 ihs_avmly.cpp             | 318 +++++++++++++++++++++++-----------------------
 ihs_avsolve.cpp           |  19 +--
 ihs_avstrtgy.cpp          |  10 +-
 ihs_basicval.cpp          |  38 +++---
 ledger_invariant_init.cpp |   4 +-
 ledgervalues.cpp          |   4 +-
 solve.cpp                 |  18 +--
 14 files changed, 416 insertions(+), 328 deletions(-)

diff --git a/README.branch.patch b/README.branch.patch
index 744b789..8f426c7 100644
--- a/README.branch.patch
+++ b/README.branch.patch
@@ -1,3 +1,80 @@
+Make conversions explicit, e.g.:
+  currency amount;
+- amount = 3.14;           // implicit
++ amount = currency(3.14); // explicit
+  double floating;
+- floating = amount;       // was implicit
++ floating = amount.d();
+That may seem like mere ugly verbosity, but...
+- amount = 0.0;            // (integer)(round(100.0 * 0.0))
++ amount = currency();     // integer = 0
++ currency amount {};      // even better, where possible
+- if(0.0 == amount)        // 0.0 == (double)(amount / 100.0)
++ if(currency() == amount) // integer == integer
+A user-defined literals like "0_c" could replace
+
+Removed the very costly implicit conversions:
+    currency& operator=(double d)
+    operator double() const
+but allowed
+    currency& operator=(int i) // assigns 100.0 * argument
+
+Because using integer math means not having to worry about catastrophic
+cancellation, made improvements like this:
+
+- LMI_ASSERT(materially_equal(GrossPmts[j], EeGrossPmts[j] + ErGrossPmts[j]));
++ LMI_ASSERT(GrossPmts[j] == EeGrossPmts[j] + ErGrossPmts[j]);
+
+     double max_loan =
+          AVUnloaned.d() * 0.9
+        + (AVRegLn + AVPrfLn).d()
+        - RegLnBal.d() * i_reg
+        - PrfLnBal.d() * i_prf
+        - mlydedtonextmodalpmtdate.d()
+        ;
+
+random notes--further cleanup and other future tasks:
+
++    if(currency() == RegLnBal && 0.0 == PrfLnBal)
+                                  ^^^ should be "currency()"
+
+max<double>...should rarely if ever need to specify template parameter
+
+should be currency():
++    AVUnloaned               = 0;          // Antediluvian.
+
+unary operator-()
+
+loads class should use currency type for policy fees
+
+probably DCV should be currency instead of double
+
+Solves are still a puzzle: is it inherently slower to find an
+integral root than a floating-point root of a polynomial? That
+question is crucial because making solves dramatically slower
+could mean that a currency class is unaffordable.
+
+After the changes in this commit:
+
+  naic, no solve      : 1.085e-01 s mean;     103995 us least of  10 runs
+  naic, specamt solve : 1.798e-01 s mean;     179138 us least of   6 runs
+  naic, ee prem solve : 1.672e-01 s mean;     166277 us least of   6 runs
+  finra, no solve     : 2.711e-02 s mean;      26542 us least of  37 runs
+  finra, specamt solve: 9.375e-02 s mean;      93087 us least of  11 runs
+  finra, ee prem solve: 8.994e-02 s mean;      89230 us least of  12 runs
+
+which is about ten percent faster for "finra, no solve", the
+only scenario that involves no explicit or implicit solve,
+so these improvements are valuable even if making all the
+conversions explicit impairs readability somewhat. Still,
+solves need to be looked at. Compared to production, even with
+these changes the currency branch is thirty percent slower for the
+"finra, no solve" case (yet further improvements might bring that
+into an acceptable range), but only half as fast for the "naic"
+scenarios (which involve solves).
+
+//
+
 As of
   commit 7bac50c7154a419ff93a1398313dc7fe6d9fbed5 (HEAD -> valyuta/002)
   Date:   2020-09-13T23:50:07+00:00
diff --git a/accountvalue.cpp b/accountvalue.cpp
index 21c475e..e8bbe46 100644
--- a/accountvalue.cpp
+++ b/accountvalue.cpp
@@ -228,19 +228,19 @@ currency AccountValue::RunOneCell(mcenum_run_basis 
TheBasis)
 
     InforceYear      = yare_input_.InforceYear;
     InforceMonth     = yare_input_.InforceMonth;
-    InforceAVGenAcct = yare_input_.InforceGeneralAccountValue;
+    InforceAVGenAcct = currency(yare_input_.InforceGeneralAccountValue);
 
     ItLapsed         = false;
     LapseMonth       = 0;
     LapseYear        = 0;
 
-    TaxBasis         = 0.0;
+    TaxBasis         = currency();
 
-    MaxLoan          = 0.0;
-    RegLnBal         = 0.0;
-    PrfLnBal         = 0.0;
-    AVRegLn          = 0.0;
-    AVPrfLn          = 0.0;
+    MaxLoan          = currency();
+    RegLnBal         = currency();
+    PrfLnBal         = currency();
+    AVRegLn          = currency();
+    AVPrfLn          = currency();
 
     // 'InforceAVGenAcct' is unloaned only; this branch wasn't
     // designed to allow inforce loans.
@@ -290,7 +290,7 @@ void AccountValue::DoYear
 
     pmt                   = stored_pmts[Year];
     YearsPremLoadTgt      = Loads_->target_premium_load(GenBasis_)[Year];
-    YearsMonthlyPolicyFee = Loads_->monthly_policy_fee(GenBasis_)[Year];
+    YearsMonthlyPolicyFee = 
currency(Loads_->monthly_policy_fee(GenBasis_)[Year]);
     ActualSpecAmt         = base_specamt(Year);
 
     // These variables are set for each pass independently.
@@ -345,16 +345,16 @@ void AccountValue::DoYear
             }
         }
 
-    VariantValues().AcctVal[Year] = AVUnloaned + AVRegLn + AVPrfLn;
+    VariantValues().AcctVal[Year] = (AVUnloaned + AVRegLn + AVPrfLn).d();
     VariantValues().CSVNet[Year] = VariantValues().AcctVal[Year] - 
VariantValues().SurrChg[Year];
     // Update death benefit: "deathbft" currently holds benefit as of the
     //   beginning of month 12, but we want it as of the end of that month,
     //   in case the corridor or option 2 drove it up during the last month.
     TxSetDeathBft();
-    VariantValues().EOYDeathBft[Year] = deathbft;
+    VariantValues().EOYDeathBft[Year] = deathbft.d();
 
     // IHS !! Change one of these names, which differ only in the terminal 's'.
-    InvariantValues().GrossPmt[Year] += std::accumulate(GrossPmts.begin(), 
GrossPmts.end(), 0.0);
+    InvariantValues().GrossPmt[Year] += std::accumulate(GrossPmts.begin(), 
GrossPmts.end(), currency()).d();
     InvariantValues().Outlay[Year] =
             InvariantValues().GrossPmt   [Year]
         -   InvariantValues().NetWD      [Year]
@@ -497,7 +497,7 @@ void AccountValue::PerformSpecAmtStrategy()
 
     for(int j = 0; j < BasicValues::GetLength(); ++j)
         {
-        InvariantValues().SpecAmt[j] = SA;
+        InvariantValues().SpecAmt[j] = SA.d();
         }
 }
 
@@ -557,7 +557,7 @@ void AccountValue::TxOptionChange()
     // Carry the new spec amt forward into all future years.
     for(int j = Year; j < BasicValues::GetLength(); ++j)
         {
-        InvariantValues().SpecAmt[j] = ActualSpecAmt;
+        InvariantValues().SpecAmt[j] = ActualSpecAmt.d();
         }
 }
 
@@ -589,7 +589,7 @@ void AccountValue::TxSpecAmtChange()
     // Carry the new spec amt forward into all future years.
     for(int j = Year; j < BasicValues::GetLength(); ++j)
         {
-        InvariantValues().SpecAmt[j] = ActualSpecAmt;
+        InvariantValues().SpecAmt[j] = ActualSpecAmt.d();
         }
 }
 
@@ -734,7 +734,7 @@ void AccountValue::TxLoanRepay()
 
     AVUnloaned -= RequestedLoan;
     AVRegLn += RequestedLoan;    // IHS !! Preferred loans--see lmi.
-    InvariantValues().NewCashLoan[Year] = RequestedLoan;
+    InvariantValues().NewCashLoan[Year] = RequestedLoan.d();
 }
 
 /// Set account value before monthly deductions.
@@ -790,7 +790,8 @@ void AccountValue::TxSetCoiCharge()
     TxSetDeathBft();
 
     // Negative AV doesn't increase NAAR.
-    NAAR = round_naar()(deathbft * mlyguarv - (AVUnloaned + AVRegLn + 
AVPrfLn));
+    NAAR = (deathbft * mlyguarv - (AVUnloaned + AVRegLn + AVPrfLn)).d();
+    NAAR = round_naar()(NAAR);
 
     CoiCharge = round_coi_charge().c(NAAR * YearsCoiRate0);
 }
@@ -799,19 +800,20 @@ void AccountValue::TxSetCoiCharge()
 
 void AccountValue::TxSetRiderDed()
 {
-    WpCharge = 0.0;
+    WpCharge = currency();
     if(haswp)
         {
-        WpCharge =
+        WpCharge = currency
+            (
                 YearsWpRate
             *   (CoiCharge + YearsMonthlyPolicyFee + AdbCharge)
-            ;
+            );
         }
 
-    AdbCharge = 0.0;
+    AdbCharge = currency();
     if(hasadb)
         {
-        AdbCharge = currency(YearsAdbRate * std::min<double>(500000.0, 
ActualSpecAmt));
+        AdbCharge = currency(YearsAdbRate * std::min<double>(500000.0, 
ActualSpecAmt.d()));
         }
 }
 
@@ -879,7 +881,7 @@ void AccountValue::TxTakeWD()
         }
 
     // Nothing to do if no withdrawal requested.
-    if(0.0 == wd)
+    if(currency() == wd)
         {
         return;
         }
@@ -889,7 +891,7 @@ void AccountValue::TxTakeWD()
     // Impose minimum amount (if nonzero) on withdrawals.
     if(wd < MinWD)
         {
-        wd = 0.0;
+        wd = currency();
         }
 
     // Impose maximum amount.
@@ -934,7 +936,7 @@ void AccountValue::TxTakeWD()
             // Carry the new spec amt forward into all future years.
             for(int j = Year; j < BasicValues::GetLength(); ++j)
                 {
-                InvariantValues().SpecAmt[j] = ActualSpecAmt;
+                InvariantValues().SpecAmt[j] = ActualSpecAmt.d();
                 }
             }
             break;
@@ -953,7 +955,7 @@ void AccountValue::TxTakeWD()
     wd -= std::min(WDFee, currency(wd * WDFeeRate));
     // IHS !! This treats input WD as gross; it probably should be net. But 
compare lmi.
 
-    InvariantValues().NetWD[Year] = wd;
+    InvariantValues().NetWD[Year] = wd.d();
 // IHS !!    TaxBasis -= wd; // Withdrawals are subtracted from basis in lmi.
 }
 
@@ -977,12 +979,13 @@ void AccountValue::TxTakeLoan()
     // If maximum exceeded...limit it.
     // IHS !! For solves, the lmi branch uses an 'ullage' concept.
     double max_loan =
-          AVUnloaned * 0.9    // IHS !! Icky manifest constant--lmi uses a 
database entity.
+          AVUnloaned.d() * 0.9    // IHS !! Icky manifest constant--lmi uses a 
database entity.
         // - surrchg
-        + (AVRegLn + AVPrfLn)
-        - RegLnBal * (std::pow((1.0 + YearsRegLnIntDueRate), 12 - Month) - 1.0)
-        - PrfLnBal * (std::pow((1.0 + YearsPrfLnIntDueRate), 12 - Month) - 1.0)
-        - mlydedtonextmodalpmtdate;
+        + (AVRegLn + AVPrfLn).d()
+        - RegLnBal.d() * (std::pow((1.0 + YearsRegLnIntDueRate), 12 - Month) - 
1.0)
+        - PrfLnBal.d() * (std::pow((1.0 + YearsPrfLnIntDueRate), 12 - Month) - 
1.0)
+        - mlydedtonextmodalpmtdate.d()
+        ;
     // Interest adjustment: d upper n where n is # months remaining in year.
     // Witholding this keeps policy from becoming overloaned before year end.
     double IntAdj = std::pow((1.0 + YearsRegLnIntDueRate), 12 - Month);
@@ -1002,7 +1005,7 @@ void AccountValue::TxTakeLoan()
 
     AVUnloaned -= RequestedLoan;
     AVRegLn += RequestedLoan;    // IHS !! Also preferred loans: implemented 
in lmi.
-    InvariantValues().NewCashLoan[Year] = RequestedLoan;
+    InvariantValues().NewCashLoan[Year] = RequestedLoan.d();
 }
 
 /// Test for lapse.
diff --git a/basicvalues.cpp b/basicvalues.cpp
index 4c4a2ab..7c593c3 100644
--- a/basicvalues.cpp
+++ b/basicvalues.cpp
@@ -200,7 +200,7 @@ currency BasicValues::GetModalTgtPrem
     // IHS !! Implemented better in lmi.
     double Annuity = (1.0 - std::pow(u, 12 / a_mode)) / (1.0 - u);
 
-    double z = a_specamt;
+    double z = a_specamt.d();
     z /=
         (   1.0
         +   InterestRates_->GenAcctNetRate
@@ -274,7 +274,7 @@ currency BasicValues::GetModalTgtSpecAmt
         );
     double Annuity = (1.0 - std::pow(u, 12 / a_mode)) / (1.0 - u);
 
-    double z = a_pmt;
+    double z = a_pmt.d();
     z /= Annuity;
     z *= 1.0 - Loads_->target_premium_load(mce_gen_curr)[0];
 //    z /= WpRate;
diff --git a/currency.hpp b/currency.hpp
index 0a54a91..133aa16 100644
--- a/currency.hpp
+++ b/currency.hpp
@@ -64,16 +64,16 @@ class currency
     ~currency() = default;
 
     explicit currency(double    d) : m_ {from_double(d)}           {}
-    explicit currency(int       i) : m_ {bourn_cast<data_type>(i)} {}
+    explicit currency(int       i) : m_ {cents_per_dollar * 
bourn_cast<data_type>(i)} {}
     explicit currency(data_type i) : m_ {bourn_cast<data_type>(i)} {}
 
     currency& operator=(currency const&) = default;
     // IMPORTANT eventually suppress this? or both of these?
     // defining both causes real problems
-    currency& operator=(double d) {m_ = from_double(d);           return 
*this;}
-//  currency& operator=(int    i) {m_ = bourn_cast<data_type>(i); return 
*this;}
+//  currency& operator=(double d) {m_ = from_double(d);           return 
*this;}
+    currency& operator=(int    i) {m_ = cents_per_dollar * 
bourn_cast<data_type>(i); return *this;}
 
-    operator double() const {return to_double();}
+//  operator double() const {return to_double();}
     double d() const {return to_double();}
 
 #if 0
diff --git a/gpt_specamt.cpp b/gpt_specamt.cpp
index e7b192d..e2b67fe 100644
--- a/gpt_specamt.cpp
+++ b/gpt_specamt.cpp
@@ -107,14 +107,14 @@ class FindSpecAmt
                     ,a_Trial
                     ,NetPmtFactorTgt
                     ,NetPmtFactorExc
-                    ,Values_.GetAnnualTgtPrem(Duration, SpecAmt)
+                    ,Values_.GetAnnualTgtPrem(Duration, SpecAmt).d()
                     )
             -   Premium
             ;
         }
     double Get()
         {
-        return SpecAmt;
+        return SpecAmt.d();
         }
 };
 
@@ -150,7 +150,7 @@ currency gpt_specamt::CalculateSpecAmt
         ,z
         ,a_EIOBasis
         ,a_Duration
-        ,a_Premium
+        ,a_Premium.d()
         ,a_NetPmtFactorTgt
         ,a_NetPmtFactorExc
         );
diff --git a/ihs_acctval.cpp b/ihs_acctval.cpp
index 0f51640..7dd48e0 100644
--- a/ihs_acctval.cpp
+++ b/ihs_acctval.cpp
@@ -111,16 +111,16 @@ AccountValue::AccountValue(Input const& input)
     // all be kept together.
     LapseMonth               = 0;          // Antediluvian.
     LapseYear                = 0;          // Antediluvian.
-    AVUnloaned               = 0.0;        // Antediluvian.
-    pmt                      = 0.0;        // Antediluvian.
+    AVUnloaned               = 0;          // Antediluvian.
+    pmt                      = 0;          // Antediluvian.
     pmt_mode                 = mce_annual; // Antediluvian.
     ModeIndex                = 0;          // Antediluvian.
-    wd                       = 0.0;        // Antediluvian.
+    wd                       = 0;          // Antediluvian.
     mlyguarv                 = 0.0;        // Antediluvian.
-    deathbft                 = 0.0;        // Antediluvian.
+    deathbft                 = 0;          // Antediluvian.
     haswp                    = false;      // Antediluvian.
     hasadb                   = false;      // Antediluvian.
-    mlydedtonextmodalpmtdate = 0.0;        // Antediluvian.
+    mlydedtonextmodalpmtdate = 0;          // Antediluvian.
 
     set_list_bill_year_and_month();
 
@@ -158,7 +158,7 @@ currency AccountValue::specamt_for_7702(int year) const
 {
     return
                               base_specamt(year)
-        + (TermIsDbFor7702  ? term_specamt(year) : 0.0)
+        + (TermIsDbFor7702  ? term_specamt(year) : currency())
         ;
 }
 
@@ -168,7 +168,7 @@ currency AccountValue::specamt_for_7702A(int year) const
 {
     return
                               base_specamt(year)
-        + (TermIsDbFor7702A ? term_specamt(year) : 0.0)
+        + (TermIsDbFor7702A ? term_specamt(year) : currency())
         ;
 }
 
@@ -217,13 +217,13 @@ Then run other bases.
 // this rather expensive function.
 void AccountValue::SetGuarPrem()
 {
-    GuarPremium = 0.0;
+    GuarPremium = currency();
     if(BasicValues::IsSubjectToIllustrationReg())
         {
         GuarPremium = SolveGuarPremium();
         }
     LMI_ASSERT(GuarPremium < 1.0e100);
-    ledger_->SetGuarPremium(GuarPremium);
+    ledger_->SetGuarPremium(GuarPremium.d());
 }
 
 //============================================================================
@@ -405,8 +405,8 @@ void AccountValue::InitializeLife(mcenum_run_basis a_Basis)
         (0
         ,mce_annual
         ,base_specamt(0)
-        );
-    double sa = specamt_for_7702(0);
+        ).d();
+    double sa = specamt_for_7702(0).d();
 
     // It is at best superfluous to do this for every basis.
     // TAXATION !! Don't do that then.
@@ -450,7 +450,7 @@ void AccountValue::InitializeLife(mcenum_run_basis a_Basis)
     if(yare_input_.EffectiveDate == yare_input_.InforceAsOfDate)
         {
         // No need to initialize 'pmts_7702a' in this case.
-        bfts_7702a.push_back(specamt_for_7702A(0));
+        bfts_7702a.push_back(specamt_for_7702A(0).d());
         }
     else
         {
@@ -530,7 +530,7 @@ void AccountValue::SetInitialValues()
     Month                 = InforceMonth;
     CoordinateCounters();
 
-    DB7702A               = 0.0;  // TODO ?? TAXATION !! This seems silly.
+    DB7702A               = currency();  // TODO ?? TAXATION !! This seems 
silly.
 
     AVRegLn               = round_minutiae().c(InforceAVRegLn);
     AVPrfLn               = round_minutiae().c(InforceAVPrfLn);
@@ -562,21 +562,21 @@ void AccountValue::SetInitialValues()
             ;
         }
 
-    MaxLoan                     = 0.0;
+    MaxLoan                     = currency();
 
-    GenAcctIntCred              = 0.0;
-    SepAcctIntCred              = 0.0;
-    RegLnIntCred                = 0.0;
-    PrfLnIntCred                = 0.0;
+    GenAcctIntCred              = currency();
+    SepAcctIntCred              = currency();
+    RegLnIntCred                = currency();
+    PrfLnIntCred                = currency();
 
-    MaxWD                       = 0.0;
-    GrossWD                     = 0.0;
-    NetWD                       = 0.0;
+    MaxWD                       = currency();
+    GrossWD                     = currency();
+    NetWD                       = currency();
 
     CumPmts                     = InforceCumPmts;
     TaxBasis                    = InforceTaxBasis;
     YearlyTaxBasis.assign(BasicValues::GetLength(), currency());
-    MlyNoLapsePrem              = 0.0;
+    MlyNoLapsePrem              = currency();
     CumNoLapsePrem              = InforceCumNoLapsePrem;
 
     // Initialize all elements of this vector to 'false'. Then, when
@@ -599,7 +599,7 @@ void AccountValue::SetInitialValues()
 
     database().query_into(DB_TermCanLapse       , TermCanLapse);
     TermRiderActive             = true;
-    TermDB                      = 0.0;
+    TermDB                      = currency();
 
     ItLapsed                    = false;
 
@@ -628,10 +628,10 @@ void AccountValue::SetInitialValues()
             }
         }
 
-    CoiCharge                   = 0.0;
-    RiderCharges                = 0.0;
-    NetCoiCharge                = 0.0;
-    MlyDed                      = 0.0;
+    CoiCharge                   = currency();
+    RiderCharges                = currency();
+    NetCoiCharge                = currency();
+    MlyDed                      = currency();
     CumulativeSalesLoad         = 
round_minutiae().c(yare_input_.InforceCumulativeSalesLoad);
 
     database().query_into(DB_ExpRatCoiRetention, CoiRetentionRate);
@@ -807,29 +807,29 @@ void AccountValue::InitializeYear()
         Irc7702A_->UpdateBOY7702A(Year);
         }
 
-    MonthsPolicyFees            = 0.0;
-    SpecAmtLoad                 = 0.0;
+    MonthsPolicyFees            = currency();
+    SpecAmtLoad                 = currency();
 
-    AssetsPostBom               = 0.0;
-    CumPmtsPostBom              = 0.0;
-    SepAcctLoad                 = 0.0;
+    AssetsPostBom               = currency();
+    CumPmtsPostBom              = currency();
+    SepAcctLoad                 = currency();
 
-    YearsTotalCoiCharge         = 0.0;
-    YearsTotalRiderCharges      = 0.0;
+    YearsTotalCoiCharge         = currency();
+    YearsTotalRiderCharges      = currency();
     YearsAVRelOnDeath           = 0.0;
     YearsLoanRepaidOnDeath      = 0.0;
     YearsGrossClaims            = 0.0;
     YearsDeathProceeds          = 0.0;
     YearsNetClaims              = 0.0;
-    YearsTotalNetIntCredited    = 0.0;
-    YearsTotalGrossIntCredited  = 0.0;
-    YearsTotalLoanIntAccrued    = 0.0;
+    YearsTotalNetIntCredited    = currency();
+    YearsTotalGrossIntCredited  = currency();
+    YearsTotalLoanIntAccrued    = currency();
     YearsTotalNetCoiCharge      = 0.0;
-    YearsTotalPolicyFee         = 0.0;
+    YearsTotalPolicyFee         = currency();
     YearsTotalDacTaxLoad        = 0.0;
-    YearsTotalSpecAmtLoad       = 0.0;
-    YearsTotalSepAcctLoad       = 0.0;
-    YearsTotalGptForceout       = 0.0;
+    YearsTotalSpecAmtLoad       = currency();
+    YearsTotalSepAcctLoad       = currency();
+    YearsTotalGptForceout       = currency();
 
     NextYearsProjectedCoiCharge = 0.0;
 
@@ -840,7 +840,7 @@ void AccountValue::InitializeYear()
     // variable in each function might have sufficed, except that this
     // quantity is used in the optional monthly detail report. Its
     // value depends on the maximum loan, so it cannot be known here.
-    ActualLoan                  = 0.0;
+    ActualLoan                  = currency();
 
     GrossPmts   .assign(12, currency());
     EeGrossPmts .assign(12, currency());
@@ -892,7 +892,7 @@ void AccountValue::InitializeSpecAmt()
 
     if(0 == Year)
         {
-        InvariantValues().InitTgtPrem = AnnualTargetPrem;
+        InvariantValues().InitTgtPrem = AnnualTargetPrem.d();
         }
 
     // TODO ?? Perform specamt strategy here?
@@ -931,8 +931,8 @@ void AccountValue::set_list_bill_premium()
             ,Outlay_->er_premium_modes()[Year]
             ,base_specamt(Year)
             );
-        InvariantValues().ListBillPremium   = z;
-        InvariantValues().ErListBillPremium = z;
+        InvariantValues().ListBillPremium   = z.d();
+        InvariantValues().ErListBillPremium = z.d();
         }
     else
         {
@@ -942,9 +942,9 @@ void AccountValue::set_list_bill_premium()
             ,base_specamt(Year)
             ,term_specamt(Year)
             );
-        InvariantValues().EeListBillPremium = z.first;
-        InvariantValues().ErListBillPremium = z.second;
-        InvariantValues().ListBillPremium = z.first + z.second;
+        InvariantValues().EeListBillPremium = z.first.d();
+        InvariantValues().ErListBillPremium = z.second.d();
+        InvariantValues().ListBillPremium = z.first.d() + z.second.d();
         }
 }
 
@@ -969,8 +969,8 @@ void AccountValue::set_modal_min_premium()
             ,Outlay_->er_premium_modes()[Year]
             ,base_specamt(Year)
             );
-        InvariantValues().ModalMinimumPremium[Year]   = z;
-        InvariantValues().ErModalMinimumPremium[Year] = z;
+        InvariantValues().ModalMinimumPremium[Year]   = z.d();
+        InvariantValues().ErModalMinimumPremium[Year] = z.d();
         }
     else
         {
@@ -980,9 +980,9 @@ void AccountValue::set_modal_min_premium()
             ,base_specamt(Year)
             ,term_specamt(Year)
             );
-        InvariantValues().EeModalMinimumPremium[Year] = z.first;
-        InvariantValues().ErModalMinimumPremium[Year] = z.second;
-        InvariantValues().ModalMinimumPremium[Year] = z.first + z.second;
+        InvariantValues().EeModalMinimumPremium[Year] = z.first.d();
+        InvariantValues().ErModalMinimumPremium[Year] = z.second.d();
+        InvariantValues().ModalMinimumPremium[Year] = z.first.d() + 
z.second.d();
         }
 }
 
@@ -1145,7 +1145,7 @@ void AccountValue::SetProjectedCoiCharge()
 
 void AccountValue::FinalizeYear()
 {
-    VariantValues().TotalLoanBalance[Year] = RegLnBal + PrfLnBal;
+    VariantValues().TotalLoanBalance[Year] = (RegLnBal + PrfLnBal).d();
 
     currency total_av = TotalAccountValue();
     currency surr_chg = SurrChg();
@@ -1201,12 +1201,12 @@ void AccountValue::FinalizeYear()
         }
     cv_7702 = std::max(cv_7702, HoneymoonValue);
 
-    VariantValues().AcctVal     [Year] = total_av;
-    VariantValues().AVGenAcct   [Year] = AVGenAcct + AVRegLn + AVPrfLn;
-    VariantValues().AVSepAcct   [Year] = AVSepAcct;
+    VariantValues().AcctVal     [Year] = total_av.d();
+    VariantValues().AVGenAcct   [Year] = (AVGenAcct + AVRegLn + AVPrfLn).d();
+    VariantValues().AVSepAcct   [Year] = AVSepAcct.d();
     VariantValues().DacTaxRsv   [Year] = DacTaxRsv;
-    VariantValues().CSVNet      [Year] = csv_net;
-    VariantValues().CV7702      [Year] = cv_7702;
+    VariantValues().CSVNet      [Year] = csv_net.d();
+    VariantValues().CV7702      [Year] = cv_7702.d();
 
     // Update death benefit. 'DBReflectingCorr' currently equals the
     // death benefit as of the beginning of the twelfth month, but its
@@ -1216,11 +1216,11 @@ void AccountValue::FinalizeYear()
     TxSetDeathBft();
     TxSetTermAmt();
     // post values to LedgerVariant
-    InvariantValues().TermSpecAmt   [Year] = TermSpecAmt;
-    VariantValues().TermPurchased   [Year] = TermDB;
+    InvariantValues().TermSpecAmt   [Year] = TermSpecAmt.d();
+    VariantValues().TermPurchased   [Year] = TermDB.d();
     // Add term rider DB
-    VariantValues().BaseDeathBft    [Year] = DBReflectingCorr;
-    VariantValues().EOYDeathBft     [Year] = DBReflectingCorr + TermDB;
+    VariantValues().BaseDeathBft    [Year] = DBReflectingCorr.d();
+    VariantValues().EOYDeathBft     [Year] = (DBReflectingCorr + TermDB).d();
 
 /*
     // AV already includes any experience refund credited, but it's
@@ -1245,19 +1245,19 @@ void AccountValue::FinalizeYear()
 
     // Monthly deduction detail
 
-    VariantValues().COICharge         [Year] = YearsTotalCoiCharge        ;
-    VariantValues().RiderCharges      [Year] = YearsTotalRiderCharges     ;
+    VariantValues().COICharge         [Year] = YearsTotalCoiCharge.d()    ;
+    VariantValues().RiderCharges      [Year] = YearsTotalRiderCharges.d() ;
     VariantValues().AVRelOnDeath      [Year] = YearsAVRelOnDeath          ;
     VariantValues().ClaimsPaid        [Year] = YearsGrossClaims           ;
     VariantValues().DeathProceedsPaid [Year] = YearsDeathProceeds         ;
     VariantValues().NetClaims         [Year] = YearsNetClaims             ;
-    VariantValues().NetIntCredited    [Year] = YearsTotalNetIntCredited   ;
-    VariantValues().GrossIntCredited  [Year] = YearsTotalGrossIntCredited ;
-    VariantValues().LoanIntAccrued    [Year] = YearsTotalLoanIntAccrued   ;
+    VariantValues().NetIntCredited    [Year] = YearsTotalNetIntCredited.d();
+    VariantValues().GrossIntCredited  [Year] = YearsTotalGrossIntCredited.d();
+    VariantValues().LoanIntAccrued    [Year] = YearsTotalLoanIntAccrued.d();
     VariantValues().NetCOICharge      [Year] = YearsTotalNetCoiCharge     ;
-    VariantValues().PolicyFee         [Year] = YearsTotalPolicyFee        ;
+    VariantValues().PolicyFee         [Year] = YearsTotalPolicyFee.d()    ;
     VariantValues().DacTaxLoad        [Year] = YearsTotalDacTaxLoad       ;
-    VariantValues().SpecAmtLoad       [Year] = YearsTotalSpecAmtLoad      ;
+    VariantValues().SpecAmtLoad       [Year] = YearsTotalSpecAmtLoad.d()  ;
     VariantValues().PremTaxLoad       [Year] = PremiumTax_->ytd_load();
 
     currency notional_sep_acct_charge =
@@ -1274,7 +1274,7 @@ void AccountValue::FinalizeYear()
             )
         ;
 #endif // 0
-    VariantValues().SepAcctCharges    [Year] = notional_sep_acct_charge   ;
+    VariantValues().SepAcctCharges    [Year] = notional_sep_acct_charge.d();
 
     // Record dynamic interest rate in ledger object.
     //
@@ -1311,8 +1311,8 @@ void AccountValue::FinalizeYear()
     VariantValues().NetPmt[Year] = std::accumulate
         (NetPmts.begin()
         ,NetPmts.end()
-        ,-YearsTotalGptForceout
-        );
+        ,currency() - YearsTotalGptForceout.d() // unary operator-()
+        ).d();
 
     if(mce_run_gen_curr_sep_full == RunBasis_)
         {
@@ -1324,15 +1324,16 @@ void AccountValue::FinalizeYear()
         // Forceouts should be a distinct component, passed separately
         // to ledger values. Probably we should treat 1035 exchanges
         // and NAAR 'forceouts' the same way.
-        InvariantValues().GrossPmt  [Year]  -= YearsTotalGptForceout;
-        InvariantValues().EeGrossPmt[Year]  -= YearsTotalGptForceout;
+        InvariantValues().GrossPmt  [Year]  -= YearsTotalGptForceout.d();
+        InvariantValues().EeGrossPmt[Year]  -= YearsTotalGptForceout.d();
 
         for(int j = 0; j < 12; ++j)
             {
-            LMI_ASSERT(materially_equal(GrossPmts[j], EeGrossPmts[j] + 
ErGrossPmts[j]));
-            InvariantValues().GrossPmt  [Year]  += GrossPmts[j];
-            InvariantValues().EeGrossPmt[Year]  += EeGrossPmts[j];
-            InvariantValues().ErGrossPmt[Year]  += ErGrossPmts[j];
+//          LMI_ASSERT(materially_equal(GrossPmts[j], EeGrossPmts[j] + 
ErGrossPmts[j]));
+            LMI_ASSERT(GrossPmts[j] == EeGrossPmts[j] + ErGrossPmts[j]);
+            InvariantValues().GrossPmt  [Year]  += GrossPmts  [j].d();
+            InvariantValues().EeGrossPmt[Year]  += EeGrossPmts[j].d();
+            InvariantValues().ErGrossPmt[Year]  += ErGrossPmts[j].d();
             }
         if(0 == Year)
             {
@@ -1353,7 +1354,7 @@ void AccountValue::FinalizeYear()
             -   InvariantValues().NewCashLoan[Year]
             ;
 
-        InvariantValues().GptForceout[Year] = YearsTotalGptForceout;
+        InvariantValues().GptForceout[Year] = YearsTotalGptForceout.d();
 
 // SOMEDAY !! Not yet implemented.
 //        InvariantValues().NaarForceout[Year] = 
InvariantValues().ErGrossPmt[Year];
@@ -1366,8 +1367,8 @@ void AccountValue::SetAnnualInvariants()
     YearsCorridorFactor     = GetCorridorFactor()[Year];
     YearsDBOpt              = DeathBfts_->dbopt()[Year];
     // policy fee should be rounded in loads class
-    YearsMonthlyPolicyFee   = Loads_->monthly_policy_fee(GenBasis_)[Year];
-    YearsAnnualPolicyFee    = Loads_->annual_policy_fee (GenBasis_)[Year];
+    YearsMonthlyPolicyFee   = 
currency(Loads_->monthly_policy_fee(GenBasis_)[Year]);
+    YearsAnnualPolicyFee    = currency(Loads_->annual_policy_fee 
(GenBasis_)[Year]);
 
     YearsGenAcctIntRate     = InterestRates_->GenAcctNetRate
         (GenBasis_
diff --git a/ihs_avdebug.cpp b/ihs_avdebug.cpp
index aee9b3d..047528b 100644
--- a/ihs_avdebug.cpp
+++ b/ihs_avdebug.cpp
@@ -419,19 +419,19 @@ void AccountValue::DebugPrint()
     SetMonthlyDetail(eCumNoLapsePrem     ,CumNoLapsePrem                   );
     SetMonthlyDetail(eNoLapseActive      ,NoLapseActive                    );
     SetMonthlyDetail(eEOMAV              ,TotalAccountValue()              );
-    SetMonthlyDetail(eHMValue            ,std::max<double>(HoneymoonValue, 
0.0));
+    SetMonthlyDetail(eHMValue            ,std::max(HoneymoonValue, 
currency()));
     SetMonthlyDetail(eSurrChg            ,SurrChg()                        );
 
 // TODO ?? Unfortunately duplicated from AccountValue::FinalizeYear().
-    double total_av = TotalAccountValue();
-    double csv_net =
+    currency total_av = TotalAccountValue();
+    currency csv_net =
             total_av
         -   SurrChg()
         -   RegLnBal
         +   GetRefundableSalesLoad()
 //        +   std::max(0.0, ExpRatReserve) // This would be added if it 
existed.
         ;
-    csv_net = std::max<double>(HoneymoonValue, csv_net);
+    csv_net = std::max(HoneymoonValue, csv_net);
 
     SetMonthlyDetail(eEOMCSVNet          ,csv_net                          );
     SetMonthlyDetail(eEOMCV7702          ,CashValueFor7702()               );
diff --git a/ihs_avmly.cpp b/ihs_avmly.cpp
index 9ac13e5..c56f1fd 100644
--- a/ihs_avmly.cpp
+++ b/ihs_avmly.cpp
@@ -120,18 +120,18 @@ void AccountValue::DoMonthDR()
     // TAXATION !! Is it really useful to comment the arguments here?
     Irc7702A_->UpdateBft7702A
         (Dcv
-        ,DBReflectingCorr + TermDB // DB7702A
-        ,OldDB // prior_db_7702A
+        ,(DBReflectingCorr + TermDB).d() // DB7702A
+        ,OldDB.d() // prior_db_7702A
         ,DBReflectingCorr == DBIgnoringCorr
         // TAXATION !! This assumes the term rider can be treated as death 
benefit;
         // use 'TermIsDbFor7702A'.
-        ,ActualSpecAmt + TermSpecAmt
-        ,OldSA // prior_sa_7702A
-        ,CashValueFor7702()
+        ,(ActualSpecAmt + TermSpecAmt).d()
+        ,OldSA.d() // prior_sa_7702A
+        ,CashValueFor7702().d()
         );
 
-    NetPmts  [Month] = 0.0; // TODO ?? expunge as being unnecessary
-    GrossPmts[Month] = 0.0; // TODO ?? expunge as being unnecessary
+    NetPmts  [Month] = currency(); // TODO ?? expunge as being unnecessary
+    GrossPmts[Month] = currency(); // TODO ?? expunge as being unnecessary
     TxExch1035();
     // TODO ?? TAXATION !! Is this where spec amt should be increased by GPT?
 
@@ -142,7 +142,7 @@ void AccountValue::DoMonthDR()
         // exchanges, but now seems unnecessary because this
         // assertion never fires:
         //   LMI_ASSERT(kludge_account_value == Dcv);
-        kludge_account_value = Dcv;
+        kludge_account_value = currency(Dcv);
         }
     kludge_account_value = std::max
         (HoneymoonValue
@@ -153,24 +153,24 @@ void AccountValue::DoMonthDR()
 // TODO ?? TAXATION !! Use CashValueFor7702() instead?
     double max_necessary_premium = Irc7702A_->MaxNecessaryPremium // round
         (Dcv
-        ,AnnualTargetPrem
+        ,AnnualTargetPrem.d()
         ,YearsTotLoadTgtLowestPremtax
         ,YearsTotLoadExcLowestPremtax
-        ,kludge_account_value
+        ,kludge_account_value.d()
         );
     double max_non_mec_premium = Irc7702A_->MaxNonMecPremium // round
         (Dcv
-        ,AnnualTargetPrem
+        ,AnnualTargetPrem.d()
         ,YearsTotLoadTgtLowestPremtax
         ,YearsTotLoadExcLowestPremtax
-        ,kludge_account_value
+        ,kludge_account_value.d()
         );
 
     // Saved for monthly detail report only. TAXATION !! Then are
     // these still needed...perhaps in order to report their values
     // prior to accepting any payment?
-    NetMaxNecessaryPremium   = Irc7702A_->DebugGetNetMaxNecPm  ();
-    GrossMaxNecessaryPremium = Irc7702A_->DebugGetGrossMaxNecPm();
+    NetMaxNecessaryPremium   = currency(Irc7702A_->DebugGetNetMaxNecPm  ());
+    GrossMaxNecessaryPremium = currency(Irc7702A_->DebugGetGrossMaxNecPm());
 
     // Determine list-bill premiums only after transactions that
     // might change specamt have been processed.
@@ -187,12 +187,12 @@ void AccountValue::DoMonthDR()
         {
         Irc7702A_->UpdatePmt7702A
             (Dcv
-            ,-NetWD // TAXATION !! This should be gross, not net.
+            ,-NetWD.d() // TAXATION !! This should be gross, not net. // unary 
operator-()
             ,false
-            ,AnnualTargetPrem
+            ,AnnualTargetPrem.d()
             ,YearsTotLoadTgtLowestPremtax
             ,YearsTotLoadExcLowestPremtax
-            ,kludge_account_value
+            ,kludge_account_value.d()
             );
         }
 
@@ -214,8 +214,8 @@ void AccountValue::DoMonthDR()
         );
 //  NetMaxNecessaryPremium
 //  GrossMaxNecessaryPremium
-    NecessaryPremium = necessary_premium;
-    UnnecessaryPremium = unnecessary_premium;
+    NecessaryPremium   = currency(necessary_premium  );
+    UnnecessaryPremium = currency(unnecessary_premium);
     if(necessary_premium < 0.0 || unnecessary_premium < 0.0)
         warning()
 //          << GrossPmts[Month] << " GrossPmts[Month]\n"
@@ -247,7 +247,7 @@ void AccountValue::DoMonthDR()
         (Dcv // Potentially modified.
         ,unnecessary_premium
         ,necessary_premium
-        ,CashValueFor7702()
+        ,CashValueFor7702().d()
         );
     LMI_ASSERT(0.0 <= Dcv);
 
@@ -435,17 +435,18 @@ void AccountValue::DecrementAVProportionally(currency 
decrement)
 {
     decrement = round_minutiae().c(decrement); // already rounded?
 
-    if(materially_equal(decrement, AVGenAcct + AVSepAcct))
+//  if(materially_equal(decrement, AVGenAcct + AVSepAcct))
+    if(decrement == AVGenAcct + AVSepAcct)
         {
-        AVGenAcct = 0.0;
-        AVSepAcct = 0.0;
+        AVGenAcct = currency();
+        AVSepAcct = currency();
         return;
         }
 
     double general_account_proportion  = 0.0;
     double separate_account_proportion = 0.0;
-    double general_account_nonnegative_assets  = std::max<double>(0.0, 
AVGenAcct);
-    double separate_account_nonnegative_assets = std::max<double>(0.0, 
AVSepAcct);
+    double general_account_nonnegative_assets  = std::max(0.0, AVGenAcct.d());
+    double separate_account_nonnegative_assets = std::max(0.0, AVSepAcct.d());
     if
         (  0.0 == general_account_nonnegative_assets
         && 0.0 == separate_account_nonnegative_assets
@@ -498,10 +499,11 @@ void AccountValue::DecrementAVProgressively
     ,oenum_increment_account_preference preferred_account
     )
 {
-    if(materially_equal(decrement, AVGenAcct + AVSepAcct))
+//  if(materially_equal(decrement, AVGenAcct + AVSepAcct))
+    if(decrement == AVGenAcct + AVSepAcct)
         {
-        AVGenAcct = 0.0;
-        AVSepAcct = 0.0;
+        AVGenAcct = currency();
+        AVSepAcct = currency();
         return;
         }
 
@@ -554,9 +556,9 @@ void AccountValue::TxExch1035()
         if(!SolvingForGuarPremium)
             {
             // Maybe this should return the modified value instead of altering 
the argument.
-            double z = GrossPmts[Month];
+            double z = GrossPmts[Month].d();
             Irc7702_->ProcessGptPmt(Year, z);
-            GrossPmts[Month] = z;
+            GrossPmts[Month] = currency(z);
             }
         // Limit external 1035 first, then internal, as necessary to avoid
         // exceeding the guideline limit. This is what the customer would
@@ -592,17 +594,17 @@ void AccountValue::TxExch1035()
     LMI_ASSERT(0.0 == AVSepAcct);
     process_payment(NetPmts[Month]);
 
-    DBReflectingCorr = 0.0;
+    DBReflectingCorr = currency();
     TxSetDeathBft();
     TxSetTermAmt();
     // TODO ?? TAXATION !! Should 1035 exchanges be handled somewhere else?
     LMI_ASSERT(0.0 == Dcv);
     Irc7702A_->Update1035Exch7702A
         (Dcv
-        ,NetPmts[Month]
+        ,NetPmts[Month].d()
         // TAXATION !! This assumes the term rider can be treated as death 
benefit;
         // use 'TermIsDbFor7702A'.
-        ,ActualSpecAmt + TermSpecAmt
+        ,(ActualSpecAmt + TermSpecAmt).d()
 //        ,DBReflectingCorr + TermDB // TAXATION !! Alternate if 7702A benefit 
is DB?
         );
 
@@ -622,7 +624,7 @@ void AccountValue::TxExch1035()
         // Immediately after a 1035 exchange, DCV should be
         // the 1035 amount reduced by any premium-based loads,
         // but only for the current rate basis.
-        LMI_ASSERT(materially_equal(Dcv, doubleize(NetPmts[Month])));
+        LMI_ASSERT(materially_equal(Dcv, NetPmts[Month].d()));
 
         // The initial seven-pay premium shown on the illustration
         // must be its value immediately after any 1035 exchange,
@@ -633,12 +635,8 @@ void AccountValue::TxExch1035()
             );
         }
 
-    LMI_ASSERT
-        (materially_equal
-            (GrossPmts[Month]
-            ,EeGrossPmts[Month] + ErGrossPmts[Month]
-            )
-        );
+//  LMI_ASSERT(materially_equal(GrossPmts[Month], EeGrossPmts[Month] + 
ErGrossPmts[Month]));
+    LMI_ASSERT(GrossPmts[Month] == EeGrossPmts[Month] + ErGrossPmts[Month]);
 }
 
 //============================================================================
@@ -795,12 +793,12 @@ void AccountValue::ChangeSpecAmtBy(currency delta)
 // TODO ?? Shouldn't this be moved to FinalizeMonth()? The problem is
 // that the ledger object is used for working storage, where it should
 // probably be write-only instead.
-        InvariantValues().SpecAmt[j] = ActualSpecAmt;
+        InvariantValues().SpecAmt[j] = ActualSpecAmt.d();
         // Adjust term here only if it's formally a rider.
         // Otherwise, its amount should not have been changed.
         if(!TermIsNotRider)
             {
-            InvariantValues().TermSpecAmt[j] = TermSpecAmt;
+            InvariantValues().TermSpecAmt[j] = TermSpecAmt.d();
             }
 // Term specamt is a vector in class LedgerInvariant, but a scalar in
 // the input classes, e.g.:
@@ -828,7 +826,7 @@ void AccountValue::ChangeSupplAmtBy(currency delta)
     // Carry the new supplemental amount forward into all future years.
     for(int j = Year; j < BasicValues::GetLength(); ++j)
         {
-        InvariantValues().TermSpecAmt[j] = TermSpecAmt;
+        InvariantValues().TermSpecAmt[j] = TermSpecAmt.d();
         }
     // Reset term DB whenever term SA changes. It's not obviously
     // necessary to do this here, but neither should it do any harm.
@@ -838,7 +836,7 @@ void AccountValue::ChangeSupplAmtBy(currency delta)
 //============================================================================
 void AccountValue::InitializeMonth()
 {
-    GptForceout       = 0.0;
+    GptForceout       = currency();
     premium_load_     = 0.0;
     sales_load_       = 0.0;
     premium_tax_load_ = 0.0;
@@ -942,7 +940,7 @@ void AccountValue::TxOptionChange()
         case mce_option2:
             if(OptChgCanDecrSA)
                 {
-                ChangeSpecAmtBy(currency(-std::max(currency(), 
TotalAccountValue())));
+                ChangeSpecAmtBy(currency() - std::max(currency(), 
TotalAccountValue())); // unary operator-()
                 }
             else
                 {
@@ -952,7 +950,7 @@ void AccountValue::TxOptionChange()
         case mce_rop:
             if(OptChgCanDecrSA)
                 {
-                ChangeSpecAmtBy(currency(-std::max(currency(), CumPmts)));
+                ChangeSpecAmtBy(currency() - std::max(currency(), CumPmts)); 
// unary operator-()
                 }
             else
                 {
@@ -962,7 +960,7 @@ void AccountValue::TxOptionChange()
         case mce_mdb:
             {
             // Change spec amt by its additive inverse, making it 0.
-            ChangeSpecAmtBy(currency(-(ActualSpecAmt + TermSpecAmt)));
+            ChangeSpecAmtBy(currency() - (ActualSpecAmt + TermSpecAmt)); // 
unary operator-()
             }
             break;
         }
@@ -1121,8 +1119,8 @@ void AccountValue::TxTestGPT()
     // use 'TermIsDbFor7702'.
     bool adj_event =
             (
-                !materially_equal(OldSA, ActualSpecAmt + TermSpecAmt)
-            &&  !materially_equal(OldDB, DBReflectingCorr + TermDB)
+                OldSA != ActualSpecAmt + TermSpecAmt
+            &&  OldDB != DBReflectingCorr + TermDB
             )
         ||  old_dbopt != new_dbopt
         ;
@@ -1132,19 +1130,19 @@ void AccountValue::TxTestGPT()
         // Or maybe not, because we can't match it if there was a plan change.
         Irc7702_->ProcessAdjustableEvent
             (Year
-            ,DBReflectingCorr + TermDB
-            ,OldDB
+            ,(DBReflectingCorr + TermDB).d()
+            ,OldDB.d()
             // TAXATION !! This assumes the term rider can be treated as death 
benefit;
             // use 'TermIsDbFor7702'.
-            ,ActualSpecAmt + TermSpecAmt
-            ,OldSA
+            ,(ActualSpecAmt + TermSpecAmt).d()
+            ,OldSA.d()
             ,new_dbopt
             ,old_dbopt
-            ,AnnualTargetPrem
+            ,AnnualTargetPrem.d()
             );
         }
 
-    GptForceout = Irc7702_->Forceout();
+    GptForceout = currency(Irc7702_->Forceout());
     // TODO ?? TAXATION !! On other bases, nothing is forced out, and payments 
aren't limited.
     process_distribution(GptForceout);
     YearsTotalGptForceout += GptForceout;
@@ -1214,7 +1212,7 @@ void AccountValue::TxAscertainDesiredPayment()
         return;
         }
 
-    if(!(materially_equal(GrossPmts[Month], EeGrossPmts[Month] + 
ErGrossPmts[Month])))
+    if(GrossPmts[Month] != EeGrossPmts[Month] + ErGrossPmts[Month])
         warning()
             << GrossPmts[Month] << " GrossPmts[Month]\n"
             << EeGrossPmts[Month] + ErGrossPmts[Month] << " EeGrossPmts[Month] 
+ ErGrossPmts[Month]\n"
@@ -1224,7 +1222,8 @@ void AccountValue::TxAscertainDesiredPayment()
             << Month << " Month\n"
             << LMI_FLUSH
             ;
-    LMI_ASSERT(materially_equal(GrossPmts[Month], EeGrossPmts[Month] + 
ErGrossPmts[Month]));
+//  LMI_ASSERT(materially_equal(GrossPmts[Month], EeGrossPmts[Month] + 
ErGrossPmts[Month]));
+    LMI_ASSERT(GrossPmts[Month] == EeGrossPmts[Month] + ErGrossPmts[Month]);
 
     currency eepmt {};
     if(ee_pay_this_month)
@@ -1233,9 +1232,9 @@ void AccountValue::TxAscertainDesiredPayment()
         // Illustration-reg guaranteed premium ignores GPT limit.
         if(!SolvingForGuarPremium)
             {
-            double z = eepmt;
+            double z = eepmt.d();
             Irc7702_->ProcessGptPmt(Year, z);
-            eepmt = z;
+            eepmt = currency(z);
             }
         EeGrossPmts[Month] += eepmt;
         GrossPmts  [Month] += eepmt;
@@ -1248,15 +1247,15 @@ void AccountValue::TxAscertainDesiredPayment()
         // Illustration-reg guaranteed premium ignores GPT limit.
         if(!SolvingForGuarPremium)
             {
-            double z = erpmt;
+            double z = erpmt.d();
             Irc7702_->ProcessGptPmt(Year, z);
-            erpmt = z;
+            erpmt = currency(z);
             }
         ErGrossPmts[Month] += erpmt;
         GrossPmts  [Month] += erpmt;
         }
 
-    if(!(materially_equal(GrossPmts[Month], EeGrossPmts[Month] + 
ErGrossPmts[Month])))
+    if(GrossPmts[Month] != EeGrossPmts[Month] + ErGrossPmts[Month])
         warning()
             << GrossPmts[Month] << " GrossPmts[Month]\n"
             << EeGrossPmts[Month] + ErGrossPmts[Month] << " EeGrossPmts[Month] 
+ ErGrossPmts[Month]\n"
@@ -1266,7 +1265,8 @@ void AccountValue::TxAscertainDesiredPayment()
             << Month << " Month\n"
             << LMI_FLUSH
             ;
-    LMI_ASSERT(materially_equal(GrossPmts[Month], EeGrossPmts[Month] + 
ErGrossPmts[Month]));
+//  LMI_ASSERT(materially_equal(GrossPmts[Month], EeGrossPmts[Month] + 
ErGrossPmts[Month]));
+    LMI_ASSERT(GrossPmts[Month] == EeGrossPmts[Month] + ErGrossPmts[Month]);
     LMI_ASSERT(GrossPmts[Month] < 1.0e100);
 
     if(0 == Year && 0 == Month)
@@ -1274,15 +1274,16 @@ void AccountValue::TxAscertainDesiredPayment()
         // Illustration-reg guaranteed premium ignores GPT limit.
         if(!SolvingForGuarPremium)
             {
-            double z = Dumpin;
+            double z = Dumpin.d();
             Irc7702_->ProcessGptPmt(Year, z);
-            Dumpin = z;
+            Dumpin = currency(z);
             }
         EeGrossPmts[Month] += Dumpin;
         GrossPmts  [Month] += Dumpin;
         }
 
-    if(!(materially_equal(GrossPmts[Month], EeGrossPmts[Month] + 
ErGrossPmts[Month])))
+//  if(!(materially_equal(GrossPmts[Month], EeGrossPmts[Month] + 
ErGrossPmts[Month])))
+    if(GrossPmts[Month] != EeGrossPmts[Month] + ErGrossPmts[Month])
         warning()
             << GrossPmts[Month] << " GrossPmts[Month]\n"
             << EeGrossPmts[Month] + ErGrossPmts[Month] << " EeGrossPmts[Month] 
+ ErGrossPmts[Month]\n"
@@ -1292,7 +1293,8 @@ void AccountValue::TxAscertainDesiredPayment()
             << Month << " Month\n"
             << LMI_FLUSH
             ;
-    LMI_ASSERT(materially_equal(GrossPmts[Month], EeGrossPmts[Month] + 
ErGrossPmts[Month]));
+//  LMI_ASSERT(materially_equal(GrossPmts[Month], EeGrossPmts[Month] + 
ErGrossPmts[Month]));
+    LMI_ASSERT(GrossPmts[Month] == EeGrossPmts[Month] + ErGrossPmts[Month]);
 }
 
 /// Limit payment (e.g., to the non-MEC maximum).
@@ -1312,7 +1314,8 @@ void AccountValue::TxLimitPayment(double a_maxpmt)
     // we shouldn't.
 // TODO ?? TAXATION !! Clean this up, and put GPT limit here, on prem net of 
WD.
 
-    if(!(materially_equal(GrossPmts[Month], EeGrossPmts[Month] + 
ErGrossPmts[Month])))
+    if(GrossPmts[Month] != EeGrossPmts[Month] + ErGrossPmts[Month])
+//  if(!(materially_equal(GrossPmts[Month], EeGrossPmts[Month] + 
ErGrossPmts[Month])))
         warning()
             << GrossPmts[Month] << " GrossPmts[Month]\n"
             << EeGrossPmts[Month] + ErGrossPmts[Month] << " EeGrossPmts[Month] 
+ ErGrossPmts[Month]\n"
@@ -1322,7 +1325,8 @@ void AccountValue::TxLimitPayment(double a_maxpmt)
             << Month << " Month\n"
             << LMI_FLUSH
             ;
-    LMI_ASSERT(materially_equal(GrossPmts[Month], EeGrossPmts[Month] + 
ErGrossPmts[Month]));
+//  LMI_ASSERT(materially_equal(GrossPmts[Month], EeGrossPmts[Month] + 
ErGrossPmts[Month]));
+    LMI_ASSERT(GrossPmts[Month] == EeGrossPmts[Month] + ErGrossPmts[Month]);
 
     if(mce_reduce_prem == yare_input_.AvoidMecMethod && 
!Irc7702A_->IsMecAlready())
         {
@@ -1332,7 +1336,7 @@ void AccountValue::TxLimitPayment(double a_maxpmt)
             gross_1035 = External1035Amount + Internal1035Amount;
             }
         currency gross_pmt_without_1035 = GrossPmts[Month] - gross_1035;
-        gross_pmt_without_1035 = std::min<double>(gross_pmt_without_1035, 
a_maxpmt);
+        gross_pmt_without_1035 = currency(std::min(gross_pmt_without_1035.d(), 
a_maxpmt));
         // TODO ?? For now at least, reduce employee premium first.
         progressively_limit
             (EeGrossPmts[Month]
@@ -1346,7 +1350,8 @@ void AccountValue::TxLimitPayment(double a_maxpmt)
         GrossPmts[Month] = EeGrossPmts[Month] + ErGrossPmts[Month];
         }
 
-    LMI_ASSERT(materially_equal(GrossPmts[Month], EeGrossPmts[Month] + 
ErGrossPmts[Month]));
+//  LMI_ASSERT(materially_equal(GrossPmts[Month], EeGrossPmts[Month] + 
ErGrossPmts[Month]));
+    LMI_ASSERT(GrossPmts[Month] == EeGrossPmts[Month] + ErGrossPmts[Month]);
 
     if(Solving || mce_run_gen_curr_sep_full == RunBasis_)
         {
@@ -1360,12 +1365,8 @@ void AccountValue::TxLimitPayment(double a_maxpmt)
         GrossPmts[Month] = EeGrossPmts[Month] + ErGrossPmts[Month];
         }
 
-    LMI_ASSERT
-        (materially_equal
-            (GrossPmts[Month]
-            ,EeGrossPmts[Month] + ErGrossPmts[Month]
-            )
-        );
+//  LMI_ASSERT(materially_equal(GrossPmts[Month], EeGrossPmts[Month] + 
ErGrossPmts[Month]));
+    LMI_ASSERT(GrossPmts[Month] == EeGrossPmts[Month] + ErGrossPmts[Month]);
 }
 
 //============================================================================
@@ -1386,7 +1387,7 @@ void AccountValue::TxRecognizePaymentFor7702A
     currency kludge_account_value = std::max(TotalAccountValue(), 
HoneymoonValue);
     if(0 == Year && 0 == Month)
         {
-        kludge_account_value = Dcv;
+        kludge_account_value = currency(Dcv);
         }
     kludge_account_value = std::max
         (HoneymoonValue
@@ -1402,12 +1403,12 @@ void AccountValue::TxRecognizePaymentFor7702A
     currency amount_paid_7702A = a_pmt;
     Irc7702A_->UpdatePmt7702A
         (Dcv
-        ,amount_paid_7702A
+        ,amount_paid_7702A.d()
         ,a_this_payment_is_unnecessary
-        ,AnnualTargetPrem
+        ,AnnualTargetPrem.d()
         ,YearsTotLoadTgtLowestPremtax
         ,YearsTotLoadExcLowestPremtax
-        ,kludge_account_value
+        ,kludge_account_value.d()
         );
 }
 
@@ -1443,7 +1444,7 @@ void AccountValue::TxAcceptPayment(currency a_pmt)
 
     process_payment(net_pmt);
 
-    Dcv += std::max(currency(), net_pmt);
+    Dcv += std::max(0.0, net_pmt.d());
     LMI_ASSERT(0.0 <= Dcv);
 
     if(HoneymoonActive)
@@ -1489,7 +1490,7 @@ currency AccountValue::GetPremLoad
 {
     currency excess_portion;
     // All excess.
-    if(0.0 == UnusedTargetPrem)
+    if(currency() == UnusedTargetPrem)
         {
         excess_portion = a_pmt;
         }
@@ -1497,12 +1498,12 @@ currency AccountValue::GetPremLoad
     else if(UnusedTargetPrem < a_pmt)
         {
         excess_portion = a_pmt - UnusedTargetPrem;
-        UnusedTargetPrem = 0.0;
+        UnusedTargetPrem = currency();
         }
     // All target.
     else
         {
-        excess_portion = 0.0;
+        excess_portion = currency();
         UnusedTargetPrem -= a_pmt;
         }
     currency target_portion = a_pmt - excess_portion;
@@ -1520,7 +1521,7 @@ currency AccountValue::GetPremLoad
     CumulativeSalesLoad += sales_load_;
 
     premium_tax_load_ = PremiumTax_->calculate_load
-        (a_pmt - a_portion_exempt_from_premium_tax
+        ((a_pmt - a_portion_exempt_from_premium_tax).d()
         ,*StratifiedCharges_
         );
 
@@ -1582,16 +1583,16 @@ void AccountValue::TxLoanRepay()
 
     // TODO ?? This idiom seems too cute. And it can return -0.0 .
     // Maximum repayment is total debt.
-    ActualLoan = -std::min(-RequestedLoan, doubleize(RegLnBal) + 
doubleize(PrfLnBal));
+    ActualLoan = currency() - std::min(currency() - RequestedLoan, RegLnBal + 
PrfLnBal); // unary operator-()
 
     process_distribution(ActualLoan);
-    LMI_ASSERT(0.0 == progressively_reduce(AVRegLn , AVPrfLn , 
currency(-ActualLoan)));
-    LMI_ASSERT(0.0 == progressively_reduce(RegLnBal, PrfLnBal, 
currency(-ActualLoan)));
+    LMI_ASSERT(0.0 == progressively_reduce(AVRegLn , AVPrfLn , currency() - 
ActualLoan)); // unary operator-()
+    LMI_ASSERT(0.0 == progressively_reduce(RegLnBal, PrfLnBal, currency() - 
ActualLoan)); // unary operator-()
 
 // This seems wrong. If we're changing something that's invariant among
 // bases, why do we change it for each basis?
 // TODO ?? Shouldn't this be moved to FinalizeMonth()?
-    InvariantValues().NewCashLoan[Year] = ActualLoan;
+    InvariantValues().NewCashLoan[Year] = ActualLoan.d();
     // TODO ?? Consider changing loan_ullage_[Year] here.
 }
 
@@ -1619,7 +1620,7 @@ void AccountValue::TxSetBOMAV()
                     ? std::max
                         (term_specamt(0) + base_specamt(0)
                         ,round_death_benefit().c(NetPmts[0] * 
YearsCorridorFactor)
-                        )
+                        ).d()
                     : yare_input_.InforceSpecAmtLoadBase
                 )
             );
@@ -1639,7 +1640,7 @@ void AccountValue::TxSetBOMAV()
 
     process_deduction(MonthsPolicyFees + SpecAmtLoad);
 
-    Dcv -= MonthsPolicyFees + SpecAmtLoad;
+    Dcv -= (MonthsPolicyFees + SpecAmtLoad).d();
     Dcv = std::max(0.0, Dcv);
 }
 
@@ -1705,12 +1706,12 @@ void AccountValue::TxSetDeathBft()
             // Specamt is a floor under DB (and therefore zero here)
             // because this option defines the DB as the minimum
             // required by the corridor (but not less than zero).
-            DBIgnoringCorr = 0.0;
-            DB7702A        = 0.0;
+            DBIgnoringCorr = currency();
+            DB7702A        = currency();
             }
             break;
         }
-    LMI_ASSERT(0.0 <= DBIgnoringCorr);
+    LMI_ASSERT(currency() <= DBIgnoringCorr);
 
     // Surrender charges are generally ignored here, but any negative
     // surrender charge must be subtracted, increasing the account value.
@@ -1737,8 +1738,8 @@ void AccountValue::TxSetDeathBft()
     // TAXATION !! Use DB_Irc7702BftIsSpecAmt
     DB7702A = DBReflectingCorr + TermDB;
 
-    DcvDeathBft = std::max<double>
-        (DBIgnoringCorr
+    DcvDeathBft = std::max
+        (DBIgnoringCorr.d()
         ,   (
                 YearsCorridorFactor
             *   (   Dcv
@@ -1808,12 +1809,12 @@ void AccountValue::EndTermRider(bool convert)
         {
         ChangeSpecAmtBy(TermSpecAmt);
         }
-    TermSpecAmt = 0.0;
-    TermDB = 0.0;
+    TermSpecAmt = currency();
+    TermDB      = currency();
     // Carry the new term spec amt forward into all future years.
     for(int j = Year; j < BasicValues::GetLength(); ++j)
         {
-        InvariantValues().TermSpecAmt[j] = TermSpecAmt;
+        InvariantValues().TermSpecAmt[j] = TermSpecAmt.d();
         }
 }
 
@@ -1843,7 +1844,7 @@ void AccountValue::TxSetCoiCharge()
 // TAXATION !! Should this be handled at the same time as GPT forceouts?
 
     DcvNaar = material_difference
-        (std::max<double>(DcvDeathBft, DBIgnoringCorr) * DBDiscountRate[Year]
+        (std::max<double>(DcvDeathBft, DBIgnoringCorr.d()) * 
DBDiscountRate[Year]
         ,std::max(0.0, Dcv)
         );
     // DCV need not be rounded.
@@ -1888,15 +1889,15 @@ void AccountValue::TxSetCoiCharge()
 
 void AccountValue::TxSetRiderDed()
 {
-    AdbCharge = 0.0;
+    AdbCharge = currency();
     if(yare_input_.AccidentalDeathBenefit)
         {
         AdbCharge = round_rider_charges().c
-            (YearsAdbRate * std::min<double>(ActualSpecAmt, AdbLimit)
+            (YearsAdbRate * std::min(ActualSpecAmt.d(), AdbLimit)
             );
         }
 
-    SpouseRiderCharge = 0.0;
+    SpouseRiderCharge = currency();
     if(yare_input_.SpouseRider)
         {
         SpouseRiderCharge = round_rider_charges().c
@@ -1904,7 +1905,7 @@ void AccountValue::TxSetRiderDed()
             );
         }
 
-    ChildRiderCharge = 0.0;
+    ChildRiderCharge = currency();
     if(yare_input_.ChildRider)
         {
         ChildRiderCharge = round_rider_charges().c
@@ -1912,7 +1913,7 @@ void AccountValue::TxSetRiderDed()
             );
         }
 
-    TermCharge = 0.0;
+    TermCharge    = currency();
     DcvTermCharge = 0.0;
     if(TermRiderActive)
         {
@@ -1926,7 +1927,7 @@ void AccountValue::TxSetRiderDed()
         DcvTermCharge = YearsDcvCoiRate * TermDB * DBDiscountRate[Year];
         }
 
-    WpCharge = 0.0;
+    WpCharge    = currency();
     DcvWpCharge = 0.0;
     if(yare_input_.WaiverOfPremiumBenefit)
         {
@@ -1935,9 +1936,9 @@ void AccountValue::TxSetRiderDed()
             case oe_waiver_times_specamt:
                 {
                 WpCharge = round_rider_charges().c
-                    (YearsWpRate * std::min<double>(ActualSpecAmt, WpLimit)
+                    (YearsWpRate * std::min(ActualSpecAmt.d(), WpLimit)
                     );
-                DcvWpCharge = WpCharge;
+                DcvWpCharge = WpCharge.d();
                 }
                 break;
             case oe_waiver_times_deductions:
@@ -1981,11 +1982,11 @@ void AccountValue::TxDoMlyDed()
     if(TermRiderActive && TermCanLapse && (AVGenAcct + AVSepAcct - CoiCharge) 
< TermCharge)
         {
         EndTermRider(false);
-        TermCharge = 0.0;
+        TermCharge = currency();
         }
 
     // 'Simple' riders are the same for AV and DCV.
-    double simple_rider_charges =
+    currency simple_rider_charges =
             AdbCharge
         +   SpouseRiderCharge
         +   ChildRiderCharge
@@ -1993,7 +1994,7 @@ void AccountValue::TxDoMlyDed()
 
     double dcv_mly_ded =
             DcvCoiCharge
-        +   simple_rider_charges
+        +   simple_rider_charges.d()
         +   DcvTermCharge
         +   DcvWpCharge
         ;
@@ -2014,7 +2015,7 @@ void AccountValue::TxDoMlyDed()
     // determined.
     MlyDed += MonthsPolicyFees + SpecAmtLoad;
 
-    YearsTotalNetCoiCharge += NetCoiCharge;
+    YearsTotalNetCoiCharge += NetCoiCharge.d();
 
     SepAcctValueAfterDeduction = AVSepAcct;
 }
@@ -2072,15 +2073,15 @@ void AccountValue::TxTakeSepAcctLoad()
         {
         double stratified_load = StratifiedCharges_->stratified_sepacct_load
             (GenBasis_
-            ,AssetsPostBom
-            ,CumPmtsPostBom
+            ,AssetsPostBom.d()
+            ,CumPmtsPostBom.d()
             ,database().query<double>(DB_DynSepAcctLoadLimit)
             );
 
         double tiered_comp = 0.0;
         if(oe_asset_charge_load == 
database().query<oenum_asset_charge_type>(DB_AssetChargeType))
             {
-            tiered_comp = 
StratifiedCharges_->tiered_asset_based_compensation(AssetsPostBom);
+            tiered_comp = 
StratifiedCharges_->tiered_asset_based_compensation(AssetsPostBom.d());
             }
         if(0.0 != tiered_comp)
             {
@@ -2107,7 +2108,7 @@ void AccountValue::TxTakeSepAcctLoad()
     SepAcctLoad = round_interest_credit().c(YearsSepAcctLoadRate * AVSepAcct);
     process_deduction(SepAcctLoad);
     YearsTotalSepAcctLoad += SepAcctLoad;
-    Dcv -= SepAcctLoad;
+    Dcv -= SepAcctLoad.d();
     Dcv = std::max(0.0, Dcv);
 }
 
@@ -2148,8 +2149,8 @@ void AccountValue::ApplyDynamicMandE(currency assets)
 
     // Annual separate-account rates.
 
-    double m_and_e_rate = StratifiedCharges_->tiered_m_and_e(GenBasis_, 
assets);
-    double imf_rate = 
StratifiedCharges_->tiered_investment_management_fee(assets);
+    double m_and_e_rate = StratifiedCharges_->tiered_m_and_e(GenBasis_, 
assets.d());
+    double imf_rate = 
StratifiedCharges_->tiered_investment_management_fee(assets.d());
     if(0.0 != imf_rate)
         {
         alarum()
@@ -2159,7 +2160,7 @@ void AccountValue::ApplyDynamicMandE(currency assets)
         }
     double asset_comp_rate =
         (oe_asset_charge_spread == 
database().query<oenum_asset_charge_type>(DB_AssetChargeType))
-            ? StratifiedCharges_->tiered_asset_based_compensation(assets)
+            ? StratifiedCharges_->tiered_asset_based_compensation(assets.d())
             : 0.0
             ;
     if(0.0 != asset_comp_rate)
@@ -2226,25 +2227,25 @@ void AccountValue::TxCreditInt()
     if(0.0 < AVSepAcct)
         {
         SepAcctIntCred = InterestCredited(AVSepAcct, YearsSepAcctIntRate);
-        double gross   = InterestCredited(AVSepAcct, gross_sep_acct_rate);
-        notional_sep_acct_charge = gross - SepAcctIntCred;
+        double gross   = InterestCredited(AVSepAcct, gross_sep_acct_rate).d();
+        notional_sep_acct_charge = (gross - SepAcctIntCred).d();
         // Guard against catastrophic cancellation. Testing the
         // absolute values of the addends for material equality is not
         // sufficient, because the interest increment has already been
         // rounded.
-        double result = AVSepAcct + SepAcctIntCred;
+        double result = (AVSepAcct + SepAcctIntCred).d();
         if(result < 0.0 && 0.0 <= AVSepAcct)
             {
-            AVSepAcct = 0.0;
+            AVSepAcct = currency();
             }
         else
             {
-            AVSepAcct = result;
+            AVSepAcct = currency(result);
             }
         }
     else
         {
-        SepAcctIntCred = 0.0;
+        SepAcctIntCred = currency();
         }
 
     if(0.0 < AVGenAcct)
@@ -2268,7 +2269,7 @@ void AccountValue::TxCreditInt()
         }
     else
         {
-        GenAcctIntCred = 0.0;
+        GenAcctIntCred = currency();
         }
 
     LMI_ASSERT(0.0 <= Dcv);
@@ -2299,11 +2300,11 @@ void AccountValue::TxLoanInt()
 {
     // Reinitialize to zero before potential early exit, to sweep away
     // any leftover values (e.g., after a loan has been paid off).
-    RegLnIntCred = 0.0;
-    PrfLnIntCred = 0.0;
+    RegLnIntCred = currency();
+    PrfLnIntCred = currency();
 
     // Nothing more to do if there's no loan outstanding.
-    if(0.0 == RegLnBal && 0.0 == PrfLnBal)
+    if(currency() == RegLnBal && 0.0 == PrfLnBal)
         {
         return;
         }
@@ -2316,8 +2317,8 @@ void AccountValue::TxLoanInt()
     AVRegLn += RegLnIntCred;
     AVPrfLn += PrfLnIntCred;
 
-    double RegLnIntAccrued = InterestCredited(RegLnBal, YearsRegLnIntDueRate);
-    double PrfLnIntAccrued = InterestCredited(PrfLnBal, YearsPrfLnIntDueRate);
+    currency RegLnIntAccrued = InterestCredited(RegLnBal, 
YearsRegLnIntDueRate);
+    currency PrfLnIntAccrued = InterestCredited(PrfLnBal, 
YearsPrfLnIntDueRate);
 
     RegLnBal += RegLnIntAccrued;
     PrfLnBal += PrfLnIntAccrued;
@@ -2409,7 +2410,7 @@ void AccountValue::TxTakeWD()
         return;
         }
 
-    GrossWD = 0.0;
+    GrossWD = currency();
     RequestedWD = Outlay_->withdrawals()[Year];
 
     if(Debugging || 0.0 != RequestedWD)
@@ -2417,12 +2418,12 @@ void AccountValue::TxTakeWD()
         SetMaxWD();
         }
 
-    NetWD = 0.0;
+    NetWD = currency();
 
     // Nothing more to do if no withdrawal requested.
     if(0.0 == RequestedWD)
         {
-        withdrawal_ullage_[Year] = 0.0;
+        withdrawal_ullage_[Year] = currency();
 // This seems wrong. If we're changing something that's invariant among
 // bases, why do we change it for each basis?
 // TODO ?? Shouldn't this be moved to FinalizeMonth()?
@@ -2460,8 +2461,8 @@ void AccountValue::TxTakeWD()
     // Impose minimum amount on withdrawals.
     if(RequestedWD < MinWD)
         {
-        withdrawal_ullage_[Year] = 0.0;
-        NetWD = 0.0;
+        withdrawal_ullage_[Year] = currency();
+        NetWD = currency();
         }
 // TODO ?? If WD zero, skip some steps? Cannot simply return in this case
 // because user may prefer to shift to loans.
@@ -2482,10 +2483,10 @@ void AccountValue::TxTakeWD()
         // Even after the entire basis has been withdrawn, we still
         // take withdrawals if payments since have increased the basis.
         // TODO ?? Should RequestedWD be constrained by MaxWD and MinWD here?
-        if(0.0 == TaxBasis || std::min(TaxBasis, RequestedWD) < MinWD) // All 
loan
+        if(currency() == TaxBasis || std::min(TaxBasis, RequestedWD) < MinWD) 
// All loan
             {
-            withdrawal_ullage_[Year] = 0.0;
-            NetWD = 0.0;
+            withdrawal_ullage_[Year] = currency();
+            NetWD = currency();
             }
         else if(NetWD < TaxBasis) // All WD
             {
@@ -2567,7 +2568,7 @@ void AccountValue::TxTakeWD()
         return;
         }
 
-    GrossWD = round_withdrawal().c(NetWD + std::min<double>(WDFee, NetWD * 
WDFeeRate));
+    GrossWD = round_withdrawal().c(NetWD.d() + std::min(WDFee.d(), NetWD * 
WDFeeRate));
 
     // Free partial surrenders: for instance, the first 20% of account
     // value might be withdrawn each policy year free of surrender
@@ -2585,13 +2586,13 @@ void AccountValue::TxTakeWD()
         LMI_ASSERT(AVPrfLn == PrfLnBal);
         LMI_ASSERT(av == AVGenAcct + AVSepAcct);
         double free_wd = FreeWDProportion[Year] * av;
-        non_free_wd = std::max(0.0, doubleize(GrossWD) - free_wd);
+        non_free_wd = currency(std::max(0.0, GrossWD.d() - free_wd));
         }
     double partial_surrchg = non_free_wd * surrchg_proportion;
     GrossWD += round_withdrawal().c(partial_surrchg);
 
     process_distribution(GrossWD);
-    Dcv -= GrossWD;
+    Dcv -= GrossWD.d();
     Dcv = std::max(0.0, Dcv);
 
     switch(YearsDBOpt)
@@ -2673,7 +2674,7 @@ void AccountValue::TxTakeWD()
             {
             // TODO ?? TAXATION !! What if reference argument
             // 'premiums_paid_increment' is modified?
-            double premiums_paid_increment = -GrossWD;
+            double premiums_paid_increment = 0.0 - GrossWD.d(); // unary 
operator-()
             Irc7702_->ProcessGptPmt(Year, premiums_paid_increment);
             }
         }
@@ -2681,7 +2682,7 @@ void AccountValue::TxTakeWD()
 // This seems wrong. If we're changing something that's invariant among
 // bases, why do we change it for each basis?
 // TODO ?? Shouldn't this be moved to FinalizeMonth()?
-    InvariantValues().NetWD[Year] = NetWD;
+    InvariantValues().NetWD[Year] = NetWD.d();
 }
 
 //============================================================================
@@ -2689,10 +2690,10 @@ void AccountValue::TxTakeWD()
 void AccountValue::SetMaxLoan()
 {
     double max_loan =
-          (AVGenAcct + AVSepAcct) * MaxLoanAVMult
-        + (AVRegLn + AVPrfLn)
-        - anticipated_deduction(MaxLoanDed_)
-        - std::max(currency(), SurrChg())
+          (AVGenAcct + AVSepAcct).d() * MaxLoanAVMult
+        + (AVRegLn + AVPrfLn).d()
+        - anticipated_deduction(MaxLoanDed_).d()
+        - std::max(currency(), SurrChg()).d()
         ;
 
     // Illustrations generally permit loans only on anniversary.
@@ -2764,9 +2765,9 @@ void AccountValue::TxTakeLoan()
         }
 
     // Nothing more to do if no loan requested.
-    if(RequestedLoan <= 0.0)
+    if(RequestedLoan <= currency())
         {
-        loan_ullage_[Year] = 0.0;
+        loan_ullage_[Year] = currency();
         return;
         }
 
@@ -2785,7 +2786,7 @@ void AccountValue::TxTakeLoan()
         ActualLoan = std::min(max_loan_increment, RequestedLoan);
         ActualLoan = std::max(ActualLoan, currency());
         // TODO ?? Shouldn't this happen in FinalizeMonth()?
-        InvariantValues().NewCashLoan[Year] = ActualLoan;
+        InvariantValues().NewCashLoan[Year] = ActualLoan.d();
         }
 
     {
@@ -2875,7 +2876,8 @@ void AccountValue::TxTestLapse()
             (       NoLapseMinAge <= Year + BasicValues::GetIssueAge()
                 &&  NoLapseMinDur <= Year
             ||      CumPmts < CumNoLapsePrem
-                &&  !materially_equal(CumPmts, CumNoLapsePrem)
+//              &&  !materially_equal(CumPmts, CumNoLapsePrem)
+                &&  CumPmts != CumNoLapsePrem
             )
             {
             NoLapseActive = false;
@@ -2936,8 +2938,8 @@ void AccountValue::TxTestLapse()
         {
         if(NoLapseActive && lapse_test_csv < 0.0)
             {
-            AVGenAcct = 0.0;
-            AVSepAcct = 0.0;
+            AVGenAcct = currency();
+            AVSepAcct = currency();
             // TODO ?? Can't this be done elsewhere?
             VariantValues().CSVNet[Year] = 0.0;
             }
@@ -2960,9 +2962,9 @@ void AccountValue::FinalizeMonth()
         {
         if(0 == Year && 0 == Month)
             {
-            InvariantValues().External1035Amount = External1035Amount;
-            InvariantValues().Internal1035Amount = Internal1035Amount;
-            InvariantValues().Dumpin = Dumpin;
+            InvariantValues().External1035Amount = External1035Amount.d();
+            InvariantValues().Internal1035Amount = Internal1035Amount.d();
+            InvariantValues().Dumpin = Dumpin.d();
             }
 
         // TAXATION !! We could also capture MEC status on other bases here.
diff --git a/ihs_avsolve.cpp b/ihs_avsolve.cpp
index c4e5396..b22c769 100644
--- a/ihs_avsolve.cpp
+++ b/ihs_avsolve.cpp
@@ -67,7 +67,7 @@ class SolveHelper
 //  double operator()(currency a_CandidateValue)
     double operator()(double a_CandidateValue)
         {
-        return av.SolveTest(currency(a_CandidateValue));
+        return av.SolveTest(currency(a_CandidateValue)).d();
         }
 };
 
@@ -184,9 +184,11 @@ currency AccountValue::SolveTest(currency a_CandidateValue)
     currency most_negative_csv(0.0);
     if(no_lapse_dur < SolveTargetDuration_)
         {
-        most_negative_csv = *std::min_element
-            (VariantValues().CSVNet.begin() + no_lapse_dur
-            ,VariantValues().CSVNet.begin() + SolveTargetDuration_
+        most_negative_csv = currency
+            (*std::min_element
+                (VariantValues().CSVNet.begin() + no_lapse_dur
+                ,VariantValues().CSVNet.begin() + SolveTargetDuration_
+                )
             );
         }
 
@@ -206,7 +208,7 @@ currency AccountValue::SolveTest(currency a_CandidateValue)
         );
     currency worst_negative = std::min
         (most_negative_csv
-        ,currency(-greatest_ullage) // really want a unary-negation operator
+        ,currency() - greatest_ullage // really want a unary-negation operator
         );
 
     // SolveTargetDuration_ is in origin one. That's natural for loop
@@ -215,10 +217,11 @@ currency AccountValue::SolveTest(currency 
a_CandidateValue)
     currency value = currency(VariantValues().CSVNet[SolveTargetDuration_ - 
1]);
     if(mce_solve_for_target_naar == SolveTarget_)
         {
-        value =
+        value = currency
+            (
               VariantValues().EOYDeathBft[SolveTargetDuration_ - 1]
             - VariantValues().AcctVal    [SolveTargetDuration_ - 1]
-            ;
+            );
         }
     if(worst_negative < 0.0)
         {
@@ -370,7 +373,7 @@ currency AccountValue::Solve
                 (  0 == SolveBeginYear_
                 && yare_input_.EffectiveDate == yare_input_.InforceAsOfDate
                 ,false
-                );
+                ).d();
             }
             break;
         case mce_solve_ee_prem:
diff --git a/ihs_avstrtgy.cpp b/ihs_avstrtgy.cpp
index 27eb18a..fb3197f 100644
--- a/ihs_avstrtgy.cpp
+++ b/ihs_avstrtgy.cpp
@@ -147,8 +147,9 @@ void AccountValue::PerformSpecAmtStrategy()
             strategy = mce_sa_input_scalar;
             }
         currency z = CalculateSpecAmtFromStrategy(j, 0, explicit_value, 
strategy);
-        // Shouldn't rounding be done upstream?
-        DeathBfts_->set_specamt(currency(round_specamt()(std::max(m, z))), j, 
1 + j);
+        // Shouldn't rounding be done upstream? Was it?
+        DeathBfts_->set_specamt(currency(std::max(m, z)), j, 1 + j); // 
round(currency)?
+//      DeathBfts_->set_specamt(currency(round_specamt()(std::max(m, z))), j, 
1 + j); // round(currency)?
         if
             (  j == InforceYear
             && yare_input_.EffectiveDate != yare_input_.InforceAsOfDate
@@ -179,8 +180,9 @@ void AccountValue::PerformSupplAmtStrategy()
         currency explicit_value = DeathBfts_->supplamt()[j];
         mcenum_sa_strategy strategy = 
yare_input_.SupplementalAmountStrategy[j];
         currency z = CalculateSpecAmtFromStrategy(j, 0, explicit_value, 
strategy);
-        // Shouldn't rounding be done upstream?
-        DeathBfts_->set_supplamt(currency(round_specamt()(std::max(m, z))), j, 
1 + j);
+        // Shouldn't rounding be done upstream? Was it?
+//      DeathBfts_->set_supplamt(currency(round_specamt()(std::max(m, z))), j, 
1 + j); // round(currency)?
+        DeathBfts_->set_supplamt(currency(std::max(m, z)), j, 1 + j); // 
round(currency)?
         }
 }
 
diff --git a/ihs_basicval.cpp b/ihs_basicval.cpp
index 9b2702f..2b79304 100644
--- a/ihs_basicval.cpp
+++ b/ihs_basicval.cpp
@@ -565,7 +565,7 @@ void BasicValues::Init7702()
 /// These loads should instead reflect the lowest premium-tax rate.
             ,Loads_->target_premium_load_excluding_premium_tax()
             ,Loads_->excess_premium_load_excluding_premium_tax()
-            ,InitialTargetPremium
+            ,InitialTargetPremium.d()
             ,round_min_premium()
             ,round_max_premium()
             ,round_min_specamt()
@@ -614,10 +614,10 @@ currency BasicValues::GetAnnualTgtPrem(int a_year, 
currency a_specamt) const
 void BasicValues::SetPermanentInvariants()
 {
     // maybe implement query_into<currency>
-    MinIssSpecAmt      = database().query<int>(DB_MinIssSpecAmt);
-    MinIssBaseSpecAmt  = database().query<int>(DB_MinIssBaseSpecAmt);
-    MinRenlSpecAmt     = database().query<int>(DB_MinRenlSpecAmt);
-    MinRenlBaseSpecAmt = database().query<int>(DB_MinRenlBaseSpecAmt);
+    MinIssSpecAmt      = currency(database().query<int>(DB_MinIssSpecAmt     
));
+    MinIssBaseSpecAmt  = currency(database().query<int>(DB_MinIssBaseSpecAmt 
));
+    MinRenlSpecAmt     = currency(database().query<int>(DB_MinRenlSpecAmt    
));
+    MinRenlBaseSpecAmt = 
currency(database().query<int>(DB_MinRenlBaseSpecAmt));
     database().query_into(DB_NoLapseDboLvlOnly    , NoLapseDboLvlOnly);
     database().query_into(DB_NoLapseUnratedOnly   , NoLapseUnratedOnly);
     database().query_into(DB_DboChgCanIncrSpecAmt , OptChgCanIncrSA);
@@ -635,7 +635,7 @@ void BasicValues::SetPermanentInvariants()
     database().query_into(DB_MinPremType          , MinPremType);
     database().query_into(DB_TgtPremType          , TgtPremType);
     database().query_into(DB_TgtPremFixedAtIssue  , TgtPremFixedAtIssue);
-    TgtPremMonthlyPolFee = database().query<int>(DB_TgtPremMonthlyPolFee);
+    TgtPremMonthlyPolFee = 
currency(database().query<int>(DB_TgtPremMonthlyPolFee));
     // Assertion: see comments on GetModalPremTgtFromTable().
     LMI_ASSERT(0.0 == TgtPremMonthlyPolFee || oe_modal_table == TgtPremType);
     database().query_into(DB_CurrCoiTable0Limit   , CurrCoiTable0Limit);
@@ -661,8 +661,8 @@ void BasicValues::SetPermanentInvariants()
 //  AdbLimit         = database().query<int>(DB_AdbLimit        );
 //  WpLimit          = database().query<int>(DB_WpLimit         );
 //  SpecAmtLoadLimit = database().query<int>(DB_SpecAmtLoadLimit);
-    MinWD            = database().query<int>(DB_MinWd           );
-    WDFee            = database().query<int>(DB_WdFee           );
+    MinWD            = currency(database().query<int>(DB_MinWd           ));
+    WDFee            = currency(database().query<int>(DB_WdFee           ));
     database().query_into(DB_WdFeeRate            , WDFeeRate);
     database().query_into(DB_AllowChangeToDbo2    , AllowChangeToDBO2);
     database().query_into(DB_AllowSpecAmtIncr     , AllowSAIncr);
@@ -770,7 +770,7 @@ void BasicValues::SetPermanentInvariants()
     database().query_into(DB_Effective7702DboRop, Effective7702DboRop);
     TermIsDbFor7702     = oe_7702_term_is_db == 
database().query<oenum_7702_term>(DB_TermIsQABOrDb7702 );
     TermIsDbFor7702A    = oe_7702_term_is_db == 
database().query<oenum_7702_term>(DB_TermIsQABOrDb7702A);
-    MaxNAAR             = yare_input_.MaximumNaar;
+    MaxNAAR             = currency(yare_input_.MaximumNaar);
 
     database().query_into(DB_MinPremIntSpread, MinPremIntSpread_);
 }
@@ -1083,8 +1083,8 @@ currency BasicValues::GetModalPremGLP
     // for GPT reimplementation.
     double z = Irc7702_->CalculateGLP
         (a_duration
-        ,a_bft_amt
-        ,a_specamt
+        ,a_bft_amt.d()
+        ,a_specamt.d()
         ,Irc7702_->GetLeastBftAmtEver()
         ,effective_dbopt_7702(DeathBfts_->dbopt()[0], Effective7702DboRop)
         );
@@ -1107,8 +1107,8 @@ currency BasicValues::GetModalPremGSP
 {
     double z = Irc7702_->CalculateGSP
         (a_duration
-        ,a_bft_amt
-        ,a_specamt
+        ,a_bft_amt.d()
+        ,a_specamt.d()
         ,Irc7702_->GetLeastBftAmtEver()
         );
 
@@ -1203,7 +1203,7 @@ std::pair<double,double> BasicValues::approx_mly_ded
     if(yare_input_.AccidentalDeathBenefit)
         {
         double const r = MortalityRates_->AdbRates()[year];
-        mly_ded += r * std::min<double>(specamt, AdbLimit);
+        mly_ded += r * std::min(specamt.d(), AdbLimit);
         }
 
     if(yare_input_.SpouseRider)
@@ -1221,7 +1221,7 @@ std::pair<double,double> BasicValues::approx_mly_ded
     if(true) // Written thus for parallelism and to keep 'r' local.
         {
         double const r = Loads_->specified_amount_load(mce_gen_curr)[year];
-        mly_ded += r * std::min<double>(specamt, SpecAmtLoadLimit);
+        mly_ded += r * std::min(specamt.d(), SpecAmtLoadLimit);
         }
 
     mly_ded += Loads_->monthly_policy_fee(mce_gen_curr)[year];
@@ -1235,7 +1235,7 @@ std::pair<double,double> BasicValues::approx_mly_ded
             {
             case oe_waiver_times_specamt:
                 {
-                mly_ded += r * std::min<double>(specamt, WpLimit);
+                mly_ded += r * std::min(specamt.d(), WpLimit);
                 }
                 break;
             case oe_waiver_times_deductions:
@@ -1291,7 +1291,7 @@ std::pair<double,double> BasicValues::approx_mly_ded_ex
     if(yare_input_.AccidentalDeathBenefit)
         {
         double const r = MortalityRates_->AdbRates()[year];
-        er_ded += r * std::min<double>(specamt, AdbLimit);
+        er_ded += r * std::min(specamt.d(), AdbLimit);
         }
 
     // Paid by ee.
@@ -1312,7 +1312,7 @@ std::pair<double,double> BasicValues::approx_mly_ded_ex
     if(true) // Written thus for parallelism and to keep 'r' local.
         {
         double const r = Loads_->specified_amount_load(mce_gen_curr)[year];
-        er_ded += r * std::min<double>(specamt, SpecAmtLoadLimit);
+        er_ded += r * std::min(specamt.d(), SpecAmtLoadLimit);
         }
 
     // Paid by er.
@@ -1326,7 +1326,7 @@ std::pair<double,double> BasicValues::approx_mly_ded_ex
             case oe_waiver_times_specamt:
                 {
                 // Paid by er. (In this case, WP excludes term.)
-                er_ded += r * std::min<double>(specamt, WpLimit);
+                er_ded += r * std::min(specamt.d(), WpLimit);
                 }
                 break;
             case oe_waiver_times_deductions:
diff --git a/ledger_invariant_init.cpp b/ledger_invariant_init.cpp
index 103d149..e759f28 100644
--- a/ledger_invariant_init.cpp
+++ b/ledger_invariant_init.cpp
@@ -143,7 +143,7 @@ void LedgerInvariant::Init(BasicValues const* b)
     // Scalable scalars.
 
     // SOMEDAY !! Things indexed with '[0]' should probably use inforce year 
instead.
-    InitBaseSpecAmt            = b->DeathBfts_->specamt()[0];
+    InitBaseSpecAmt            = (b->DeathBfts_->specamt()[0]).d();
     InitTermSpecAmt            = TermSpecAmt[0];
     ChildRiderAmount           = b->yare_input_.ChildRiderAmount;
     SpouseRiderAmount          = b->yare_input_.SpouseRiderAmount;
@@ -735,7 +735,7 @@ void LedgerInvariant::ReInit(BasicValues const* b)
         }
     SpecAmt                    = doubleize(b->DeathBfts_->specamt());
 
-    InitBaseSpecAmt            = b->DeathBfts_->specamt()[0];
+    InitBaseSpecAmt            = (b->DeathBfts_->specamt()[0]).d();
     InitTermSpecAmt            = TermSpecAmt[0];
 
     IsMec                      = false;
diff --git a/ledgervalues.cpp b/ledgervalues.cpp
index be75c42..7139419 100644
--- a/ledgervalues.cpp
+++ b/ledgervalues.cpp
@@ -40,10 +40,10 @@ double IllusVal::run(Input const& input)
     AccountValue av(input);
     av.SetDebugFilename(filename_);
 
-    double z = av.RunAV();
+    currency z = av.RunAV();
     ledger_ = av.ledger_from_av();
 
-    return z;
+    return z.d(); // or just change return type to currency?
 }
 
 std::shared_ptr<Ledger const> IllusVal::ledger() const
diff --git a/solve.cpp b/solve.cpp
index df49fcb..08176c8 100644
--- a/solve.cpp
+++ b/solve.cpp
@@ -84,9 +84,9 @@ currency SolveTest()
     // IHS !! Start counting only at end of no-lapse period--lmi does that 
already.
     for(int j = 0; j < ThatSolveTgtYear; ++j)
         {
-        Negative = std::min<double>
+        Negative = std::min
             (Negative
-            ,ConstThat->VariantValues().CSVNet[j]
+            ,currency(ConstThat->VariantValues().CSVNet[j])
 // Ideally, it'd be this:
 //          ,std::min(ConstThat->VariantValues().CSVNet[j], 
ConstThat->loan_ullage_[j])
 // but the antediluvian branch doesn't calculate ullage at all.
@@ -133,7 +133,7 @@ currency SolveTest()
             break;
         case mce_solve_for_target_csv:
             {
-            y = ThatSolveTargetValue;
+            y = currency(ThatSolveTargetValue);
             }
             break;
         case mce_solve_for_target_naar: // Fall through.
@@ -155,7 +155,7 @@ inline static double SolveSpecAmt(double CandidateValue)
 {
 // IHS !! Change surrchg when SA changes?
     That->SolveSetSpecAmt(currency(CandidateValue), ThatSolveBegYear, 
ThatSolveEndYear);
-    return only_set_values ? 0.0 : SolveTest();
+    return only_set_values ? 0.0 : SolveTest().d();
 }
 
 //============================================================================
@@ -163,7 +163,7 @@ inline static double SolvePrem(double CandidateValue)
 //inline static double SolvePrem(currency CandidateValue)
 {
     That->SolveSetPmts(currency(CandidateValue), ThatSolveBegYear, 
ThatSolveEndYear);
-    return only_set_values ? 0.0 : SolveTest();
+    return only_set_values ? 0.0 : SolveTest().d();
 }
 
 //============================================================================
@@ -171,7 +171,7 @@ inline static double SolveLoan(double CandidateValue)
 //inline static double SolveLoan(currency CandidateValue)
 {
     That->SolveSetLoans(currency(CandidateValue), ThatSolveBegYear, 
ThatSolveEndYear);
-    return only_set_values ? 0.0 : SolveTest();
+    return only_set_values ? 0.0 : SolveTest().d();
 }
 
 //============================================================================
@@ -179,7 +179,7 @@ inline static double SolveWD(double CandidateValue)
 //inline static double SolveWD(currency CandidateValue)
 {
     That->SolveSetWDs(currency(CandidateValue), ThatSolveBegYear, 
ThatSolveEndYear);
-    return only_set_values ? 0.0 : SolveTest();
+    return only_set_values ? 0.0 : SolveTest().d();
 }
 
 //============================================================================
@@ -283,7 +283,7 @@ currency AccountValue::Solve()
             LowerBound = 0.0;
             // If solved premium exceeds specified amount, there's a problem.
             // IHS !! Better to use the maximum SA, not the first SA?
-            UpperBound = DeathBfts_->specamt()[0];
+            UpperBound = DeathBfts_->specamt()[0].d();
             Decimals   = 2;
             SolveFn    = SolvePrem;
             }
@@ -353,6 +353,6 @@ currency AccountValue::Solve()
     only_set_values = !Solving;
     currency actual_solution = currency(Solution.first);
 
-    SolveFn(actual_solution);
+    SolveFn(actual_solution.d());
     return actual_solution;
 }



reply via email to

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