lmi-commits
[Top][All Lists]
Advanced

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

[lmi-commits] [lmi] valyuta/001 504f88c 3/4: See README.branch


From: Greg Chicares
Subject: [lmi-commits] [lmi] valyuta/001 504f88c 3/4: See README.branch
Date: Sat, 22 Aug 2020 11:37:10 -0400 (EDT)

branch: valyuta/001
commit 504f88c6cc1e925ef280deaffe2697a44029df73
Author: Gregory W. Chicares <gchicares@sbcglobal.net>
Commit: Gregory W. Chicares <gchicares@sbcglobal.net>

    See README.branch
    
    The two earlier commits on this branch set up some code changes that
    wouldn't compile. This commit rectifies them, often in a cavalier
    manner. Do read 'README.branch'.
---
 README.branch             | 131 +++++++++++++++++++++++
 account_value.hpp         |  61 ++++++-----
 accountvalue.cpp          |  54 +++++-----
 basic_values.hpp          |  36 ++++---
 basicvalues.cpp           |  20 ++--
 currency.hpp              |  58 ++++++++++-
 currency_test.cpp         |   6 ++
 death_benefits.cpp        |   4 +-
 gpt_specamt.cpp           |   2 +-
 group_values.cpp          |   3 +-
 ihs_acctval.cpp           |  90 ++++++++--------
 ihs_avdebug.cpp           |   4 +-
 ihs_avmly.cpp             | 260 ++++++++++++++++++++++++++++------------------
 ihs_avsolve.cpp           |  14 +--
 ihs_avstrtgy.cpp          |  62 +++++------
 ihs_basicval.cpp          | 176 +++++++++++++++++--------------
 ledger_invariant_init.cpp |   8 +-
 outlay.cpp                |  18 ++--
 solve.cpp                 |  40 +++----
 19 files changed, 663 insertions(+), 384 deletions(-)

diff --git a/README.branch b/README.branch
new file mode 100644
index 0000000..118cf9f
--- /dev/null
+++ b/README.branch
@@ -0,0 +1,131 @@
+// README for this branch.
+//
+// Copyright (C) 2020 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// https://savannah.nongnu.org/projects/lmi
+// email: <gchicares@sbcglobal.net>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+[based on 'README.branch' from branch valyuta/000]
+
+This branch explores replacing 'double' with a currency class. Several
+dozen data members of class AccountValue were changed to the new type.
+Those members were chosen as representative, for experimentation;
+many others should also be of currency type.
+
+The currency class is a coarse exploratory attempt.
+
+Various code changes were made:
+ - some explicit type conversions;
+ - std::max<double>, with template parameter explicitly specified, to
+   make it work with one double and one currency argument;
+ - an intermediate variable (max_loan) of type double, to hold some
+   calculations that are necessarily floating point before posting the
+   final result to the (currency) MaxLoan variable;
+
+For branch valyuta/000:
+ - similarly, restructuring of the NAAR calculation.
+but for this branch, that calculation wasn't restructured. It's not
+clear which way is better.
+
+The regression test does not pass. Some variables have never been
+rounded, though they are certainly integral numbers of cents in the
+problem domain:
+  AVSepAcct especially; also AVGenAcct sometimes
+and those are defects that should be fixed in the trunk. On branch
+valyuta/000, these were also noted:
+  DacTaxLoad, PremTaxLoad
+  SpecAmtLoad
+  RiderCharges
+  GrossPmt (for testdeck 'sample')
+but some of those defects have since been fixed in origin/master,
+while others (DacTaxLoad, PremTaxLoad) are deliberately not rounded
+(but total premium load is rounded).
+
+Future directions:
+
+The ledger class too often contains values that are plainly intended
+to represent an integral number of cents, but whose values look like
+  17696.499999999996362
+where a compiler might choose either closest representable neighbor,
+the choice depending on the phase of the moon; such false positives
+make regression testing too laborious. One idea is to "de-round" these
+values, but that would be to let the tail wag the dog.
+
+It seems far better to use a fixed-point class for values that are
+supposed to be an integral number of cents. The underlying type can
+be either integer or floating point; that's an implementation detail
+that needn't be resolved now. The crucial idea is that the internal
+representation should be cents; users of course expect to see dollars
+and cents, but that's a View concern--the Model wants to use cents.
+With a fixed-point class, a value such as 17696.499999999996362 that
+had actually been rounded would be stored as 1769650 exactly, and
+printed as 17696.50 (i.e., with an explicit decimal point) for
+regression testing (and of course for end-user report generation).
+
+This coarse implementation runs slowly:
+
+ * branch valyuta/000
+
+/opt/lmi/bin[0]$wine ./lmi_cli_shared.exe --accept --data_path=/opt/lmi/data 
--selftest
+Test solve speed: 1.805e-01 s mean;     180125 us least of   6 runs
+
+versus about 89000 us for production, i.e., half as fast. Numeric
+conversions probably account for most of that overhead.
+
+ * branch valyuta/001 [this branch]
+
+Test solve speed: 3.644e-01 s mean;     363663 us least of   3 runs
+
+...even with
+    using data_type = double;
+in 'currency.hpp'. That's much worse than the older branch because
+many more variables have been changed to 'currency' on this branch.
+
+With
+    using data_type = std::int64_t;
+it's a little faster:
+
+Test solve speed: 3.394e-01 s mean;     338108 us least of   3 runs
+
+That 25000 us improvement is small relative to the enormous overhead
+of this experimental work; but if that overhead can be substantially
+eliminated, the integer-math improvement is about fifteen percent of
+the speed measured in production.
+
+With
+    using currency = double;
+it's about as fast as production:
+
+Test solve speed: 9.283e-02 s mean;      92595 us least of  11 runs
+
+Regression testing finds many discrepancies. With
+    using data_type = double;
+a couple of testcases are off by a couple hundred dollars, about
+a dozen more are off by ten to a hundred, and about three dozen
+others by one to ten dollars; about five hundred differ by at least
+one cent, and seven hundred by less than one cent. It is unknown
+which of these regressions are actually errors; certainly some of
+the touchstone values (like non-integral-cents account values) are
+defects that should be fixed on master. Discrepancies in regression
+testing are about the same with
+    using data_type = std::int64_t;
+OTOH, with
+    using currency = double;
+only about a hundred testcases differ at all, of which about seventy
+are off by one cent or more, yet one testcase is off by about 1e+14
+due to an insane 'KFactor', apparently arising from catastrophic
+cancellation of a minute 'ProjectedCoi'.
diff --git a/account_value.hpp b/account_value.hpp
index 11dafbc..519d6d6 100644
--- a/account_value.hpp
+++ b/account_value.hpp
@@ -148,11 +148,11 @@ class LMI_SO AccountValue final
         ,int              a_InforceMonth = 0
         );
 
-    void   SolveSetSpecAmt      (currency a_CandidateValue);
-    void   SolveSetEePrem       (currency a_CandidateValue);
-    void   SolveSetErPrem       (currency a_CandidateValue);
-    void   SolveSetLoan         (currency a_CandidateValue);
-    void   SolveSetWD           (currency a_CandidateValue);
+    void   SolveSetSpecAmt      (double a_CandidateValue);
+    void   SolveSetEePrem       (double a_CandidateValue);
+    void   SolveSetErPrem       (double a_CandidateValue);
+    void   SolveSetLoan         (double a_CandidateValue);
+    void   SolveSetWD           (double a_CandidateValue);
 
     void   DebugPrint           ();
 
@@ -190,21 +190,21 @@ class LMI_SO AccountValue final
 
     bool PrecedesInforceDuration(int year, int month);
 
-    currency Solve(); // Antediluvian.
-    currency Solve
+    double Solve(); // Antediluvian.
+    double Solve
         (mcenum_solve_type   a_SolveType
         ,int                 a_SolveBeginYear
         ,int                 a_SolveEndYear
         ,mcenum_solve_target a_SolveTarget
-        ,currency            a_SolveTargetCsv
+        ,double              a_SolveTargetCsv
         ,int                 a_SolveTargetYear
         ,mcenum_gen_basis    a_SolveGenBasis
         ,mcenum_sep_basis    a_SolveSepBasis
         );
 
-    currency SolveTest             (currency a_CandidateValue);
+    double SolveTest             (double a_CandidateValue);
 
-    currency SolveGuarPremium      ();
+    double SolveGuarPremium      ();
 
     double GetPartMortQ            (int year) const;
 
@@ -236,13 +236,13 @@ class LMI_SO AccountValue final
     void TxTestGPT                  ();
     void TxPmt(); // Antediluvian.
     void TxAscertainDesiredPayment  ();
-    void TxLimitPayment             (currency a_maxpmt);
+    void TxLimitPayment             (double a_maxpmt);
     void TxRecognizePaymentFor7702A
         (currency a_pmt
         ,bool     a_this_payment_is_unnecessary
         );
     void TxAcceptPayment            (currency payment);
-    double GetPremLoad
+    currency GetPremLoad
         (currency a_pmt
         ,double   a_portion_exempt_from_premium_tax
         );
@@ -271,14 +271,14 @@ class LMI_SO AccountValue final
     // Reflects optional daily interest accounting.
     double ActualMonthlyRate    (double monthly_rate) const;
     currency InterestCredited
-        (double currency
-        ,double monthly_rate
+        (currency principal
+        ,double   monthly_rate
         ) const;
 
     bool   IsModalPmtDate          (mcenum_mode) const;
     bool   IsModalPmtDate          (); // Antediluvian.
     int    MonthsToNextModalPmtDate() const;
-    currency anticipated_deduction   (mcenum_anticipated_deduction);
+    currency anticipated_deduction (mcenum_anticipated_deduction);
 
     currency minimum_specified_amount(bool issuing_now, bool term_rider) const;
     void   ChangeSpecAmtBy         (currency delta);
@@ -422,8 +422,8 @@ class LMI_SO AccountValue final
 
     currency  NetMaxNecessaryPremium;
     currency  GrossMaxNecessaryPremium;
-    currency  NecessaryPremium;
-    currency  UnnecessaryPremium;
+    currency  NecessaryPremium   {currency(0.0)};
+    currency  UnnecessaryPremium {currency(0.0)};
 
     // 7702A CVAT deemed cash value.
     double  Dcv;
@@ -555,22 +555,29 @@ class LMI_SO AccountValue final
     currency  RequestedLoan;
     currency  RequestedWD;
 
-    currency  AdbCharge;
-    currency  SpouseRiderCharge;
-    currency  ChildRiderCharge;
-    currency  WpCharge;
-    currency  TermCharge;
+// These really ought to be rounded individually:
+//  currency  AdbCharge;
+//  currency  SpouseRiderCharge;
+//  currency  ChildRiderCharge;
+//  currency  WpCharge;
+//  currency  TermCharge;
+
+    double    AdbCharge;
+    double    SpouseRiderCharge;
+    double    ChildRiderCharge;
+    double    WpCharge;
+    double    TermCharge;
 
     currency  MlyDed;
     currency  mlydedtonextmodalpmtdate; // Antediluvian.
 
     currency  YearsTotalCoiCharge;
     currency  YearsTotalRiderCharges;
-    currency  YearsAVRelOnDeath;
-    currency  YearsLoanRepaidOnDeath;
-    currency  YearsGrossClaims;
-    currency  YearsDeathProceeds;
-    currency  YearsNetClaims;
+    double    YearsAVRelOnDeath;
+    double    YearsLoanRepaidOnDeath;
+    double    YearsGrossClaims;
+    double    YearsDeathProceeds;
+    double    YearsNetClaims;
     double    YearsTotalNetIntCredited;
     double    YearsTotalGrossIntCredited;
     double    YearsTotalLoanIntAccrued;
diff --git a/accountvalue.cpp b/accountvalue.cpp
index d4afefb..5ce5039 100644
--- a/accountvalue.cpp
+++ b/accountvalue.cpp
@@ -131,18 +131,18 @@ std::shared_ptr<Ledger const> 
AccountValue::ledger_from_av() const
 }
 
 //============================================================================
-double AccountValue::RunAV()
+currency AccountValue::RunAV()
 {
     InvariantValues().Init(this);
-    OverridingPmts = InvariantValues().EePmt;
+    OverridingPmts = currencyize(InvariantValues().EePmt);
     Solving = mce_solve_none != yare_input_.SolveType;
     return RunAllApplicableBases();
 }
 
 //============================================================================
-double AccountValue::RunOneBasis(mcenum_run_basis TheBasis)
+currency AccountValue::RunOneBasis(mcenum_run_basis TheBasis)
 {
-    double z;
+    currency z;
     if(Solving)
         {
         // IHS !! Isn't this unreachable?
@@ -164,7 +164,7 @@ double AccountValue::RunOneBasis(mcenum_run_basis TheBasis)
 //   if running all bases
 //     run all bases
 //
-double AccountValue::RunAllApplicableBases()
+currency AccountValue::RunAllApplicableBases()
 {
     // set pmts, specamt, surrchg
 
@@ -176,11 +176,11 @@ double AccountValue::RunAllApplicableBases()
         ,mce_sep_full
         );
 
-    double z = 0.0;
+    currency z(0.0);
     if(Solving)
         {
         z = Solve();
-        OverridingPmts = InvariantValues().EePmt;
+        OverridingPmts = currencyize(InvariantValues().EePmt);
         ledger_->SetOneLedgerVariant(run_basis, VariantValues());
         Solving = false;
         }
@@ -202,7 +202,7 @@ double AccountValue::RunAllApplicableBases()
 }
 
 //============================================================================
-double AccountValue::RunOneCell(mcenum_run_basis TheBasis)
+currency AccountValue::RunOneCell(mcenum_run_basis TheBasis)
 {
     if(Solving)
         {
@@ -323,8 +323,8 @@ void AccountValue::DoYear
 
     YearsCorridorFactor = BasicValues::GetCorridorFactor()[Year];
 
-    GrossPmts  .assign(12, 0.0);
-    NetPmts    .assign(12, 0.0);
+    GrossPmts  .assign(12, currency(0.0));
+    NetPmts    .assign(12, currency(0.0));
 
     // IHS !! Strategy here?
 
@@ -443,12 +443,12 @@ void AccountValue::PerformSpecAmtStrategy()
             break;
         case mce_sa_maximum:
             {
-            SA = GetModalMaxSpecAmt(InvariantValues().EeMode[0].value(), 
InvariantValues().EePmt[0]);
+            SA = GetModalMaxSpecAmt(InvariantValues().EeMode[0].value(), 
currency(InvariantValues().EePmt[0]));
             }
             break;
         case mce_sa_target:
             {
-            SA = GetModalTgtSpecAmt(InvariantValues().EeMode[0].value(), 
InvariantValues().EePmt[0]);
+            SA = GetModalTgtSpecAmt(InvariantValues().EeMode[0].value(), 
currency(InvariantValues().EePmt[0]));
             }
             break;
         case mce_sa_mep:
@@ -533,7 +533,7 @@ void AccountValue::TxOptionChange()
             {
             // Option 2: decrease spec amt by AV, but not below min spec amt.
             ActualSpecAmt -= AV;
-            ActualSpecAmt = std::max(ActualSpecAmt, MinSpecAmt);
+            ActualSpecAmt = std::max(ActualSpecAmt, currency(MinSpecAmt));
             // An alternative is to lapse the policy.
             }
             break;
@@ -576,7 +576,7 @@ void AccountValue::TxSpecAmtChange()
         }
 
     // Change specified amount.
-    ActualSpecAmt = std::max(MinSpecAmt, DeathBfts_->specamt()[Year]);
+    ActualSpecAmt = std::max(currency(MinSpecAmt), 
DeathBfts_->specamt()[Year]);
 
     // Carry the new spec amt forward into all future years.
     for(int j = Year; j < BasicValues::GetLength(); ++j)
@@ -587,7 +587,7 @@ void AccountValue::TxSpecAmtChange()
 
 //============================================================================
 // Set payment according to selected strategy, in each non-solve year.
-void AccountValue::PerformPmtStrategy(double* a_Pmt)
+void AccountValue::PerformPmtStrategy(currency* a_Pmt)
 {
     // Don't override premium during solve period.
     if
@@ -750,13 +750,13 @@ void AccountValue::TxSetDeathBft()
         case mce_option1:
             {
             // Option 1: specamt, or corridor times AV if greater.
-            deathbft = std::max(ActualSpecAmt, YearsCorridorFactor * AV);
+            deathbft = std::max<double>(ActualSpecAmt, YearsCorridorFactor * 
AV);
             }
             break;
         case mce_option2:
             // Option 2: specamt plus AV, or corridor times AV if greater.
             // Negative AV doesn't decrease death benefit.
-            deathbft = std::max
+            deathbft = std::max<double>
                 (ActualSpecAmt + std::max(0.0, AV)
                 ,YearsCorridorFactor * AV
                 );
@@ -802,7 +802,7 @@ void AccountValue::TxSetRiderDed()
     AdbCharge = 0.0;
     if(hasadb)
         {
-        AdbCharge = YearsAdbRate * std::min(500000.0, ActualSpecAmt);
+        AdbCharge = YearsAdbRate * std::min<double>(500000.0, ActualSpecAmt);
         }
 }
 
@@ -812,7 +812,7 @@ void AccountValue::TxDoMlyDed()
 {
     AVUnloaned -=             CoiCharge + AdbCharge + WpCharge;
     MlyDed = YearsMonthlyPolicyFee + CoiCharge + AdbCharge + WpCharge;
-    mlydedtonextmodalpmtdate = MlyDed * MonthsToNextModalPmtDate();
+    mlydedtonextmodalpmtdate = doubleize(MlyDed) * MonthsToNextModalPmtDate(); 
// bad solution
 }
 
 /// Credit interest on account value.
@@ -915,7 +915,7 @@ void AccountValue::TxTakeWD()
             //   lapse the policy?
 // IHS !!            ActualSpecAmt = std::min(ActualSpecAmt, deathbft - wd);
             ActualSpecAmt -= wd;
-            ActualSpecAmt = std::max(ActualSpecAmt, MinSpecAmt);
+            ActualSpecAmt = std::max(ActualSpecAmt, currency(MinSpecAmt));
             ActualSpecAmt = round_specamt()(ActualSpecAmt);
             // IHS !! If WD causes AV < min AV, do we:
             //   reduce the WD?
@@ -941,7 +941,7 @@ void AccountValue::TxTakeWD()
         }
 
     // Deduct withdrawal fee.
-    wd -= std::min(WDFee, wd * WDFeeRate);
+    wd -= std::min<double>(WDFee, wd * WDFeeRate);
     // IHS !! This treats input WD as gross; it probably should be net. But 
compare lmi.
 
     InvariantValues().NetWD[Year] = wd;
@@ -979,7 +979,7 @@ void AccountValue::TxTakeLoan()
     double IntAdj = std::pow((1.0 + YearsRegLnIntDueRate), 12 - Month);
     IntAdj = (IntAdj - 1.0) / IntAdj;
     MaxLoan *= 1.0 - IntAdj;
-    MaxLoan = std::max(0.0, MaxLoan);
+    MaxLoan = std::max<double>(0.0, MaxLoan);
     MaxLoan = round_loan()(MaxLoan);
 
     // IHS !! Preferred loan calculations would go here: implemented in lmi.
@@ -1054,11 +1054,11 @@ double AccountValue::GetCurtateNetCoiChargeInforce() 
const
     {return 0.0;}
 double AccountValue::GetProjectedCoiChargeInforce() const
     {return 0.0;}
-double AccountValue::GetSepAcctAssetsInforce() const
-    {return 0.0;}
-double AccountValue::IncrementBOM(int, int, double)
-    {return 0.0;}
-void   AccountValue::IncrementEOM(int, int, double, double)
+currency AccountValue::GetSepAcctAssetsInforce() const
+    {return currency(0.0);}
+currency AccountValue::IncrementBOM(int, int, double)
+    {return currency(0.0);}
+void   AccountValue::IncrementEOM(int, int, currency, currency)
     {return;}
 void   AccountValue::IncrementEOY(int)
     {return;}
diff --git a/basic_values.hpp b/basic_values.hpp
index 0048256..521591d 100644
--- a/basic_values.hpp
+++ b/basic_values.hpp
@@ -304,13 +304,15 @@ class LMI_SO BasicValues
     mcenum_defn_life_ins         DefnLifeIns_;
     mcenum_defn_material_change  DefnMaterialChange_;
     mcenum_dbopt_7702            Effective7702DboRop;
-    currency                     MaxNAAR;
+    // Things like this are notionally currency, but they're cached
+    // values from a database of double-only values.
+    double /*currency*/          MaxNAAR;
     int                          EndtAge;
-    currency                     MinSpecAmt; // Antediluvian.
-    currency                     MinIssSpecAmt;
-    currency                     MinIssBaseSpecAmt;
-    currency                     MinRenlSpecAmt;
-    currency                     MinRenlBaseSpecAmt;
+    double /*currency*/          MinSpecAmt; // Antediluvian.
+    double /*currency*/          MinIssSpecAmt;
+    double /*currency*/          MinIssBaseSpecAmt;
+    double /*currency*/          MinRenlSpecAmt;
+    double /*currency*/          MinRenlBaseSpecAmt;
     bool                         NoLapseDboLvlOnly;
     bool                         NoLapseUnratedOnly;
     bool                         OptChgCanIncrSA;
@@ -325,13 +327,13 @@ class LMI_SO BasicValues
     int                          TermForcedConvDur;
     bool                         TermIsDbFor7702;
     bool                         TermIsDbFor7702A;
-    currency                     ExpPerKLimit;
+    double /*currency*/          ExpPerKLimit;
     oenum_modal_prem_type        MinPremType;
     oenum_modal_prem_type        TgtPremType;
     bool                         TgtPremFixedAtIssue;
-    currency                     TgtPremMonthlyPolFee;
-    currency                     CurrCoiTable0Limit;
-    currency                     CurrCoiTable1Limit;
+    double /*currency*/          TgtPremMonthlyPolFee;
+    double /*currency*/          CurrCoiTable0Limit;
+    double /*currency*/          CurrCoiTable1Limit;
     e_actuarial_table_method     CoiInforceReentry;
     mcenum_anticipated_deduction MaxWDDed_;
     double                       MaxWdGenAcctValMult;
@@ -349,11 +351,11 @@ class LMI_SO BasicValues
     bool                         SurrChgOnDecr;
     std::vector<double>          FreeWDProportion;
 
-    currency                AdbLimit;
-    currency                WpLimit;
-    currency                SpecAmtLoadLimit;
-    currency                MinWD;
-    currency                WDFee;
+    double /*currency*/     AdbLimit;
+    double /*currency*/     WpLimit;
+    double /*currency*/     SpecAmtLoadLimit;
+    double /*currency*/     MinWD;
+    double /*currency*/     WDFee;
     double                  WDFeeRate;
 
     bool                    AllowChangeToDBO2;
@@ -383,11 +385,11 @@ class LMI_SO BasicValues
     BasicValues& operator=(BasicValues const&) = delete;
 
     double mly_ded_discount_factor(int year, mcenum_mode mode) const;
-    std::pair<currency,currency> approx_mly_ded
+    std::pair<double,double> approx_mly_ded
         (int      year
         ,currency specamt
         ) const;
-    std::pair<currency,currency> approx_mly_ded_ex
+    std::pair<double,double> approx_mly_ded_ex
         (int      year
         ,currency specamt
         ,currency termamt
diff --git a/basicvalues.cpp b/basicvalues.cpp
index d93a0e0..4cb3cc7 100644
--- a/basicvalues.cpp
+++ b/basicvalues.cpp
@@ -136,20 +136,20 @@ double BasicValues::InvestmentManagementFee() const
 
 //============================================================================
 // IHS !! Simply calls the target-premium routine for now--see lmi.
-double BasicValues::GetModalMinPrem
+currency BasicValues::GetModalMinPrem
     (int         a_year
     ,mcenum_mode a_mode
-    ,double      a_specamt
+    ,currency    a_specamt
     ) const
 {
     return GetModalTgtPrem(a_year, a_mode, a_specamt);
 }
 
 //============================================================================
-double BasicValues::GetModalTgtPrem
+currency BasicValues::GetModalTgtPrem
     (int         a_year
     ,mcenum_mode a_mode
-    ,double      a_specamt
+    ,currency    a_specamt
     ) const
 {
     // IHS !! Simplistic. Ignores table ratings, flat extras, and
@@ -211,23 +211,23 @@ double BasicValues::GetModalTgtPrem
 
     // IHS !! Parameterized in lmi.
     static round_to<double> const round_it(2, r_upward);
-    return round_it(z);
+    return currency(round_it(z));
 }
 
 //============================================================================
 // Simply calls the target-specamt routine for now.
-double BasicValues::GetModalMaxSpecAmt
+currency BasicValues::GetModalMaxSpecAmt
     (mcenum_mode a_mode
-    ,double      a_pmt
+    ,currency    a_pmt
     ) const
 {
     return GetModalTgtSpecAmt(a_mode, a_pmt);
 }
 
 //============================================================================
-double BasicValues::GetModalTgtSpecAmt
+currency BasicValues::GetModalTgtSpecAmt
     (mcenum_mode a_mode
-    ,double      a_pmt
+    ,currency    a_pmt
     ) const
 {
     // IHS !! Factor out the (defectively simplistic) code this
@@ -285,7 +285,7 @@ double BasicValues::GetModalTgtSpecAmt
 
     // IHS !! Parameterized in lmi.
     static round_to<double> const round_it(0, r_downward);
-    return round_it(z);
+    return currency(round_it(z));
 }
 
 //============================================================================
diff --git a/currency.hpp b/currency.hpp
index 9dea3e8..ff738b0 100644
--- a/currency.hpp
+++ b/currency.hpp
@@ -29,10 +29,28 @@
 
 #include <cstdint>                      // int64_t
 #include <iostream>                     // ostream
+#include <vector>
 
+#define USE_CURRENCY_CLASS
+
+#if !defined USE_CURRENCY_CLASS
+using currency = double;
+
+#if defined __GNUC__
+#   pragma GCC diagnostic ignored "-Wuseless-cast"
+#endif // defined __GNUC__
+#endif // !defined USE_CURRENCY_CLASS
+
+#if defined USE_CURRENCY_CLASS
 class currency
 {
-    using data_type = std::int64_t;
+#if defined __GNUC__
+#   pragma GCC diagnostic ignored "-Wuseless-cast"
+#endif // defined __GNUC__
+    using data_type = double;
+//  using data_type = long double;
+//  using data_type = std::int64_t;
+
     friend std::ostream& operator<<(std::ostream&, currency const&);
     friend class currency_test;
 
@@ -68,6 +86,8 @@ class currency
     // Is this the ideal signature for this operator?
 //  currency operator-() const {return currency(-m_);}
 //  currency& operator-() {m_ = -m_; return *this;}
+// Dangerous--demonstrably makes calculations wrong, but hard to see why
+//  currency operator-() const {return currency(bourn_cast<double>(-m_));}
 
     currency& operator+=(currency z) {m_ += z.m_; return *this;}
     currency& operator-=(currency z) {m_ -= z.m_; return *this;}
@@ -83,6 +103,8 @@ class currency
     // Far too permissive:
 //  currency& operator*=(double z) {m_ = static_cast<data_type>(100.0 * 
to_double() * z); return *this;}
     double operator*=(double z) {return to_double() * z;}
+// Dangerous--can somehow take the place of operator*(double)
+//  currency const& operator*=(int z) {m_ *= z; return *this;}
 
   private:
     // Want something just slightly more permissive:
@@ -107,7 +129,10 @@ class currency
 inline currency operator+(currency lhs, double rhs) {return lhs += 
currency(rhs);}
 inline currency operator-(currency lhs, double rhs) {return lhs -= 
currency(rhs);}
 //inline currency operator*(currency lhs, double rhs) {return lhs *= 
currency(rhs);}
-inline double operator*(currency lhs, double rhs) {return lhs *= 
currency(rhs);}
+////inline double operator*(currency lhs, double rhs) {return lhs *= 
currency(rhs);}
+inline double operator*(currency lhs, double rhs) {return lhs.operator 
double() * rhs;}
+//inline currency operator*(currency lhs, int rhs) {return lhs *= rhs;}
+//inline currency operator*(int lhs, currency rhs) {return rhs *= lhs;}
 
 inline std::ostream& operator<<(std::ostream& os, currency const& c)
 {
@@ -115,4 +140,33 @@ inline std::ostream& operator<<(std::ostream& os, currency 
const& c)
     return os << c.to_double();
 }
 
+#endif // defined USE_CURRENCY_CLASS
+
+// Sloppy.
+inline currency requantize(double z) {return currency(z);}
+
+inline std::vector<currency> currencyize(std::vector<double> const& z)
+{
+    std::vector<currency> r;
+    r.reserve(z.size());
+    for(auto const& i : z)
+        r.push_back(currency(i));
+    return r;
+}
+
+inline double doubleize(currency const& z)
+{
+    return currency(z);
+}
+
+inline std::vector<double> doubleize(std::vector<currency> const& z)
+{
+    std::vector<double> r;
+    r.reserve(z.size());
+    for(auto const& i : z)
+//      r.push_back(i.operator double()); // no need to convert explicitly
+        r.push_back(i);
+    return r;
+}
+
 #endif // currency_hpp
diff --git a/currency_test.cpp b/currency_test.cpp
index e7af97f..06cad93 100644
--- a/currency_test.cpp
+++ b/currency_test.cpp
@@ -59,6 +59,12 @@ void currency_test::test_something()
     a1 *= a0;
     BOOST_TEST(0.00 == a1.operator double());
     BOOST_TEST(   0 == a1.m_);
+// Not sure what this should do, if anything, but it prints "6.5".
+std::cout << a1 << std::endl;
+
+    currency a2 = currency(0.0) - a1;
+    BOOST_TEST(-6.50 == a2.operator double());
+    BOOST_TEST( -650 == a2.m_);
 }
 
 int test_main(int, char*[])
diff --git a/death_benefits.cpp b/death_benefits.cpp
index 55d9bdb..ac1b01b 100644
--- a/death_benefits.cpp
+++ b/death_benefits.cpp
@@ -51,7 +51,7 @@ death_benefits::death_benefits(int length, yare_input const& 
yi)
 }
 
 //============================================================================
-void death_benefits::set_specamt(double z, int from_year, int to_year)
+void death_benefits::set_specamt(currency z, int from_year, int to_year)
 {
 #if 0
     // Something like this would seem preferable, but it gives
@@ -69,7 +69,7 @@ void death_benefits::set_specamt(double z, int from_year, int 
to_year)
 }
 
 //============================================================================
-void death_benefits::set_supplamt(double z, int from_year, int to_year)
+void death_benefits::set_supplamt(currency z, int from_year, int to_year)
 {
 #if 0
     // Something like this would seem preferable, but it gives
diff --git a/gpt_specamt.cpp b/gpt_specamt.cpp
index 8e803a3..cca9384 100644
--- a/gpt_specamt.cpp
+++ b/gpt_specamt.cpp
@@ -107,7 +107,7 @@ class FindSpecAmt
                     ,a_Trial
                     ,NetPmtFactorTgt
                     ,NetPmtFactorExc
-                    ,Values_.GetAnnualTgtPrem(Duration, SpecAmt)
+                    ,Values_.GetAnnualTgtPrem(Duration, currency(SpecAmt))
                     )
             -   Premium
             ;
diff --git a/group_values.cpp b/group_values.cpp
index dbc55d6..81987cb 100644
--- a/group_values.cpp
+++ b/group_values.cpp
@@ -28,6 +28,7 @@
 #include "assert_lmi.hpp"
 #include "configurable_settings.hpp"
 #include "contains.hpp"
+#include "currency.hpp"
 #include "emit_ledger.hpp"
 #include "fenv_guard.hpp"
 #include "input.hpp"
@@ -418,7 +419,7 @@ census_run_result run_census_in_parallel::operator()
                     ;
             for(int month = inforce_month; month < 12; ++month)
                 {
-                double assets = 0.0;
+                currency assets = currency(0.0);
 
                 // Get total case assets prior to interest crediting because
                 // those assets may determine the M&E charge.
diff --git a/ihs_acctval.cpp b/ihs_acctval.cpp
index 35bb946..fb499a5 100644
--- a/ihs_acctval.cpp
+++ b/ihs_acctval.cpp
@@ -187,7 +187,7 @@ std::shared_ptr<Ledger const> 
AccountValue::ledger_from_av() const
 }
 
 //============================================================================
-double AccountValue::RunAV()
+currency AccountValue::RunAV()
 {
 /*
 First run current, for solves and strategies. This determines
@@ -212,7 +212,7 @@ Then run other bases.
         DebugPrintInit();
         }
 
-    double z = RunAllApplicableBases();
+    currency z = RunAllApplicableBases();
 
     FinalizeLifeAllBases();
 
@@ -234,7 +234,7 @@ void AccountValue::SetGuarPrem()
 }
 
 //============================================================================
-double AccountValue::RunOneBasis(mcenum_run_basis a_Basis)
+currency AccountValue::RunOneBasis(mcenum_run_basis a_Basis)
 {
     if
         (  !BasicValues::IsSubjectToIllustrationReg()
@@ -247,7 +247,7 @@ double AccountValue::RunOneBasis(mcenum_run_basis a_Basis)
             ;
         }
 
-    double z = 0.0;
+    currency z(0.0);
     if(Solving)
         {
 // Apparently this should never be done because Solve() is called in
@@ -270,7 +270,7 @@ double AccountValue::RunOneBasis(mcenum_run_basis a_Basis)
 //   if running all bases
 //     run all bases
 //
-double AccountValue::RunAllApplicableBases()
+currency AccountValue::RunAllApplicableBases()
 {
     double z = 0.0;
 
@@ -317,7 +317,7 @@ double AccountValue::RunAllApplicableBases()
         {
         RunOneBasis(b);
         }
-    return z;
+    return currency(z); // dubious
 }
 
 //============================================================================
@@ -329,7 +329,7 @@ double AccountValue::RunAllApplicableBases()
 /// which isn't necessary anyway because all the functions it calls
 /// contain such a condition.
 
-double AccountValue::RunOneCell(mcenum_run_basis a_Basis)
+currency AccountValue::RunOneCell(mcenum_run_basis a_Basis)
 {
     InitializeLife(a_Basis);
 
@@ -355,7 +355,7 @@ double AccountValue::RunOneCell(mcenum_run_basis a_Basis)
             IncrementEOM
                 (year
                 ,month
-                ,SepAcctValueAfterDeduction * InforceLivesBoy()
+                ,currency(SepAcctValueAfterDeduction * InforceLivesBoy()) // 
conversion is dubious
                 ,CumPmts
                 );
             }
@@ -431,13 +431,13 @@ void AccountValue::InitializeLife(mcenum_run_basis 
a_Basis)
     // TODO ?? TAXATION !! Shouldn't we increase initial SA if contract in 
corridor at issue?
     OldDB = OldSA;
 
-    SurrChg_.assign(BasicValues::GetLength(), 0.0);
+    SurrChg_.assign(BasicValues::GetLength(), currency(0.0));
 
     // TAXATION !! Input::InforceAnnualTargetPremium should be used here.
     double annual_target_premium = GetModalTgtPrem
         (0
         ,mce_annual
-        ,InvariantValues().SpecAmt[0]
+        ,requantize(InvariantValues().SpecAmt[0])
         );
     double sa =
                              InvariantValues().SpecAmt    [0]
@@ -620,15 +620,15 @@ void AccountValue::SetInitialValues()
 
     CumPmts                     = InforceCumPmts;
     TaxBasis                    = InforceTaxBasis;
-    YearlyTaxBasis.assign(BasicValues::GetLength(), 0.0);
+    YearlyTaxBasis.assign(BasicValues::GetLength(), currency(0.0));
     MlyNoLapsePrem              = 0.0;
     CumNoLapsePrem              = InforceCumNoLapsePrem;
 
     // Initialize all elements of this vector to 'false'. Then, when
     // the no-lapse criteria fail to be met, future values are right.
     YearlyNoLapseActive.assign(BasicValues::GetLength(), false);
-    loan_ullage_       .assign(BasicValues::GetLength(), 0.0);
-    withdrawal_ullage_ .assign(BasicValues::GetLength(), 0.0);
+    loan_ullage_       .assign(BasicValues::GetLength(), currency(0.0));
+    withdrawal_ullage_ .assign(BasicValues::GetLength(), currency(0.0));
     NoLapseActive               = true;
     if(NoLapseDboLvlOnly && mce_option1 != DeathBfts_->dbopt()[0])
         {
@@ -731,7 +731,7 @@ void AccountValue::SetInitialValues()
 
 //============================================================================
 // Process monthly transactions up to but excluding interest credit
-double AccountValue::IncrementBOM
+currency AccountValue::IncrementBOM
     (int    year
     ,int    month
     ,double a_case_k_factor
@@ -741,7 +741,7 @@ double AccountValue::IncrementBOM
         {
         // Return value is total assets. After the policy has lapsed or
         // matured, there are no assets.
-        return 0.0;
+        return currency(0.0);
         }
 
     // Paranoid check.
@@ -790,10 +790,10 @@ double AccountValue::IncrementBOM
 //============================================================================
 // Credit interest and process all subsequent monthly transactions
 void AccountValue::IncrementEOM
-    (int year
-    ,int month
-    ,double assets_post_bom
-    ,double cum_pmts_post_bom
+    (int      year
+    ,int      month
+    ,currency assets_post_bom
+    ,currency cum_pmts_post_bom
     )
 {
     if(ItLapsed || BasicValues::GetLength() <= Year)
@@ -813,8 +813,8 @@ void AccountValue::IncrementEOM
 
     // Save arguments, constraining their values to be nonnegative,
     // for calculating banded and tiered quantities.
-    AssetsPostBom  = std::max(0.0, assets_post_bom  );
-    CumPmtsPostBom = std::max(0.0, cum_pmts_post_bom);
+    AssetsPostBom  = std::max<double>(0.0, assets_post_bom  );
+    CumPmtsPostBom = std::max<double>(0.0, cum_pmts_post_bom);
 
     DoMonthCR();
 }
@@ -896,10 +896,10 @@ void AccountValue::InitializeYear()
     // value depends on the maximum loan, so it cannot be known here.
     ActualLoan                  = 0.0;
 
-    GrossPmts   .assign(12, 0.0);
-    EeGrossPmts .assign(12, 0.0);
-    ErGrossPmts .assign(12, 0.0);
-    NetPmts     .assign(12, 0.0);
+    GrossPmts   .assign(12, currency(0.0));
+    EeGrossPmts .assign(12, currency(0.0));
+    ErGrossPmts .assign(12, currency(0.0));
+    NetPmts     .assign(12, currency(0.0));
 
     InitializeSpecAmt();
 }
@@ -929,10 +929,10 @@ void AccountValue::InitializeSpecAmt()
         InvariantValues().ModalMinimumPremium[Year] = GetModalMinPrem
             (Year
             ,InvariantValues().ErMode[Year].value()
-            ,InvariantValues().SpecAmt[Year]
+            ,requantize(InvariantValues().SpecAmt[Year])
             );
         InvariantValues().ErModalMinimumPremium[Year] =
-            InvariantValues().ModalMinimumPremium[Year]
+            requantize(InvariantValues().ModalMinimumPremium[Year])
             ;
         }
     else
@@ -940,8 +940,8 @@ void AccountValue::InitializeSpecAmt()
         auto const z = GetModalPremMlyDedEx
             (Year
             ,InvariantValues().ErMode[Year].value()
-            ,InvariantValues().SpecAmt[Year]
-            ,InvariantValues().TermSpecAmt[Year]
+            ,requantize(InvariantValues().SpecAmt[Year])
+            ,requantize(InvariantValues().TermSpecAmt[Year])
             );
         InvariantValues().EeModalMinimumPremium[Year] = z.first;
         InvariantValues().ErModalMinimumPremium[Year] = z.second;
@@ -970,12 +970,12 @@ void AccountValue::InitializeSpecAmt()
     MlyNoLapsePrem = GetModalMinPrem
         (target_year
         ,mce_monthly
-        ,InvariantValues().SpecAmt[target_year]
+        ,requantize(InvariantValues().SpecAmt[target_year])
         );
     UnusedTargetPrem = GetModalTgtPrem
         (target_year
         ,mce_annual
-        ,InvariantValues().SpecAmt[target_year]
+        ,requantize(InvariantValues().SpecAmt[target_year])
         );
     AnnualTargetPrem = UnusedTargetPrem;
 
@@ -1018,7 +1018,7 @@ void AccountValue::set_list_bill_premium()
         InvariantValues().ListBillPremium = GetListBillPremMlyDed
             (Year
             ,InvariantValues().ErMode[Year].value()
-            ,InvariantValues().SpecAmt[Year]
+            ,requantize(InvariantValues().SpecAmt[Year])
             );
         InvariantValues().ErListBillPremium =
             InvariantValues().ListBillPremium
@@ -1029,8 +1029,8 @@ void AccountValue::set_list_bill_premium()
         auto const z = GetListBillPremMlyDedEx
             (Year
             ,InvariantValues().ErMode[Year].value()
-            ,InvariantValues().SpecAmt[Year]
-            ,InvariantValues().TermSpecAmt[Year]
+            ,requantize(InvariantValues().SpecAmt[Year])
+            ,requantize(InvariantValues().TermSpecAmt[Year])
             );
         InvariantValues().EeListBillPremium = z.first;
         InvariantValues().ErListBillPremium = z.second;
@@ -1052,7 +1052,7 @@ void AccountValue::set_list_bill_premium()
 ///
 /// SOMEDAY !! Table support and UL model reg formulas should be added.
 
-double AccountValue::SurrChg() const
+currency AccountValue::SurrChg() const
 {
     LMI_ASSERT(0.0 <= SurrChg_[Year]);
     // For the nonce, CSVBoost() is netted against surrender charge.
@@ -1065,14 +1065,14 @@ double AccountValue::SurrChg() const
 ///
 /// Probably the input field should be expunged.
 
-double AccountValue::CSVBoost() const
+currency AccountValue::CSVBoost() const
 {
     double const z =
           CashValueEnhMult[Year]
         + yare_input_.CashValueEnhancementRate[Year]
         ;
     LMI_ASSERT(0.0 <= z);
-    return z * std::max(0.0, TotalAccountValue());
+    return currency(z * std::max<double>(0.0, TotalAccountValue())); // 
round...or expunge
 }
 
 //============================================================================
@@ -1149,10 +1149,16 @@ void AccountValue::SetProjectedCoiCharge()
 
     TxSetDeathBft();
     TxSetTermAmt();
+#if 0 // no need for material_difference()
     double this_years_terminal_naar = material_difference
         (DBReflectingCorr + TermDB
         ,TotalAccountValue()
         );
+#endif //
+    double this_years_terminal_naar =
+          DBReflectingCorr + TermDB
+        - TotalAccountValue()
+        ;
     this_years_terminal_naar = std::max(0.0, this_years_terminal_naar);
     double next_years_coi_rate = GetBandedCoiRates(GenBasis_, ActualSpecAmt)[1 
+ Year];
 
@@ -1213,7 +1219,7 @@ void AccountValue::FinalizeYear()
         csv_net -= surr_chg;
         }
 
-    csv_net = std::max(csv_net, HoneymoonValue);
+    csv_net = std::max<double>(csv_net, HoneymoonValue);
 
     if(!Solving)
         {
@@ -1239,7 +1245,7 @@ void AccountValue::FinalizeYear()
         {
         cv_7702 -= surr_chg;
         }
-    cv_7702 = std::max(cv_7702, HoneymoonValue);
+    cv_7702 = std::max<double>(cv_7702, HoneymoonValue);
 
     VariantValues().AcctVal     [Year] = total_av;
     VariantValues().AVGenAcct   [Year] = AVGenAcct + AVRegLn + AVPrfLn;
@@ -1504,14 +1510,14 @@ double AccountValue::GetPartMortQ(int a_year) const
 }
 
 //============================================================================
-double AccountValue::GetSepAcctAssetsInforce() const
+currency AccountValue::GetSepAcctAssetsInforce() const
 {
     if(ItLapsed || BasicValues::GetLength() <= Year)
         {
-        return 0.0;
+        return currency(0.0);
         }
 
-    return SepAcctValueAfterDeduction * InvariantValues().InforceLives[Year];
+    return currency(SepAcctValueAfterDeduction * 
InvariantValues().InforceLives[Year]); // round this
 }
 
 //============================================================================
diff --git a/ihs_avdebug.cpp b/ihs_avdebug.cpp
index 425d8d0..bf6490a 100644
--- a/ihs_avdebug.cpp
+++ b/ihs_avdebug.cpp
@@ -413,7 +413,7 @@ void AccountValue::DebugPrint()
     SetMonthlyDetail(eCumNoLapsePrem     ,CumNoLapsePrem                   );
     SetMonthlyDetail(eNoLapseActive      ,NoLapseActive                    );
     SetMonthlyDetail(eEOMAV              ,TotalAccountValue()              );
-    SetMonthlyDetail(eHMValue            ,std::max(HoneymoonValue, 0.0)    );
+    SetMonthlyDetail(eHMValue            ,std::max<double>(HoneymoonValue, 
0.0)    );
     SetMonthlyDetail(eSurrChg            ,SurrChg()                        );
 
 // TODO ?? Unfortunately duplicated from AccountValue::FinalizeYear().
@@ -425,7 +425,7 @@ void AccountValue::DebugPrint()
         +   GetRefundableSalesLoad()
 //        +   std::max(0.0, ExpRatReserve) // This would be added if it 
existed.
         ;
-    csv_net = std::max(HoneymoonValue, csv_net);
+    csv_net = std::max<double>(HoneymoonValue, csv_net);
 
     SetMonthlyDetail(eEOMCSVNet          ,csv_net                          );
     SetMonthlyDetail(eEOMCV7702          ,CashValueFor7702()               );
diff --git a/ihs_avmly.cpp b/ihs_avmly.cpp
index 049d515..4827009 100644
--- a/ihs_avmly.cpp
+++ b/ihs_avmly.cpp
@@ -144,21 +144,24 @@ void AccountValue::DoMonthDR()
         //   LMI_ASSERT(kludge_account_value == Dcv);
         kludge_account_value = Dcv;
         }
-    kludge_account_value = std::max
+    kludge_account_value = std::max<double>
         (HoneymoonValue
         ,   kludge_account_value
           + GetRefundableSalesLoad()
 //          + std::max(0.0, ExpRatReserve) // This would be added if it 
existed.
         );
 // TODO ?? TAXATION !! Use CashValueFor7702() instead?
-    double max_necessary_premium = Irc7702A_->MaxNecessaryPremium
+    // These two functions return DBL_MAX in some circumstances;
+    // that gets translated to a negative currency amount, which
+    // is most infelicitous.
+    double max_necessary_premium = Irc7702A_->MaxNecessaryPremium // round
         (Dcv
         ,AnnualTargetPrem
         ,YearsTotLoadTgtLowestPremtax
         ,YearsTotLoadExcLowestPremtax
         ,kludge_account_value
         );
-    double max_non_mec_premium = Irc7702A_->MaxNonMecPremium
+    double max_non_mec_premium = Irc7702A_->MaxNonMecPremium // round
         (Dcv
         ,AnnualTargetPrem
         ,YearsTotLoadTgtLowestPremtax
@@ -201,19 +204,42 @@ void AccountValue::DoMonthDR()
         {
         gross_1035 = External1035Amount + Internal1035Amount;
         }
-    double necessary_premium = std::min
+    double necessary_premium = std::min<double> // round
         (GrossPmts[Month] - gross_1035
         ,max_necessary_premium
         );
-    double unnecessary_premium = material_difference
-        (GrossPmts[Month]
+    double unnecessary_premium = material_difference // round
+        (doubleize(GrossPmts[Month])
         ,gross_1035 + necessary_premium
         );
+    NecessaryPremium = necessary_premium; // or round here?
+    UnnecessaryPremium = unnecessary_premium; // or round here?
+
+    if(NecessaryPremium < 0.0 || UnnecessaryPremium < 0.0 || necessary_premium 
< 0.0 || unnecessary_premium < 0.0)
+      warning()
+        << NecessaryPremium << " NecessaryPremium\n"
+        << UnnecessaryPremium << " UnnecessaryPremium\n"
+        << necessary_premium << " necessary_premium\n"
+        << unnecessary_premium << " unnecessary_premium\n"
+        << gross_1035 << " gross_1035\n"
+        << GrossPmts[Month] - gross_1035 << " GrossPmts[Month] - gross_1035\n"
+        << GrossPmts[Month] << " GrossPmts[Month]\n"
+        << doubleize(GrossPmts[Month]) << " doubleize(GrossPmts[Month])\n"
+        << kludge_account_value << " kludge_account_value\n"
+        << max_necessary_premium << " max_necessary_premium\n"
+        << max_non_mec_premium << " max_non_mec_premium\n"
+        << LMI_FLUSH
+        ;
 
     // It is crucial to accept necessary premium before processing a
     // material change, so that the correct DCV is used.
-    TxRecognizePaymentFor7702A(necessary_premium, false);
-    TxAcceptPayment(necessary_premium);
+    TxRecognizePaymentFor7702A(NecessaryPremium, false);
+    if(NecessaryPremium < 0.0)
+      warning()
+        << NecessaryPremium << " NecessaryPremium\n"
+        << LMI_FLUSH
+        ;
+    TxAcceptPayment(NecessaryPremium);
     if(0.0 < unnecessary_premium)
         {
         Irc7702A_->InduceMaterialChange();
@@ -232,9 +258,15 @@ void AccountValue::DoMonthDR()
         );
     LMI_ASSERT(0.0 <= Dcv);
 
-    UnnecessaryPremium = unnecessary_premium;
-    TxRecognizePaymentFor7702A(unnecessary_premium, true);
-    TxAcceptPayment(unnecessary_premium);
+// moved up
+//  UnnecessaryPremium = unnecessary_premium; // or round here?
+    TxRecognizePaymentFor7702A(UnnecessaryPremium, true);
+    if(UnnecessaryPremium < 0.0)
+      warning()
+        << UnnecessaryPremium << " UnnecessaryPremium\n"
+        << LMI_FLUSH
+        ;
+    TxAcceptPayment(UnnecessaryPremium);
 
     TxTakeLoan();
     TxLoanRepay();
@@ -261,7 +293,7 @@ void AccountValue::DoMonthCR()
 
 //============================================================================
 // Apportion all payments among accounts.
-void AccountValue::process_payment(double payment)
+void AccountValue::process_payment(currency payment)
 {
     // Apply ee and er net payments according to database rules.
     // Net payments were already aggregated, then split between
@@ -273,21 +305,21 @@ void AccountValue::process_payment(double payment)
     LMI_ASSERT(0.0 <= EeGrossPmts[Month]);
     LMI_ASSERT(0.0 <= ErGrossPmts[Month]);
 
-    double net_pmt = payment;
+    currency net_pmt = payment;
 
-    double gross_1035 = 0.0;
+    currency gross_1035(0.0);
     if(0 == Year && 0 == Month)
         {
         gross_1035 = External1035Amount + Internal1035Amount;
         }
-    double gross_non_1035_pmts = GrossPmts[Month] - gross_1035;
+    currency gross_non_1035_pmts = GrossPmts[Month] - gross_1035;
     double er_ratio = 0.0;
     if(0.0 != gross_non_1035_pmts)
         {
         er_ratio = ErGrossPmts[Month] / gross_non_1035_pmts;
         }
-    double er_net_pmt = er_ratio * net_pmt;
-    double ee_net_pmt = net_pmt - er_net_pmt;
+    currency er_net_pmt = currency(round_gross_premium()(er_ratio * net_pmt));
+    currency ee_net_pmt = net_pmt - er_net_pmt;
 
     switch(ee_premium_allocation_method)
         {
@@ -323,7 +355,7 @@ void AccountValue::process_payment(double payment)
 //============================================================================
 // Prorate increments to account value between separate- and general-account
 // portions of unloaned account value according to input allocations.
-void AccountValue::IncrementAVProportionally(double increment)
+void AccountValue::IncrementAVProportionally(currency increment)
 {
     AVGenAcct += increment * GenAcctPaymentAllocation;
     AVSepAcct += increment * SepAcctPaymentAllocation;
@@ -332,7 +364,7 @@ void AccountValue::IncrementAVProportionally(double 
increment)
 //============================================================================
 // Apply increments to account value to the preferred account.
 void AccountValue::IncrementAVPreferentially
-    (double                             increment
+    (currency                           increment
     ,oenum_increment_account_preference preferred_account
     )
 {
@@ -354,7 +386,7 @@ void AccountValue::IncrementAVPreferentially
 /// Apportion all charges to be deducted from account value among
 /// accounts.
 
-void AccountValue::process_deduction(double decrement)
+void AccountValue::process_deduction(currency decrement)
 {
     switch(deduction_method)
         {
@@ -373,7 +405,7 @@ void AccountValue::process_deduction(double decrement)
 
 /// Apportion all distributions from account value among accounts.
 
-void AccountValue::process_distribution(double decrement)
+void AccountValue::process_distribution(currency decrement)
 {
     switch(distribution_method)
         {
@@ -402,7 +434,7 @@ void AccountValue::process_distribution(double decrement)
 /// Otherwise, unloaned account value might have a minuscule negative
 /// value due to catastrophic cancellation, improperly causing lapse.
 
-void AccountValue::DecrementAVProportionally(double decrement)
+void AccountValue::DecrementAVProportionally(currency decrement)
 {
     if(materially_equal(decrement, AVGenAcct + AVSepAcct))
         {
@@ -413,8 +445,8 @@ void AccountValue::DecrementAVProportionally(double 
decrement)
 
     double general_account_proportion  = 0.0;
     double separate_account_proportion = 0.0;
-    double general_account_nonnegative_assets  = std::max(0.0, AVGenAcct);
-    double separate_account_nonnegative_assets = std::max(0.0, AVSepAcct);
+    double general_account_nonnegative_assets  = std::max<double>(0.0, 
AVGenAcct);
+    double separate_account_nonnegative_assets = std::max<double>(0.0, 
AVSepAcct);
     if
         (  0.0 == general_account_nonnegative_assets
         && 0.0 == separate_account_nonnegative_assets
@@ -458,7 +490,7 @@ void AccountValue::DecrementAVProportionally(double 
decrement)
 /// value due to catastrophic cancellation, improperly causing lapse.
 
 void AccountValue::DecrementAVProgressively
-    (double                             decrement
+    (currency                           decrement
     ,oenum_increment_account_preference preferred_account
     )
 {
@@ -517,7 +549,9 @@ void AccountValue::TxExch1035()
         // Illustration-reg guaranteed premium ignores GPT limit.
         if(!SolvingForGuarPremium)
             {
-            Irc7702_->ProcessGptPmt(Year, GrossPmts[Month]);
+            double z = doubleize(GrossPmts[Month]); // yicky workaround
+            Irc7702_->ProcessGptPmt(Year, z);
+            GrossPmts[Month] = z;
             }
         // Limit external 1035 first, then internal, as necessary to avoid
         // exceeding the guideline limit. This is what the customer would
@@ -569,7 +603,7 @@ void AccountValue::TxExch1035()
 
     if(HoneymoonActive)
         {
-        HoneymoonValue += std::max(0.0, GrossPmts[Month]);
+        HoneymoonValue += std::max<double>(0.0, GrossPmts[Month]);
         }
 
     CumPmts += GrossPmts[Month];
@@ -583,7 +617,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, NetPmts[Month]));
+        LMI_ASSERT(materially_equal(Dcv, doubleize(NetPmts[Month])));
 
         // The initial seven-pay premium shown on the illustration
         // must be its value immediately after any 1035 exchange,
@@ -603,7 +637,7 @@ void AccountValue::TxExch1035()
 }
 
 //============================================================================
-double AccountValue::CashValueFor7702() const
+currency AccountValue::CashValueFor7702() const
 {
     return std::max
         (HoneymoonValue
@@ -634,12 +668,12 @@ double AccountValue::ActualMonthlyRate(double 
monthly_rate) const
 
 //============================================================================
 // Rounded interest increment.
-double AccountValue::InterestCredited
-    (double principal
-    ,double monthly_rate
+currency AccountValue::InterestCredited
+    (currency principal
+    ,double   monthly_rate
     ) const
 {
-    return round_interest_credit()(principal * 
ActualMonthlyRate(monthly_rate));
+    return currency(round_interest_credit()(principal * 
ActualMonthlyRate(monthly_rate)));
 }
 
 //============================================================================
@@ -667,20 +701,21 @@ int AccountValue::MonthsToNextModalPmtDate() const
 /// Argument 'term_rider' indicates whether a term rider is to be
 /// taken into account, as that affects the base-policy minimum.
 
-double AccountValue::minimum_specified_amount(bool issuing_now, bool 
term_rider) const
+currency AccountValue::minimum_specified_amount(bool issuing_now, bool 
term_rider) const
 {
-    return
+    return currency
+        (
           issuing_now
         ? (term_rider ? MinIssBaseSpecAmt  : MinIssSpecAmt )
         : (term_rider ? MinRenlBaseSpecAmt : MinRenlSpecAmt)
-        ;
+        );
 }
 
 //============================================================================
 // All changes to SA must be handled here.
 // Proportionately reduce base and term SA if term rider present.
 // Make sure ActualSpecAmt is never less than minimum specamt.
-void AccountValue::ChangeSpecAmtBy(double delta)
+void AccountValue::ChangeSpecAmtBy(currency delta)
 {
     double ProportionAppliedToTerm = 0.0;
     // Adjust term here only if it's formally a rider.
@@ -726,7 +761,7 @@ void AccountValue::ChangeSpecAmtBy(double delta)
             if(TermRiderActive)
                 {
                 TermSpecAmt =
-                      std::max(TermSpecAmt + ActualSpecAmt, MinRenlSpecAmt)
+                      std::max<double>(TermSpecAmt + ActualSpecAmt, 
MinRenlSpecAmt)
                     - ActualSpecAmt
                     ;
                 }
@@ -772,11 +807,11 @@ void AccountValue::ChangeSpecAmtBy(double delta)
     TxSetDeathBft();
 }
 
-void AccountValue::ChangeSupplAmtBy(double delta)
+void AccountValue::ChangeSupplAmtBy(currency delta)
 {
     TermSpecAmt += delta;
 
-    TermSpecAmt = std::max
+    TermSpecAmt = std::max<double>
         (TermSpecAmt
         ,0.0 // No minimum other than zero is defined.
         );
@@ -869,11 +904,11 @@ void AccountValue::TxOptionChange()
                 {
                 if(mce_option2 == old_option)
                     {
-                    ChangeSpecAmtBy(std::max(0.0, TotalAccountValue()));
+                    ChangeSpecAmtBy(std::max(currency(0.0), 
TotalAccountValue()));
                     }
                 else if(mce_rop == old_option)
                     {
-                    ChangeSpecAmtBy(std::max(0.0, CumPmts));
+                    ChangeSpecAmtBy(std::max(currency(0.0), CumPmts));
                     }
                 else if(mce_mdb == old_option)
                     {
@@ -900,7 +935,7 @@ void AccountValue::TxOptionChange()
         case mce_option2:
             if(OptChgCanDecrSA)
                 {
-                ChangeSpecAmtBy(-std::max(0.0, TotalAccountValue()));
+                ChangeSpecAmtBy(currency(0.0) - std::max(currency(0.0), 
TotalAccountValue()));
                 }
             else
                 {
@@ -910,7 +945,7 @@ void AccountValue::TxOptionChange()
         case mce_rop:
             if(OptChgCanDecrSA)
                 {
-                ChangeSpecAmtBy(-std::max(0.0, CumPmts));
+                ChangeSpecAmtBy(currency(0.0) - std::max(currency(0.0), 
CumPmts));
                 }
             else
                 {
@@ -920,7 +955,7 @@ void AccountValue::TxOptionChange()
         case mce_mdb:
             {
             // Change spec amt by its additive inverse, making it 0.
-            ChangeSpecAmtBy(-(ActualSpecAmt + TermSpecAmt));
+            ChangeSpecAmtBy(currency(0.0) - (ActualSpecAmt + TermSpecAmt));
             }
             break;
         }
@@ -956,7 +991,7 @@ void AccountValue::TxSpecAmtChange()
 // to be called only when the spec amt changes; calling it with an argument
 // of zero is a rather bizarre concept.
 
-        ChangeSpecAmtBy(0.0);
+        ChangeSpecAmtBy(currency(0.0));
         return;
         }
 
@@ -1199,7 +1234,9 @@ void AccountValue::TxAscertainDesiredPayment()
         // Illustration-reg guaranteed premium ignores GPT limit.
         if(!SolvingForGuarPremium)
             {
-            Irc7702_->ProcessGptPmt(Year, Dumpin);
+            double dumpin = doubleize(Dumpin); // yicky workaround
+            Irc7702_->ProcessGptPmt(Year, dumpin);
+            Dumpin = currency(dumpin);
             }
         EeGrossPmts[Month] += Dumpin;
         GrossPmts  [Month] += Dumpin;
@@ -1229,8 +1266,8 @@ void AccountValue::TxLimitPayment(double a_maxpmt)
             {
             gross_1035 = External1035Amount + Internal1035Amount;
             }
-        double gross_pmt_without_1035 = GrossPmts[Month] - gross_1035;
-        gross_pmt_without_1035 = std::min(gross_pmt_without_1035, a_maxpmt);
+        currency gross_pmt_without_1035 = GrossPmts[Month] - gross_1035;
+        gross_pmt_without_1035 = 
currency(std::min(doubleize(gross_pmt_without_1035), a_maxpmt));
         // TODO ?? For now at least, reduce employee premium first.
         progressively_limit
             (EeGrossPmts[Month]
@@ -1268,8 +1305,8 @@ void AccountValue::TxLimitPayment(double a_maxpmt)
 
 //============================================================================
 void AccountValue::TxRecognizePaymentFor7702A
-    (double a_pmt
-    ,bool   a_this_payment_is_unnecessary
+    (currency const a_pmt
+    ,bool     a_this_payment_is_unnecessary
     )
 {
     if(0.0 == a_pmt)
@@ -1286,7 +1323,7 @@ void AccountValue::TxRecognizePaymentFor7702A
         {
         kludge_account_value = Dcv;
         }
-    kludge_account_value = std::max
+    kludge_account_value = std::max<double>
         (HoneymoonValue
         ,   kludge_account_value
           + GetRefundableSalesLoad()
@@ -1310,13 +1347,18 @@ void AccountValue::TxRecognizePaymentFor7702A
 }
 
 //============================================================================
-void AccountValue::TxAcceptPayment(double a_pmt)
+void AccountValue::TxAcceptPayment(currency const a_pmt)
 {
     if(0.0 == a_pmt)
         {
         return;
         }
 
+    if(a_pmt < 0.0)
+      warning()
+        << a_pmt << " a_pmt\n"
+        << LMI_FLUSH
+        ;
     LMI_ASSERT(0.0 <= a_pmt);
     // Internal 1035 exchanges may be exempt from premium tax; they're
     // handled elsewhere, so here the exempt amount is always zero.
@@ -1331,14 +1373,14 @@ void AccountValue::TxAcceptPayment(double a_pmt)
     // automatic increases are not adjustable events, and the SA increase
     // due to a payment in this case might be considered automatic by IRS.
 
-    process_payment(net_pmt);
+    process_payment(currency(round_net_premium()(net_pmt))); // round more 
carefully above
 
     Dcv += std::max(0.0, net_pmt);
     LMI_ASSERT(0.0 <= Dcv);
 
     if(HoneymoonActive)
         {
-        HoneymoonValue += std::max(0.0, a_pmt);
+        HoneymoonValue += std::max<double>(0.0, a_pmt);
         }
 
     CumPmts += a_pmt;
@@ -1372,9 +1414,9 @@ void AccountValue::TxAcceptPayment(double a_pmt)
 /// calculation doesn't require too many adjustments, in particular
 /// when tiered premium tax is passed through as a load.
 
-double AccountValue::GetPremLoad
-    (double a_pmt
-    ,double a_portion_exempt_from_premium_tax
+currency AccountValue::GetPremLoad
+    (currency a_pmt
+    ,double   a_portion_exempt_from_premium_tax
     )
 {
     double excess_portion;
@@ -1435,13 +1477,13 @@ double AccountValue::GetPremLoad
         ||  materially_equal(total_load, sum_of_separate_loads)
         );
 
-    return round_net_premium()(sum_of_separate_loads);
+    return currency(round_net_premium()(sum_of_separate_loads));
 }
 
 //============================================================================
-double AccountValue::GetRefundableSalesLoad() const
+currency AccountValue::GetRefundableSalesLoad() const
 {
-    return CumulativeSalesLoad * YearsSalesLoadRefundRate;
+    return currency(CumulativeSalesLoad * YearsSalesLoadRefundRate);
 #if 0
     // CURRENCY !! Assertions such as these are desirable, but adding
     // them now would cause regression artifacts.
@@ -1471,11 +1513,11 @@ void AccountValue::TxLoanRepay()
 
     // TODO ?? This idiom seems too cute. And it can return -0.0 .
     // Maximum repayment is total debt.
-    ActualLoan = -std::min(-RequestedLoan, RegLnBal + PrfLnBal);
+    ActualLoan = -std::min<double>(-RequestedLoan, RegLnBal + PrfLnBal);
 
     process_distribution(ActualLoan);
-    LMI_ASSERT(0.0 == progressively_reduce(AVRegLn , AVPrfLn , -ActualLoan));
-    LMI_ASSERT(0.0 == progressively_reduce(RegLnBal, PrfLnBal, -ActualLoan));
+    LMI_ASSERT(0.0 == progressively_reduce(AVRegLn , AVPrfLn , currency(0.0) - 
ActualLoan));
+    LMI_ASSERT(0.0 == progressively_reduce(RegLnBal, PrfLnBal, currency(0.0) - 
ActualLoan));
 
 // This seems wrong. If we're changing something that's invariant among
 // bases, why do we change it for each basis?
@@ -1508,7 +1550,7 @@ void AccountValue::TxSetBOMAV()
                 )
             : yare_input_.InforceSpecAmtLoadBase
             ;
-        SpecAmtLoadBase = std::min(SpecAmtLoadBase, SpecAmtLoadLimit);
+        SpecAmtLoadBase = std::min<double>(SpecAmtLoadBase, SpecAmtLoadLimit);
         }
 
     // These assignments must happen every month.
@@ -1573,7 +1615,7 @@ void AccountValue::TxSetDeathBft()
         case mce_option2:
             {
             // Negative AV doesn't decrease death benefit.
-            DBIgnoringCorr = ActualSpecAmt + std::max(0.0, 
TotalAccountValue());
+            DBIgnoringCorr = ActualSpecAmt + std::max<double>(0.0, 
TotalAccountValue());
             DB7702A        = ActualSpecAmt;
             }
             break;
@@ -1582,8 +1624,8 @@ void AccountValue::TxSetDeathBft()
             // SA + sum of premiums less withdrawals, but not < SA;
             // i.e., ignore 'CumPmts' if it is less than zero, as it
             // easily can be, e.g., if WDs are not limited to basis.
-            DBIgnoringCorr = ActualSpecAmt + std::max(0.0, CumPmts);
-            DB7702A        = ActualSpecAmt + std::max(0.0, CumPmts);
+            DBIgnoringCorr = ActualSpecAmt + std::max<double>(0.0, CumPmts);
+            DB7702A        = ActualSpecAmt + std::max<double>(0.0, CumPmts);
             }
             break;
         case mce_mdb:
@@ -1602,19 +1644,19 @@ void AccountValue::TxSetDeathBft()
     // surrender charge must be subtracted, increasing the account value.
     double cash_value_for_corridor =
           TotalAccountValue()
-        - std::min(0.0, SurrChg())
+        - std::min<double>(0.0, SurrChg())
         + GetRefundableSalesLoad()
 //        + std::max(0.0, ExpRatReserve) // This would be added if it existed.
         ;
 
-    cash_value_for_corridor = std::max
+    cash_value_for_corridor = std::max<double>
         (cash_value_for_corridor
         ,HoneymoonValue
         );
 
-    DBReflectingCorr = std::max
+    DBReflectingCorr = std::max<double>
         (DBIgnoringCorr
-        ,YearsCorridorFactor * std::max(0.0, cash_value_for_corridor)
+        ,YearsCorridorFactor * std::max<double>(0.0, cash_value_for_corridor)
         );
     DBReflectingCorr = round_death_benefit()(DBReflectingCorr);
     LMI_ASSERT(0.0 <= DBReflectingCorr);
@@ -1623,12 +1665,12 @@ void AccountValue::TxSetDeathBft()
     // TAXATION !! Use DB_Irc7702BftIsSpecAmt
     DB7702A = DBReflectingCorr + TermDB;
 
-    DcvDeathBft = std::max
+    DcvDeathBft = std::max<double>
         (DBIgnoringCorr
         ,   (
                 YearsCorridorFactor
             *   (   Dcv
-                -   std::min(0.0, SurrChg())
+                -   std::min<double>(0.0, SurrChg())
                 +   GetRefundableSalesLoad()
 //                +   std::max(0.0, ExpRatReserve) // This would be added if 
it existed.
                 )
@@ -1663,7 +1705,7 @@ void AccountValue::TxSetTermAmt()
         return;
         }
 
-    TermDB = std::max(0.0, TermSpecAmt + DBIgnoringCorr - DBReflectingCorr);
+    TermDB = std::max<double>(0.0, TermSpecAmt + DBIgnoringCorr - 
DBReflectingCorr);
     TermDB = round_death_benefit()(TermDB);
 }
 
@@ -1719,17 +1761,29 @@ void AccountValue::TxSetCoiCharge()
     // the account value by deducting a negative mortality charge.
     NAAR = material_difference
         (DBReflectingCorr * DBDiscountRate[Year]
-        ,std::max(0.0, TotalAccountValue())
+        ,std::max<double>(0.0, TotalAccountValue())
         );
     NAAR = std::max(0.0, round_naar()(NAAR));
 
+#if 0
+      warning()
+        << std::setprecision(21)
+        << NAAR << " NAAR\n"
+        << DBReflectingCorr << " DBReflectingCorr\n"
+        << DBDiscountRate[Year] << " DBDiscountRate[Year]\n"
+        << DBReflectingCorr * DBDiscountRate[Year] << " DBReflectingCorr * 
DBDiscountRate[Year]\n"
+        << TotalAccountValue() << " TotalAccountValue()\n"
+        << std::max<double>(0.0, TotalAccountValue()) << " 
std::max<double>(0.0, TotalAccountValue())\n"
+        << LMI_FLUSH
+        ;
+#endif // 0
 // TODO ?? This doesn't work. We need to reconsider the basic transactions.
 //  double naar_forceout = std::max(0.0, NAAR - MaxNAAR);
 //  process_distribution(naar_forceout);
 // TAXATION !! Should this be handled at the same time as GPT forceouts?
 
     DcvNaar = material_difference
-        (std::max(DcvDeathBft, DBIgnoringCorr) * DBDiscountRate[Year]
+        (std::max<double>(DcvDeathBft, DBIgnoringCorr) * DBDiscountRate[Year]
         ,std::max(0.0, Dcv)
         );
     // DCV need not be rounded.
@@ -1777,7 +1831,7 @@ void AccountValue::TxSetRiderDed()
     AdbCharge = 0.0;
     if(yare_input_.AccidentalDeathBenefit)
         {
-        AdbCharge = YearsAdbRate * std::min(ActualSpecAmt, AdbLimit);
+        AdbCharge = YearsAdbRate * std::min<double>(ActualSpecAmt, AdbLimit);
         }
 
     SpouseRiderCharge = 0.0;
@@ -1811,7 +1865,7 @@ void AccountValue::TxSetRiderDed()
             {
             case oe_waiver_times_specamt:
                 {
-                WpCharge = YearsWpRate * std::min(ActualSpecAmt, WpLimit);
+                WpCharge = YearsWpRate * std::min<double>(ActualSpecAmt, 
WpLimit);
                 DcvWpCharge = WpCharge;
                 }
                 break;
@@ -1985,7 +2039,7 @@ void AccountValue::TxTakeSepAcctLoad()
 //============================================================================
 // When the M&E charge depends on each month's case total assets, the
 // interest rate is no longer an annual invariant. Set it monthly here.
-void AccountValue::ApplyDynamicMandE(double assets)
+void AccountValue::ApplyDynamicMandE(currency assets)
 {
     if(!MandEIsDynamic)
         {
@@ -2214,26 +2268,26 @@ void AccountValue::TxLoanInt()
 // actual future deductions--particularly in the month of issue, when
 // it is zero.
 //
-double AccountValue::anticipated_deduction
+currency AccountValue::anticipated_deduction
     (mcenum_anticipated_deduction method)
 {
     switch(method)
         {
         case mce_twelve_times_last:
             {
-            return 12.0 * MlyDed;
+            return currency(MlyDed * double(12));
             }
         case mce_eighteen_times_last:
             {
-            return 18.0 * MlyDed;
+            return currency(MlyDed * double(18));
             }
         case mce_to_next_anniversary:
             {
-            return MlyDed * (13 - Month);
+            return currency(MlyDed * double(13 - Month));
             }
         case mce_to_next_modal_pmt_date:
             {
-            return MlyDed * (1 + MonthsToNextModalPmtDate());
+            return currency(MlyDed * double(1 + MonthsToNextModalPmtDate()));
             }
         }
     throw "Unreachable--silences a compiler diagnostic.";
@@ -2261,13 +2315,13 @@ void AccountValue::SetMaxWD()
         + (AVRegLn  + AVPrfLn)
         - (RegLnBal + PrfLnBal)
         - anticipated_deduction(MaxWDDed_)
-        - std::max(0.0, SurrChg())
+        - std::max<double>(0.0, SurrChg())
         ;
     if(MaxWD < MinWD)
         {
         MaxWD = 0.0;
         }
-    MaxWD = std::max(0.0, MaxWD);
+    MaxWD = std::max<double>(0.0, MaxWD);
 }
 
 /// Take a withdrawal.
@@ -2307,7 +2361,7 @@ void AccountValue::TxTakeWD()
 
     if(Solving)
         {
-        withdrawal_ullage_[Year] = std::max(0.0, RequestedWD - MaxWD);
+        withdrawal_ullage_[Year] = std::max<double>(0.0, RequestedWD - MaxWD);
         }
 
     if(Solving || mce_run_gen_curr_sep_full == RunBasis_)
@@ -2438,7 +2492,7 @@ void AccountValue::TxTakeWD()
         return;
         }
 
-    GrossWD = NetWD + std::min(WDFee, NetWD * WDFeeRate);
+    GrossWD = NetWD + std::min<double>(WDFee, NetWD * WDFeeRate);
 
     // Free partial surrenders: for instance, the first 20% of account
     // value might be withdrawn each policy year free of surrender
@@ -2456,7 +2510,7 @@ 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, GrossWD - free_wd);
+        non_free_wd = std::max<double>(0.0, GrossWD - free_wd);
         }
     double partial_surrchg = non_free_wd * surrchg_proportion;
     GrossWD += partial_surrchg;
@@ -2481,7 +2535,7 @@ void AccountValue::TxTakeWD()
             // Do you really want 'face' here rather than specamt? --Yes
             if(WdDecrSpecAmtDboLvl)
                 {
-                ChangeSpecAmtBy(-GrossWD);
+                ChangeSpecAmtBy(currency(0.0) - GrossWD);
                 // Min AV after WD not directly implemented.
                 // If WD causes AV < min AV, do we:
                 //   reduce the WD?
@@ -2501,7 +2555,7 @@ void AccountValue::TxTakeWD()
             {
             if(WdDecrSpecAmtDboInc)
                 {
-                ChangeSpecAmtBy(-GrossWD);
+                ChangeSpecAmtBy(currency(0.0) - GrossWD);
                 }
             else
                 {
@@ -2513,7 +2567,7 @@ void AccountValue::TxTakeWD()
             {
             if(WdDecrSpecAmtDboRop)
                 {
-                ChangeSpecAmtBy(-GrossWD);
+                ChangeSpecAmtBy(currency(0.0) - GrossWD);
                 }
             else
                 {
@@ -2559,11 +2613,11 @@ void AccountValue::TxTakeWD()
 // Calculate maximum permissible total loan (not increment).
 void AccountValue::SetMaxLoan()
 {
-    MaxLoan =
+    double max_loan =
           (AVGenAcct + AVSepAcct) * MaxLoanAVMult
         + (AVRegLn + AVPrfLn)
         - anticipated_deduction(MaxLoanDed_)
-        - std::max(0.0, SurrChg())
+        - std::max<double>(0.0, SurrChg())
         ;
 
     // Illustrations generally permit loans only on anniversary.
@@ -2598,7 +2652,7 @@ void AccountValue::SetMaxLoan()
     // the end of the policy year--but does not guarantee that, e.g.
     // because the specified amount may change between anniversaries,
     // even on illustrations.
-    MaxLoan -=
+    max_loan -=
           RegLnBal * reg_loan_factor
         + PrfLnBal * prf_loan_factor
         ;
@@ -2608,9 +2662,9 @@ void AccountValue::SetMaxLoan()
     // plausible but unasserted assumption that that factor is more
     // liberal than the preferred-loan factor?
     //
-    MaxLoan *= 1.0 - (reg_loan_factor) / (1.0 + reg_loan_factor);
+    max_loan *= 1.0 - (reg_loan_factor) / (1.0 + reg_loan_factor);
 
-    MaxLoan = round_loan()(MaxLoan);
+    MaxLoan = round_loan()(max_loan);
 
     // I do not think we want a MaxLoan < current level of indebtedness.
     MaxLoan = std::max((AVRegLn + AVPrfLn), MaxLoan);
@@ -2649,12 +2703,12 @@ void AccountValue::TxTakeLoan()
     if(Solving)
         {
         ActualLoan = RequestedLoan;
-        loan_ullage_[Year] = std::max(0.0, RequestedLoan - max_loan_increment);
+        loan_ullage_[Year] = std::max<double>(0.0, RequestedLoan - 
max_loan_increment);
         }
     else
         {
-        ActualLoan = std::min(max_loan_increment, RequestedLoan);
-        ActualLoan = std::max(ActualLoan, 0.0);
+        ActualLoan = std::min<double>(max_loan_increment, RequestedLoan);
+        ActualLoan = std::max<double>(ActualLoan, 0.0);
         // TODO ?? Shouldn't this happen in FinalizeMonth()?
         InvariantValues().NewCashLoan[Year] = ActualLoan;
         }
@@ -2734,9 +2788,9 @@ void AccountValue::TxTestLapse()
         ;
     if(!LapseIgnoresSurrChg)
         {
-        lapse_test_csv -= std::max(0.0, SurrChg());
+        lapse_test_csv -= std::max<double>(0.0, SurrChg());
         }
-    lapse_test_csv = std::max(lapse_test_csv, HoneymoonValue);
+    lapse_test_csv = std::max<double>(lapse_test_csv, HoneymoonValue);
 
     // Perform no-lapse test.
     if(NoLapseActive && !NoLapseAlwaysActive)
diff --git a/ihs_avsolve.cpp b/ihs_avsolve.cpp
index 15411f4..0fc0325 100644
--- a/ihs_avsolve.cpp
+++ b/ihs_avsolve.cpp
@@ -242,7 +242,7 @@ void AccountValue::SolveSetSpecAmt(double a_CandidateValue)
 {
 // TODO ?? Does this change the surrchg when specamt changes?
     DeathBfts_->set_specamt
-        (a_CandidateValue
+        (currency(a_CandidateValue)
         ,SolveBeginYear_
         ,SolveEndYear_
         );
@@ -251,35 +251,35 @@ void AccountValue::SolveSetSpecAmt(double 
a_CandidateValue)
 //============================================================================
 void AccountValue::SolveSetEePrem(double a_CandidateValue)
 {
-    Outlay_->set_ee_modal_premiums(a_CandidateValue, SolveBeginYear_, 
SolveEndYear_);
+    Outlay_->set_ee_modal_premiums(currency(a_CandidateValue), 
SolveBeginYear_, SolveEndYear_);
 }
 
 //============================================================================
 void AccountValue::SolveSetErPrem(double a_CandidateValue)
 {
-    Outlay_->set_er_modal_premiums(a_CandidateValue, SolveBeginYear_, 
SolveEndYear_);
+    Outlay_->set_er_modal_premiums(currency(a_CandidateValue), 
SolveBeginYear_, SolveEndYear_);
 }
 
 //============================================================================
 void AccountValue::SolveSetLoan(double a_CandidateValue)
 {
-    Outlay_->set_new_cash_loans(a_CandidateValue, SolveBeginYear_, 
SolveEndYear_);
+    Outlay_->set_new_cash_loans(currency(a_CandidateValue), SolveBeginYear_, 
SolveEndYear_);
 }
 
 //============================================================================
 void AccountValue::SolveSetWD(double a_CandidateValue)
 {
-    Outlay_->set_withdrawals(a_CandidateValue, SolveBeginYear_, SolveEndYear_);
+    Outlay_->set_withdrawals(currency(a_CandidateValue), SolveBeginYear_, 
SolveEndYear_);
 }
 
 //============================================================================
 double AccountValue::SolveGuarPremium()
 {
     // Store original er premiums for later restoration.
-    std::vector<double> stored = Outlay_->er_modal_premiums();
+    std::vector<currency> stored = Outlay_->er_modal_premiums();
     // Zero out er premiums and solve for ee premiums only.
     Outlay_->set_er_modal_premiums
-        (0.0
+        (currency(0.0)
         ,0
         ,static_cast<int>(InvariantValues().EndtAge - InvariantValues().Age)
         );
diff --git a/ihs_avstrtgy.cpp b/ihs_avstrtgy.cpp
index e748777..1e26a4e 100644
--- a/ihs_avstrtgy.cpp
+++ b/ihs_avstrtgy.cpp
@@ -55,19 +55,19 @@
 ///
 /// No minimum is imposed here; see PerformSpecAmtStrategy().
 
-double AccountValue::CalculateSpecAmtFromStrategy
+currency AccountValue::CalculateSpecAmtFromStrategy
     (int                actual_year
     ,int                reference_year
-    ,double             explicit_value
+    ,currency           explicit_value
     ,mcenum_sa_strategy strategy
     ) const
 {
-    double annualized_pmt =
+    currency annualized_pmt(
             InvariantValues().EeMode[reference_year].value()
           * InvariantValues().EePmt [reference_year]
         +   InvariantValues().ErMode[reference_year].value()
           * InvariantValues().ErPmt [reference_year]
-        ;
+          );
     switch(strategy)
         {
         case mce_sa_input_scalar:
@@ -130,8 +130,8 @@ void AccountValue::PerformSpecAmtStrategy()
     for(int j = 0; j < BasicValues::Length; ++j)
         {
         bool t = yare_input_.TermRider && 0.0 != yare_input_.TermRiderAmount;
-        double m = minimum_specified_amount(0 == j, t);
-        double explicit_value = DeathBfts_->specamt()[j];
+        currency m = minimum_specified_amount(0 == j, t);
+        currency explicit_value = DeathBfts_->specamt()[j];
         mcenum_sa_strategy strategy = yare_input_.SpecifiedAmountStrategy[j];
         // Don't override a specamt that's being solved for.
         if
@@ -144,7 +144,7 @@ void AccountValue::PerformSpecAmtStrategy()
             strategy = mce_sa_input_scalar;
             }
         double z = CalculateSpecAmtFromStrategy(j, 0, explicit_value, 
strategy);
-        DeathBfts_->set_specamt(round_specamt()(std::max(m, z)), j, 1 + j);
+        DeathBfts_->set_specamt(currency(round_specamt()(std::max<double>(m, 
z))), j, 1 + j); // rounding
         if
             (  j == InforceYear
             && yare_input_.EffectiveDate != yare_input_.InforceAsOfDate
@@ -171,22 +171,22 @@ void AccountValue::PerformSupplAmtStrategy()
 {
     for(int j = 0; j < BasicValues::Length; ++j)
         {
-        double m = 0.0; // No minimum other than zero is defined.
-        double explicit_value = DeathBfts_->supplamt()[j];
+        currency m = currency(0.0); // No minimum other than zero is defined.
+        currency explicit_value = DeathBfts_->supplamt()[j];
         mcenum_sa_strategy strategy = 
yare_input_.SupplementalAmountStrategy[j];
-        double z = CalculateSpecAmtFromStrategy(j, 0, explicit_value, 
strategy);
-        DeathBfts_->set_supplamt(round_specamt()(std::max(m, z)), j, 1 + j);
+        currency z = CalculateSpecAmtFromStrategy(j, 0, explicit_value, 
strategy);
+        DeathBfts_->set_supplamt(currency(round_specamt()(std::max(m, z))), j, 
1 + j); // rounding
         }
 }
 
 /// Set payment according to selected strategy in a non-solve year.
 
-double AccountValue::DoPerformPmtStrategy
+currency AccountValue::DoPerformPmtStrategy
     (mcenum_solve_type                       a_SolveForWhichPrem
     ,mcenum_mode                             a_CurrentMode
     ,mcenum_mode                             a_InitialMode
     ,double                                  a_TblMult
-    ,std::vector<double> const&              a_PmtVector
+    ,std::vector<currency> const&            a_PmtVector
     ,std::vector<mcenum_pmt_strategy> const& a_StrategyVector
     ) const
 {
@@ -257,7 +257,7 @@ double AccountValue::DoPerformPmtStrategy
                 }
             else
                 {
-                double sa = ActualSpecAmt + TermSpecAmt;
+                currency sa = ActualSpecAmt + TermSpecAmt;
                 return GetModalMinPrem(Year, a_CurrentMode, sa);
                 }
             }
@@ -265,38 +265,38 @@ double AccountValue::DoPerformPmtStrategy
         case mce_pmt_target:
             {
             int const target_year = TgtPremFixedAtIssue ? 0 : Year;
-            double sa = InvariantValues().SpecAmt[target_year];
+            currency sa = requantize(InvariantValues().SpecAmt[target_year]);
             return GetModalTgtPrem(Year, a_CurrentMode, sa);
             }
         case mce_pmt_mep:
             {
-            double sa =
-                                      InvariantValues().SpecAmt    [0]
-                + (TermIsDbFor7702A ? InvariantValues().TermSpecAmt[0] : 0.0)
+            currency sa =
+                                      requantize(InvariantValues().SpecAmt    
[0])
+                + (TermIsDbFor7702A ? 
requantize(InvariantValues().TermSpecAmt[0]) : currency(0.0))
                 ;
             return GetModalPremMaxNonMec(0, a_InitialMode, sa);
             }
         case mce_pmt_glp:
             {
-            double sa =
-                                     InvariantValues().SpecAmt    [0]
-                + (TermIsDbFor7702 ? InvariantValues().TermSpecAmt[0] : 0.0)
+            currency sa =
+                                     requantize(InvariantValues().SpecAmt    
[0])
+                + (TermIsDbFor7702 ? 
requantize(InvariantValues().TermSpecAmt[0]) : currency(0.0))
                 ;
             return GetModalPremGLP(0, a_InitialMode, sa, sa);
             }
         case mce_pmt_gsp:
             {
-            double sa =
-                                     InvariantValues().SpecAmt    [0]
-                + (TermIsDbFor7702 ? InvariantValues().TermSpecAmt[0] : 0.0)
+            currency sa =
+                                     requantize(InvariantValues().SpecAmt    
[0])
+                + (TermIsDbFor7702 ? 
requantize(InvariantValues().TermSpecAmt[0]) : currency(0.0))
                 ;
             return GetModalPremGSP(0, a_InitialMode, sa, sa);
             }
         case mce_pmt_corridor:
             {
-            double sa =
-                                     InvariantValues().SpecAmt    [0]
-                + (TermIsDbFor7702 ? InvariantValues().TermSpecAmt[0] : 0.0)
+            currency sa =
+                                     requantize(InvariantValues().SpecAmt    
[0])
+                + (TermIsDbFor7702 ? 
requantize(InvariantValues().TermSpecAmt[0]) : currency(0.0))
                 ;
             return GetModalPremCorridor(0, a_InitialMode, sa);
             }
@@ -315,28 +315,28 @@ double AccountValue::DoPerformPmtStrategy
 
 /// Set employee payment according to selected strategy.
 
-double AccountValue::PerformEePmtStrategy() const
+currency AccountValue::PerformEePmtStrategy() const
 {
     return DoPerformPmtStrategy
         (mce_solve_ee_prem
         ,InvariantValues().EeMode[Year].value()
         ,InvariantValues().EeMode[0]   .value()
         ,yare_input_.InsuredPremiumTableFactor
-        ,InvariantValues().EePmt
+        ,currencyize(InvariantValues().EePmt)
         ,yare_input_.PaymentStrategy
         );
 }
 
 /// Set employer payment according to selected strategy.
 
-double AccountValue::PerformErPmtStrategy() const
+currency AccountValue::PerformErPmtStrategy() const
 {
     return DoPerformPmtStrategy
         (mce_solve_er_prem
         ,InvariantValues().ErMode[Year].value()
         ,InvariantValues().ErMode[0]   .value()
         ,yare_input_.CorporationPremiumTableFactor
-        ,InvariantValues().ErPmt
+        ,currencyize(InvariantValues().ErPmt)
         ,yare_input_.CorporationPaymentStrategy
         );
 }
diff --git a/ihs_basicval.cpp b/ihs_basicval.cpp
index cec8a0a..0d9a853 100644
--- a/ihs_basicval.cpp
+++ b/ihs_basicval.cpp
@@ -596,7 +596,7 @@ void BasicValues::Init7702A()
 
 /// Public function used for GPT specamt calculation.
 
-double BasicValues::GetAnnualTgtPrem(int a_year, double a_specamt) const
+currency BasicValues::GetAnnualTgtPrem(int a_year, currency a_specamt) const
 {
     return GetModalTgtPrem(a_year, mce_annual, a_specamt);
 }
@@ -843,10 +843,10 @@ void BasicValues::SetMaxSurvivalDur()
 ///    value would otherwise be negative), or
 ///  - keeps account value nonnegative (preventing lapse directly).
 
-double BasicValues::GetModalMinPrem
+currency BasicValues::GetModalMinPrem
     (int         a_year
     ,mcenum_mode a_mode
-    ,double      a_specamt
+    ,currency    a_specamt
     ) const
 {
     switch(MinPremType)
@@ -863,10 +863,10 @@ double BasicValues::GetModalMinPrem
 
 /// Ascertain modal payment for a target-premium strategy.
 
-double BasicValues::GetModalTgtPrem
+currency BasicValues::GetModalTgtPrem
     (int         a_year
     ,mcenum_mode a_mode
-    ,double      a_specamt
+    ,currency    a_specamt
     ) const
 {
     int const target_year = TgtPremFixedAtIssue ? 0 : a_year;
@@ -889,15 +889,16 @@ double BasicValues::GetModalTgtPrem
 /// and specified amount. Thus, arguments should represent initial
 /// premium and mode.
 
-double BasicValues::GetModalPremMaxNonMec
+currency BasicValues::GetModalPremMaxNonMec
     (int      // a_year // Unused.
     ,mcenum_mode a_mode
-    ,double      a_specamt
+    ,currency    a_specamt
     ) const
 {
     // TAXATION !! No table available if 7PP calculated from first principles.
     double temp = MortalityRates_->SevenPayRates()[0];
-    return round_max_premium()(ldbl_eps_plus_one_times(temp * a_specamt / 
a_mode));
+    double z = round_max_premium()(ldbl_eps_plus_one_times(temp * a_specamt / 
a_mode));
+    return currency(z);
 }
 
 /// Calculate premium using a minimum-premium ratio.
@@ -907,19 +908,20 @@ double BasicValues::GetModalPremMaxNonMec
 /// the initial specified amount may also be fixed at issue, but that
 /// choice is left to the caller.
 
-double BasicValues::GetModalPremMinFromTable
+currency BasicValues::GetModalPremMinFromTable
     (int      // a_year // Unused.
     ,mcenum_mode a_mode
-    ,double      a_specamt
+    ,currency    a_specamt
     ) const
 {
-    return round_max_premium()
+    double z = round_max_premium()
         (ldbl_eps_plus_one_times
             (
-                a_specamt * MortalityRates_->MinimumPremiumRates()[0]
+                doubleize(a_specamt) * 
MortalityRates_->MinimumPremiumRates()[0]
             /   a_mode
             )
         );
+    return currency(z);
 }
 
 /// Calculate premium using a target-premium ratio.
@@ -945,13 +947,13 @@ double BasicValues::GetModalPremMinFromTable
 /// asserted to be zero--upstream, so that it'll signal an error even
 /// if a target strategy isn't used.
 
-double BasicValues::GetModalPremTgtFromTable
+currency BasicValues::GetModalPremTgtFromTable
     (int      // a_year // Unused.
     ,mcenum_mode a_mode
-    ,double      a_specamt
+    ,currency    a_specamt
     ) const
 {
-    return round_max_premium()
+    double z = round_max_premium()
         (ldbl_eps_plus_one_times
             (
                 ( TgtPremMonthlyPolFee * 12
@@ -960,24 +962,26 @@ double BasicValues::GetModalPremTgtFromTable
             /   a_mode
             )
         );
+    return currency(z);
 }
 
 /// Calculate premium using a tabular proxy for group insurance.
 
-double BasicValues::GetModalPremProxyTable
+currency BasicValues::GetModalPremProxyTable
     (int         a_year
     ,mcenum_mode a_mode
-    ,double      a_specamt
+    ,currency    a_specamt
     ,double      a_table_multiplier
     ) const
 {
-    return round_gross_premium()
+    double z = round_gross_premium()
         (
           a_specamt
         * MortalityRates_->GroupProxyRates()[a_year]
         * a_table_multiplier
         / a_mode
         );
+    return currency(z);
 }
 
 /// Calculate premium using a corridor ratio.
@@ -986,22 +990,23 @@ double BasicValues::GetModalPremProxyTable
 /// strategy makes sense only at issue. Thus, arguments should
 /// represent initial specified amount and mode.
 
-double BasicValues::GetModalPremCorridor
+currency BasicValues::GetModalPremCorridor
     (int      // a_year // Unused.
     ,mcenum_mode a_mode
-    ,double      a_specamt
+    ,currency    a_specamt
     ) const
 {
     double temp = GetCorridorFactor()[0];
-    return round_max_premium()(ldbl_eps_plus_one_times((a_specamt / temp) / 
a_mode));
+    double z = round_max_premium()(ldbl_eps_plus_one_times((a_specamt / temp) 
/ a_mode));
+    return currency(z);
 }
 
 //============================================================================
-double BasicValues::GetModalPremGLP
+currency BasicValues::GetModalPremGLP
     (int         a_duration
     ,mcenum_mode a_mode
-    ,double      a_bft_amt
-    ,double      a_specamt
+    ,currency    a_bft_amt
+    ,currency    a_specamt
     ) const
 {
     // TAXATION !! Use GetAnnualTgtPrem() to get target here if needed
@@ -1019,15 +1024,16 @@ double BasicValues::GetModalPremGLP
 // term rider, dumpin
 
     z /= a_mode;
-    return round_max_premium()(ldbl_eps_plus_one_times(z));
+    z = round_max_premium()(ldbl_eps_plus_one_times(z));
+    return currency(z);
 }
 
 //============================================================================
-double BasicValues::GetModalPremGSP
+currency BasicValues::GetModalPremGSP
     (int         a_duration
     ,mcenum_mode a_mode
-    ,double      a_bft_amt
-    ,double      a_specamt
+    ,currency    a_bft_amt
+    ,currency    a_specamt
     ) const
 {
     double z = Irc7702_->CalculateGSP
@@ -1042,7 +1048,8 @@ double BasicValues::GetModalPremGSP
 // term rider, dumpin
 
     z /= a_mode;
-    return round_max_premium()(ldbl_eps_plus_one_times(z));
+    z = round_max_premium()(ldbl_eps_plus_one_times(z));
+    return currency(z);
 }
 
 /// Calculate a monthly-deduction discount factor on the fly.
@@ -1116,8 +1123,8 @@ double BasicValues::mly_ded_discount_factor(int year, 
mcenum_mode mode) const
 /// is desired.
 
 std::pair<double,double> BasicValues::approx_mly_ded
-    (int    year
-    ,double specamt
+    (int      year
+    ,currency specamt
     ) const
 {
     double mly_ded = specamt * DBDiscountRate[year];
@@ -1126,7 +1133,7 @@ std::pair<double,double> BasicValues::approx_mly_ded
     if(yare_input_.AccidentalDeathBenefit)
         {
         double const r = MortalityRates_->AdbRates()[year];
-        mly_ded += r * std::min(specamt, AdbLimit);
+        mly_ded += r * std::min<double>(specamt, AdbLimit);
         }
 
     if(yare_input_.SpouseRider)
@@ -1144,7 +1151,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(specamt, SpecAmtLoadLimit);
+        mly_ded += r * std::min<double>(specamt, SpecAmtLoadLimit);
         }
 
     mly_ded += Loads_->monthly_policy_fee(mce_gen_curr)[year];
@@ -1158,7 +1165,7 @@ std::pair<double,double> BasicValues::approx_mly_ded
             {
             case oe_waiver_times_specamt:
                 {
-                mly_ded += r * std::min(specamt, WpLimit);
+                mly_ded += r * std::min<double>(specamt, WpLimit);
                 }
                 break;
             case oe_waiver_times_deductions:
@@ -1171,6 +1178,9 @@ std::pair<double,double> BasicValues::approx_mly_ded
         }
 
     double const load = 
Loads_->target_premium_load_maximum_premium_tax()[year];
+    // Don't round!
+//  mly_ded /= round_minutiae()(1.0 - load);
+//  ann_ded /= round_minutiae()(1.0 - load);
     mly_ded /= 1.0 - load;
     ann_ded /= 1.0 - load;
 
@@ -1194,9 +1204,9 @@ std::pair<double,double> BasicValues::approx_mly_ded
 /// between ee and er would not be wanted.
 
 std::pair<double,double> BasicValues::approx_mly_ded_ex
-    (int    year
-    ,double specamt
-    ,double termamt
+    (int      year
+    ,currency specamt
+    ,currency termamt
     ) const
 {
     if(0.0 != Loads_->annual_policy_fee(mce_gen_curr)[year])
@@ -1214,7 +1224,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(specamt, AdbLimit);
+        er_ded += r * std::min<double>(specamt, AdbLimit);
         }
 
     // Paid by ee.
@@ -1235,7 +1245,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(specamt, SpecAmtLoadLimit);
+        er_ded += r * std::min<double>(specamt, SpecAmtLoadLimit);
         }
 
     // Paid by er.
@@ -1249,7 +1259,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(specamt, WpLimit);
+                er_ded += r * std::min<double>(specamt, WpLimit);
                 }
                 break;
             case oe_waiver_times_deductions:
@@ -1263,6 +1273,9 @@ std::pair<double,double> BasicValues::approx_mly_ded_ex
         }
 
     double const load = 
Loads_->target_premium_load_maximum_premium_tax()[year];
+    // Don't round!
+//  ee_ded /= round_minutiae()(1.0 - load);
+//  er_ded /= round_minutiae()(1.0 - load);
     ee_ded /= 1.0 - load;
     er_ded /= 1.0 - load;
 
@@ -1271,10 +1284,10 @@ std::pair<double,double> BasicValues::approx_mly_ded_ex
 
 /// Determine an approximate "pay as you go" modal premium.
 
-double BasicValues::GetModalPremMlyDed
+currency BasicValues::GetModalPremMlyDed
     (int         year
     ,mcenum_mode mode
-    ,double      specamt
+    ,currency    specamt
     ) const
 {
     auto const deductions = approx_mly_ded(year, specamt);
@@ -1282,16 +1295,16 @@ double BasicValues::GetModalPremMlyDed
     double const mly_ded = deductions.second;
     double const v12 = mly_ded_discount_factor(year, mode);
     double const annuity = (1.0 - std::pow(v12, 12.0 / mode)) / (1.0 - v12);
-    return round_min_premium()(ann_ded + mly_ded * annuity);
+    return currency(round_min_premium()(ann_ded + mly_ded * annuity));
 }
 
 /// Determine approximate ee and er "pay as you go" modal premiums.
 
-std::pair<double,double> BasicValues::GetModalPremMlyDedEx
+std::pair<currency,currency> BasicValues::GetModalPremMlyDedEx
     (int         year
     ,mcenum_mode mode
-    ,double      specamt
-    ,double      termamt
+    ,currency    specamt
+    ,currency    termamt
     ) const
 {
     auto const deductions = approx_mly_ded_ex(year, specamt, termamt);
@@ -1300,8 +1313,8 @@ std::pair<double,double> BasicValues::GetModalPremMlyDedEx
     double const v12 = DBDiscountRate[year];
     double const annuity = (1.0 - std::pow(v12, 12.0 / mode)) / (1.0 - v12);
     return std::make_pair
-        (round_min_premium()(ee_ded * annuity)
-        ,round_min_premium()(er_ded * annuity)
+        (currency(round_min_premium()(ee_ded * annuity))
+        ,currency(round_min_premium()(er_ded * annuity))
         );
 }
 
@@ -1322,10 +1335,10 @@ std::pair<double,double> 
BasicValues::GetModalPremMlyDedEx
 /// are extraordinary, and occur only on products for which list bills
 /// would not be wanted.
 
-double BasicValues::GetListBillPremMlyDed
+currency BasicValues::GetListBillPremMlyDed
     (int         year
     ,mcenum_mode mode
-    ,double      specamt
+    ,currency    specamt
     ) const
 {
     double const p0 = approx_mly_ded(year, specamt).second;
@@ -1343,14 +1356,14 @@ double BasicValues::GetListBillPremMlyDed
         ,yare_input_.ListBillDate
         ,mly_ded_discount_factor(year, mode)
         );
-    return round_min_premium()(z);
+    return currency(round_min_premium()(z));
 }
 
-std::pair<double,double> BasicValues::GetListBillPremMlyDedEx
+std::pair<currency,currency> BasicValues::GetListBillPremMlyDedEx
     (int         year
     ,mcenum_mode mode
-    ,double      specamt
-    ,double      termamt
+    ,currency    specamt
+    ,currency    termamt
     ) const
 {
     auto const p0 = approx_mly_ded_ex(year, specamt, termamt);
@@ -1377,12 +1390,12 @@ std::pair<double,double> 
BasicValues::GetListBillPremMlyDedEx
         ,DBDiscountRate[year]
         );
     return std::make_pair
-        (round_min_premium()(ee_prem)
-        ,round_min_premium()(er_prem)
+        (currency(round_min_premium()(ee_prem))
+        ,currency(round_min_premium()(er_prem))
         );
 }
 
-double BasicValues::GetModalSpecAmtMax(double annualized_pmt) const
+currency BasicValues::GetModalSpecAmtMax(currency annualized_pmt) const
 {
     switch(MinPremType)
         {
@@ -1391,10 +1404,12 @@ double BasicValues::GetModalSpecAmtMax(double 
annualized_pmt) const
         case oe_modal_nonmec:
             return GetModalSpecAmtMinNonMec(annualized_pmt);
         case oe_modal_table:
-            return round_min_specamt()
-                (
-                    annualized_pmt
-                /   MortalityRates_->MinimumPremiumRates()[0]
+            return currency
+                (round_min_specamt()
+                    (
+                        annualized_pmt
+                    /   MortalityRates_->MinimumPremiumRates()[0]
+                    )
                 );
         }
     throw "Unreachable--silences a compiler diagnostic.";
@@ -1406,7 +1421,7 @@ double BasicValues::GetModalSpecAmtMax(double 
annualized_pmt) const
 /// duration. It's taken to include 'TgtPremMonthlyPolFee', to make
 /// this function the inverse of GetModalPremTgtFromTable(), q.v.
 
-double BasicValues::GetModalSpecAmtTgt(double annualized_pmt) const
+currency BasicValues::GetModalSpecAmtTgt(currency annualized_pmt) const
 {
     switch(TgtPremType)
         {
@@ -1415,10 +1430,12 @@ double BasicValues::GetModalSpecAmtTgt(double 
annualized_pmt) const
         case oe_modal_nonmec:
             return GetModalSpecAmtMinNonMec(annualized_pmt);
         case oe_modal_table:
-            return round_min_specamt()
-                (
-                    (annualized_pmt - TgtPremMonthlyPolFee * 12)
-                /   MortalityRates_->TargetPremiumRates()[0]
+            return currency
+                (round_min_specamt()
+                    (
+                        (annualized_pmt - TgtPremMonthlyPolFee * 12)
+                    /   MortalityRates_->TargetPremiumRates()[0]
+                    )
                 );
         }
     throw "Unreachable--silences a compiler diagnostic.";
@@ -1430,23 +1447,24 @@ double BasicValues::GetModalSpecAmtTgt(double 
annualized_pmt) const
 /// changes dramatically complicate the relationship between premium
 /// and specified amount.
 
-double BasicValues::GetModalSpecAmtMinNonMec(double annualized_pmt) const
+currency BasicValues::GetModalSpecAmtMinNonMec(currency annualized_pmt) const
 {
     // TAXATION !! No table available if 7PP calculated from first principles.
-    return round_min_specamt()(annualized_pmt / 
MortalityRates_->SevenPayRates()[0]);
+    // shouldn't this be rounded?
+    return currency(round_min_specamt()(annualized_pmt / 
MortalityRates_->SevenPayRates()[0]));
 }
 
 //============================================================================
-double BasicValues::GetModalSpecAmtGLP(double annualized_pmt) const
+currency BasicValues::GetModalSpecAmtGLP(currency annualized_pmt) const
 {
     mcenum_dbopt_7702 const z = effective_dbopt_7702(DeathBfts_->dbopt()[0], 
Effective7702DboRop);
-    return gpt_specamt::CalculateGLPSpecAmt(*this, 0, annualized_pmt, z);
+    return currency(gpt_specamt::CalculateGLPSpecAmt(*this, 0, annualized_pmt, 
z));
 }
 
 //============================================================================
-double BasicValues::GetModalSpecAmtGSP(double annualized_pmt) const
+currency BasicValues::GetModalSpecAmtGSP(currency annualized_pmt) const
 {
-    return gpt_specamt::CalculateGSPSpecAmt(*this, 0, annualized_pmt);
+    return currency(gpt_specamt::CalculateGSPSpecAmt(*this, 0, 
annualized_pmt));
 }
 
 /// Calculate specified amount using a corridor ratio.
@@ -1486,12 +1504,12 @@ double BasicValues::GetModalSpecAmtGSP(double 
annualized_pmt) const
 /// integral cents, this implementation cannot guarantee to give the
 /// desired answer in every case.
 
-double BasicValues::GetModalSpecAmtCorridor(double annualized_pmt) const
+currency BasicValues::GetModalSpecAmtCorridor(currency annualized_pmt) const
 {
     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);
+    return currency(round_min_specamt()((z * annualized_pmt) / s));
 }
 
 /// Calculate specified amount based on salary.
@@ -1501,7 +1519,7 @@ double BasicValues::GetModalSpecAmtCorridor(double 
annualized_pmt) const
 /// sufficiently large, then specamt would be negative, which cannot
 /// make any sense.
 
-double BasicValues::GetModalSpecAmtSalary(int a_year) const
+currency BasicValues::GetModalSpecAmtSalary(int a_year) const
 {
     double z =
           yare_input_.ProjectedSalary[a_year]
@@ -1512,7 +1530,7 @@ double BasicValues::GetModalSpecAmtSalary(int a_year) 
const
         z = std::min(z, yare_input_.SalarySpecifiedAmountCap);
         }
     z -= yare_input_.SalarySpecifiedAmountOffset;
-    return round_min_specamt()(std::max(0.0, z));
+    return currency(round_min_specamt()(std::max(0.0, z)));
 }
 
 /// In general, strategies linking specamt and premium commute. The
@@ -1522,13 +1540,13 @@ double BasicValues::GetModalSpecAmtSalary(int a_year) 
const
 /// calling this function elicits an error message. SOMEDAY !! It
 /// would be better to disable this strategy in the GUI.
 
-double BasicValues::GetModalSpecAmtMlyDed(double, mcenum_mode) const
+currency BasicValues::GetModalSpecAmtMlyDed(currency, mcenum_mode) const
 {
     alarum()
         << "No maximum specified amount is defined for this product."
         << LMI_FLUSH
         ;
-    return 0.0;
+    return currency(0.0);
 }
 
 /// 'Unusual' banding is one particular approach we needed to model.
@@ -1539,7 +1557,7 @@ double BasicValues::GetModalSpecAmtMlyDed(double, 
mcenum_mode) const
 
 std::vector<double> const& BasicValues::GetBandedCoiRates
     (mcenum_gen_basis rate_basis
-    ,double           a_specamt
+    ,currency         a_specamt
     ) const
 {
     if(UseUnusualCOIBanding && mce_gen_guar != rate_basis)
diff --git a/ledger_invariant_init.cpp b/ledger_invariant_init.cpp
index 289283b..a53b7bb 100644
--- a/ledger_invariant_init.cpp
+++ b/ledger_invariant_init.cpp
@@ -101,7 +101,7 @@ void LedgerInvariant::Init(BasicValues const* b)
         }
     else if(b->database().query<bool>(DB_TermIsNotRider))
         {
-        TermSpecAmt            = b->DeathBfts_->supplamt();
+        TermSpecAmt            = doubleize(b->DeathBfts_->supplamt());
         if(!each_equal(TermSpecAmt, 0.0))
             {
             HasSupplSpecAmt    = true;
@@ -111,7 +111,7 @@ void LedgerInvariant::Init(BasicValues const* b)
         {
         TermSpecAmt            .assign(Length, 0.0);
         }
-    SpecAmt                    = b->DeathBfts_->specamt();
+    SpecAmt                    = doubleize(b->DeathBfts_->specamt());
 
     // Forborne vectors.
 
@@ -675,8 +675,8 @@ void LedgerInvariant::Init(BasicValues const* b)
 // premium-strategy calculations. Use E[er]GrossPmt for illustrations:
 // they're *output* values that result from transaction processing.
 
-    EePmt                      = b->Outlay_->ee_modal_premiums();
-    ErPmt                      = b->Outlay_->er_modal_premiums();
+    EePmt                      = doubleize(b->Outlay_->ee_modal_premiums());
+    ErPmt                      = doubleize(b->Outlay_->er_modal_premiums());
 
     // Special-case strings.
 
diff --git a/outlay.cpp b/outlay.cpp
index 66e32e3..e3974f9 100644
--- a/outlay.cpp
+++ b/outlay.cpp
@@ -32,37 +32,37 @@ modal_outlay::modal_outlay(yare_input const& yi)
     :dumpin_               {yi.Dumpin}
     ,external_1035_amount_ {yi.External1035ExchangeAmount}
     ,internal_1035_amount_ {yi.Internal1035ExchangeAmount}
-    ,ee_modal_premiums_    {yi.Payment}
+    ,ee_modal_premiums_    (currencyize(yi.Payment))
     ,ee_premium_modes_     {yi.PaymentMode}
-    ,er_modal_premiums_    {yi.CorporationPayment}
+    ,er_modal_premiums_    (currencyize(yi.CorporationPayment))
     ,er_premium_modes_     {yi.CorporationPaymentMode}
-    ,new_cash_loans_       {yi.NewLoan}
-    ,withdrawals_          {yi.Withdrawal}
+    ,new_cash_loans_       (currencyize(yi.NewLoan))
+    ,withdrawals_          (currencyize(yi.Withdrawal))
 {
 }
 
-void modal_outlay::set_ee_modal_premiums(double z, int from_year, int to_year)
+void modal_outlay::set_ee_modal_premiums(currency z, int from_year, int 
to_year)
 {
     std::fill_n(ee_modal_premiums_.begin() + from_year, to_year - from_year, 
z);
 }
 
-void modal_outlay::set_er_modal_premiums(double z, int from_year, int to_year)
+void modal_outlay::set_er_modal_premiums(currency z, int from_year, int 
to_year)
 {
     std::fill_n(er_modal_premiums_.begin() + from_year, to_year - from_year, 
z);
 }
 
-void modal_outlay::set_er_modal_premiums(std::vector<double> const& z)
+void modal_outlay::set_er_modal_premiums(std::vector<currency> const& z)
 {
     LMI_ASSERT(z.size() == er_modal_premiums_.size());
     er_modal_premiums_ = z;
 }
 
-void modal_outlay::set_new_cash_loans(double z, int from_year, int to_year)
+void modal_outlay::set_new_cash_loans(currency z, int from_year, int to_year)
 {
     std::fill_n(new_cash_loans_.begin() + from_year, to_year - from_year, z);
 }
 
-void modal_outlay::set_withdrawals(double z, int from_year, int to_year)
+void modal_outlay::set_withdrawals(currency z, int from_year, int to_year)
 {
     std::fill_n(withdrawals_.begin() + from_year, to_year - from_year, z);
 }
diff --git a/solve.cpp b/solve.cpp
index 8b27ee1..e941e0e 100644
--- a/solve.cpp
+++ b/solve.cpp
@@ -153,36 +153,36 @@ double SolveTest()
 inline static double SolveSpecAmt(double CandidateValue)
 {
 // IHS !! Change surrchg when SA changes?
-    That->SolveSetSpecAmt(CandidateValue, ThatSolveBegYear, ThatSolveEndYear);
+    That->SolveSetSpecAmt(currency(CandidateValue), ThatSolveBegYear, 
ThatSolveEndYear);
     return only_set_values ? 0.0 : SolveTest();
 }
 
 //============================================================================
 inline static double SolvePrem(double CandidateValue)
 {
-    That->SolveSetPmts(CandidateValue, ThatSolveBegYear, ThatSolveEndYear);
+    That->SolveSetPmts(currency(CandidateValue), ThatSolveBegYear, 
ThatSolveEndYear);
     return only_set_values ? 0.0 : SolveTest();
 }
 
 //============================================================================
 inline static double SolveLoan(double CandidateValue)
 {
-    That->SolveSetLoans(CandidateValue, ThatSolveBegYear, ThatSolveEndYear);
+    That->SolveSetLoans(currency(CandidateValue), ThatSolveBegYear, 
ThatSolveEndYear);
     return only_set_values ? 0.0 : SolveTest();
 }
 
 //============================================================================
 inline static double SolveWD(double CandidateValue)
 {
-    That->SolveSetWDs(CandidateValue, ThatSolveBegYear, ThatSolveEndYear);
+    That->SolveSetWDs(currency(CandidateValue), ThatSolveBegYear, 
ThatSolveEndYear);
     return only_set_values ? 0.0 : SolveTest();
 }
 
 //============================================================================
 void AccountValue::SolveSetPmts
-    (double a_Pmt
-    ,int    ThatSolveBegYear
-    ,int    ThatSolveEndYear
+    (currency a_Pmt
+    ,int      ThatSolveBegYear
+    ,int      ThatSolveEndYear
     )
 {
     Outlay_->set_ee_modal_premiums(a_Pmt, ThatSolveBegYear, ThatSolveEndYear);
@@ -190,9 +190,9 @@ void AccountValue::SolveSetPmts
 
 //============================================================================
 void AccountValue::SolveSetSpecAmt
-    (double a_Bft
-    ,int    ThatSolveBegYear
-    ,int    ThatSolveEndYear
+    (currency a_Bft
+    ,int      ThatSolveBegYear
+    ,int      ThatSolveEndYear
     )
 {
     DeathBfts_->set_specamt(a_Bft, ThatSolveBegYear, ThatSolveEndYear);
@@ -200,9 +200,9 @@ void AccountValue::SolveSetSpecAmt
 
 //============================================================================
 void AccountValue::SolveSetLoans
-    (double a_Loan
-    ,int    ThatSolveBegYear
-    ,int    ThatSolveEndYear
+    (currency a_Loan
+    ,int      ThatSolveBegYear
+    ,int      ThatSolveEndYear
     )
 {
     Outlay_->set_new_cash_loans(a_Loan, ThatSolveBegYear, ThatSolveEndYear);
@@ -210,9 +210,9 @@ void AccountValue::SolveSetLoans
 
 //============================================================================
 void AccountValue::SolveSetWDs
-    (double a_WD
-    ,int    ThatSolveBegYear
-    ,int    ThatSolveEndYear
+    (currency a_WD
+    ,int      ThatSolveBegYear
+    ,int      ThatSolveEndYear
     )
 {
     Outlay_->set_withdrawals(a_WD, ThatSolveBegYear, ThatSolveEndYear);
@@ -220,9 +220,9 @@ void AccountValue::SolveSetWDs
 
 //============================================================================
 void AccountValue::SolveSetLoanThenWD
-    (double // Amt
-    ,int    // ThatSolveBegYear
-    ,int    // ThatSolveEndYear
+    (currency // Amt
+    ,int      // ThatSolveBegYear
+    ,int      // ThatSolveEndYear
     )
 {
     // IHS !! Implemented in lmi.
@@ -348,6 +348,6 @@ double AccountValue::Solve()
     only_set_values = !Solving;
     double actual_solution = Solution.first;
 
-    SolveFn(actual_solution);
+    SolveFn(currency(actual_solution));
     return actual_solution;
 }



reply via email to

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