lmi-commits
[Top][All Lists]
Advanced

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

[lmi-commits] (no subject)


From: Greg Chicares
Subject: [lmi-commits] (no subject)
Date: Sat, 18 Jun 2016 22:44:43 +0000 (UTC)

branch: master
commit 6d3def692e2b0c430ba45797655d77c9eec74b68
Author: Gregory W. Chicares <address@hidden>
Date:   Sat Jun 18 18:19:18 2016 +0000

    Replace abstract units and subunits with dollars and cents
---
 currency.hpp      |  196 ++++++++++++++++++++++++++++++-----------------------
 currency_test.cpp |   79 ++++++++++-----------
 2 files changed, 153 insertions(+), 122 deletions(-)

diff --git a/currency.hpp b/currency.hpp
index b547e7a..eb61756 100644
--- a/currency.hpp
+++ b/currency.hpp
@@ -1,4 +1,4 @@
-// Represent an amount in currency units and subunits.
+// Represent a currency amount exactly as integral cents.
 //
 // Copyright (C) 2016 Gregory W. Chicares.
 //
@@ -32,73 +32,81 @@
 #include <limits>
 #include <stdexcept>
 
-/// Represent a monetary amount as an exact number of base units and subunits.
+/// Represent a currency amount exactly as integral cents.
 ///
-/// This class currently assumes that there are 100 subunits in the base unit,
-/// which is not the case for all currencies, but definitely is for USD, which
-/// is the only currency used in lmi.
+/// This class is tailored to US currency, as lmi is tailored to US
+/// life insurance.
 ///
-/// By storing the amount as an integer number of units and subunits internally
-/// this class avoids rounding errors for the operations involving additions
-/// and subtractions. For the multiplicative operations, conversions to and
-/// from floating point type are provided and it is the caller responsibility
-/// to correctly round the final result of a calculation involving such
-/// operations to a currency amount.
+/// By storing the amount as an integer number of cents internally,
+/// this class avoids roundoff error for addition and subtraction.
+/// For multiplicative operations, conversions to and from double
+/// point type are provided; it is the caller's responsibility to
+/// round the final result of such calculations to a currency amount.
 ///
-/// This class provides value-like semantics and has a small size, making it
-/// appropriate to pass it by value instead of more usual const reference.
+/// This class provides value-like semantics and has a small size,
+/// making it appropriate to pass instances by value instead of the
+/// more usual const reference.
 
 class currency
 {
   public:
-    // Using int32_t for the value would limit this class to ~200 million units
-    // which is insufficient, so use 64 bit type which allows to represent
-    // values up to almost 1 quintillion which should be sufficient.
+    /// Using int32_t for the value would limit the range to about
+    /// twenty million dollars, which is insufficient; but int32_t
+    /// accommodates values up to about ninety quadrillion dollars,
+    /// which is enough for any life insurance contract in 2016.
+
     using amount_type = std::int_fast64_t;
 
-    static constexpr int subunits_digits = 2;
-    static constexpr int subunits_per_unit = 100; // std::pow(10, 
subunits_digits)
+    static constexpr int cents_digits = 2;
+    static constexpr int cents_per_dollar = 100; // std::pow(10, cents_digits)
 
-    static constexpr amount_type max_units()
+    static constexpr amount_type max_dollars()
         {
-        return std::numeric_limits<amount_type>::max() / subunits_per_unit;
+        return std::numeric_limits<amount_type>::max() / cents_per_dollar;
         }
 
-    // Default-initialized currency objects is 0.
+    /// No ctor-initializer-list is needed because the lone data
+    /// member has a member-initializer at its point of declaration.
+
     currency()
         {
         }
 
-    // Constructor from a positive number of units and subunits. The subunits
-    // argument must be normalized i.e. positive and strictly less than
-    // subunits_per_unit.
-    currency(amount_type units, int subunits)
+    /// Constructor from a positive number of dollars and cents.
+    ///
+    /// The cents argument must be normalized: i.e., positive and
+    /// strictly less than cents_per_dollar.
+
+    currency(amount_type dollars, int cents)
         {
-        if(units < 0 || units >= max_units())
+        if(dollars < 0 || dollars >= max_dollars())
             {
             throw std::overflow_error("Currency amount out of range.");
             }
 
-        if(subunits < 0 || subunits >= subunits_per_unit)
+        if(cents < 0 || cents >= cents_per_dollar)
             {
-            throw std::runtime_error("Invalid amount of currency subunits.");
+            throw std::runtime_error("Invalid number of cents.");
             }
 
-        subunits_ = subunits_per_unit*units + subunits;
+        cents_ = cents_per_dollar * dollars + cents;
         }
 
-    // Static constructor from the fractional amount of units rounding them to
-    // the nearest subunit. The argument may be positive or negative.
+    /// Static constructor from floating-point dollars.
+    ///
+    /// The argument may be positive or negative. Its value is rounded
+    /// to the nearest cent.
+
     static currency from_value(double d)
         {
-        if(std::trunc(d) >= static_cast<double>(max_units()))
+        if(std::trunc(d) >= static_cast<double>(max_dollars()))
             {
             throw std::overflow_error("Currency amount out of range.");
             }
 
-        // The check above ensures that the product fits into amount_type.
+        // The check above ensures that the value fits in amount_type.
         return currency
-            (static_cast<amount_type>(std::round(subunits_per_unit*d))
+            (static_cast<amount_type>(std::round(cents_per_dollar * d))
             );
         }
 
@@ -108,74 +116,83 @@ class currency
 
     // Accessors.
 
-    // The number of units may be negative.
-    amount_type units() const
+    /// Number of whole dollars. May be negative.
+
+    amount_type dollars() const
         {
-        return subunits_ / subunits_per_unit;
+        return cents_ / cents_per_dollar;
         }
-    // The number of subunits may also be negative and will always be if the
-    // number of units is, i.e. -12.34 is (-12) units + (-34) subunits.
-    int subunits() const
+
+    /// Number of whole cents. May be negative.
+    ///
+    /// The number of cents must be negative if the number of dollars
+    /// is negative. The number of cents may be negative if the number
+    /// of dollars is zero. Otherwise the number of cents must be
+    /// nonnegative.
+
+    int cents() const
         {
-        return subunits_ % subunits_per_unit;
+        return cents_ % cents_per_dollar;
         }
 
-    // Total number of subunits, i.e. 123 for 1 unit and 23 subunits.
-    amount_type total_subunits() const
+    /// Total number of cents, e.g., 123 for 1 dollar and 23 cents.
+
+    amount_type total_cents() const
         {
-        return subunits_;
+        return cents_;
         }
 
-    // Value in terms of units, to be used for arithmetic operations not
-    // provided by this class itself.
+    /// Value as floating-point dollars, for mixed-mode arithmetic.
+
     double value() const
         {
-        double result = subunits_;
-        result /= subunits_per_unit;
+        double result = cents_;
+        result /= cents_per_dollar;
         return result;
         }
 
     // Comparisons.
-    bool operator< (currency other) const { return subunits_ <  
other.subunits_; }
-    bool operator<=(currency other) const { return subunits_ <= 
other.subunits_; }
-    bool operator==(currency other) const { return subunits_ == 
other.subunits_; }
-    bool operator!=(currency other) const { return subunits_ != 
other.subunits_; }
-    bool operator> (currency other) const { return subunits_ >  
other.subunits_; }
-    bool operator>=(currency other) const { return subunits_ >= 
other.subunits_; }
+    bool operator< (currency other) const { return cents_ <  other.cents_; }
+    bool operator<=(currency other) const { return cents_ <= other.cents_; }
+    bool operator==(currency other) const { return cents_ == other.cents_; }
+    bool operator!=(currency other) const { return cents_ != other.cents_; }
+    bool operator> (currency other) const { return cents_ >  other.cents_; }
+    bool operator>=(currency other) const { return cents_ >= other.cents_; }
 
     // Arithmetic operations.
     currency operator-() const
         {
-        return currency(-subunits_);
+        return currency(-cents_);
         }
 
     currency& operator+=(currency other)
         {
-        subunits_ += other.subunits_;
+        cents_ += other.cents_;
         return *this;
         }
 
     currency& operator-=(currency other)
         {
-        subunits_ -= other.subunits_;
+        cents_ -= other.cents_;
         return *this;
         }
 
     currency& operator*=(int factor)
         {
-        subunits_ *= factor;
+        cents_ *= factor;
         return *this;
         }
 
   private:
-    // This ctor is only used internally, it is too error-prone to expose it
-    // publicly.
-    explicit currency(amount_type subunits)
-        :subunits_(subunits)
+    /// This ctor is only used internally: it is too error-prone to
+    /// expose it publicly.
+
+    explicit currency(amount_type cents)
+        :cents_(cents)
         {
         }
 
-    amount_type subunits_ = 0;
+    amount_type cents_ = 0;
 };
 
 inline currency operator+(currency lhs, currency rhs)
@@ -198,43 +215,54 @@ inline currency operator*(int lhs, currency rhs)
     return currency(rhs) *= lhs;
 }
 
+/// Insert the dollars-and-cents amount into a stream.
+///
+/// Dollars and cents, being exact integers, are formatted separately,
+/// but the negative sign cannot be supplied by either of those two
+/// separate formatting operations: $-12.34 must not be inserted as
+/// "-12.-34"; and $-0.56 must be inserted as "-0.56" even though the
+/// whole-dollar amount is not negative.
+///
+/// The decimal mark is hard-coded as '.' because that is universal
+/// US practice.
+
 inline std::ostream& operator<<(std::ostream& os, currency c)
 {
-    // When formatting a negative currency amount, there should be no sign
-    // before the number of subunits: "-12.34" and not "-12.-34". On the other
-    // hand side, we need to output the sign manually for negative amount
-    // because we can't rely on it appearing as part of c.units(): this doesn't
-    // work when units amount is 0.
-    //
-    // Decimal point is currently hard-coded as it is always the appropriate
-    // separator to use for lmi.
-    if(c.total_subunits() < 0)
+    if(c.total_cents() < 0)
         {
         os << '-';
         }
 
     return os
-        << std::abs(c.units())
+        << std::abs(c.dollars())
         << '.'
         << std::setfill('0')
-        << std::setw(currency::subunits_digits)
-        << std::abs(c.subunits());
+        << std::setw(currency::cents_digits)
+        << std::abs(c.cents());
 }
 
+/// Extract a dollars-and-cents amount from a stream.
+///
+/// The negative sign requires special attention so that $-0.56 is not
+/// extracted as -0 dollars plus 56 cents.
+///
+/// The decimal mark is hard-coded as '.' because that is universal
+/// US practice.
+
 inline std::istream& operator>>(std::istream& is, currency& c)
 {
-    // We can't rely on comparing units with 0 as this doesn't work for
-    // "-0.xx", so test for the sign ourselves and skip it if it's there.
     bool const negative = is.peek() == '-';
     if(negative)
         {
         is.get();
         }
 
-    currency::amount_type units = 0;
-    is >> units;
+    currency::amount_type dollars = 0;
+    is >> dollars;
     if(!is)
+        {
         return is;
+        }
 
     if(is.get() != '.')
         {
@@ -242,18 +270,20 @@ inline std::istream& operator>>(std::istream& is, 
currency& c)
         return is;
         }
 
-    int subunits = 0;
-    is >> subunits;
+    int cents = 0;
+    is >> cents;
     if(!is)
+        {
         return is;
+        }
 
-    if(subunits < 0 || subunits >= currency::subunits_per_unit)
+    if(cents < 0 || cents >= currency::cents_per_dollar)
         {
         is.setstate(std::ios_base::failbit);
         return is;
         }
 
-    c = currency(units, subunits);
+    c = currency(dollars, cents);
     if(negative)
         {
         c = -c;
diff --git a/currency_test.cpp b/currency_test.cpp
index 44e099c..2603e8d 100644
--- a/currency_test.cpp
+++ b/currency_test.cpp
@@ -1,4 +1,4 @@
-// Currency amounts--unit test.
+// Represent a currency amount exactly as integral cents--unit test.
 //
 // Copyright (C) 2016 Gregory W. Chicares.
 //
@@ -31,87 +31,88 @@
 
 void test_ctors()
 {
-    BOOST_TEST_EQUAL(currency().total_subunits(), 0);
-    BOOST_TEST_EQUAL(currency(0, 99).total_subunits(), 99);
-    BOOST_TEST_EQUAL(currency(1, 99).total_subunits(), 199);
+    BOOST_TEST_EQUAL(currency(     ).total_cents(),   0);
+    BOOST_TEST_EQUAL(currency(0, 99).total_cents(),  99);
+    BOOST_TEST_EQUAL(currency(1, 99).total_cents(), 199);
 
     currency const c(4, 56);
-    BOOST_TEST_EQUAL(currency(c).total_subunits(), 456);
+    BOOST_TEST_EQUAL(currency(c).total_cents(), 456);
 
-    static char const* const range_error = "Currency amount out of range.";
-    BOOST_TEST_THROW(currency(-1, 0), std::overflow_error, range_error);
-    BOOST_TEST_THROW(currency(-1, 99), std::overflow_error, range_error);
-    BOOST_TEST_THROW(currency(-1, -99), std::overflow_error, range_error);
+    static char const* const overflow_msg = "Currency amount out of range.";
+    BOOST_TEST_THROW(currency(-1,   0), std::overflow_error, overflow_msg);
+    BOOST_TEST_THROW(currency(-1,  99), std::overflow_error, overflow_msg);
+    BOOST_TEST_THROW(currency(-1, -99), std::overflow_error, overflow_msg);
     BOOST_TEST_THROW
         (currency(std::numeric_limits<currency::amount_type>::max(), 0)
         ,std::overflow_error
-        ,range_error
+        ,overflow_msg
         );
     BOOST_TEST_THROW
         (currency(std::numeric_limits<currency::amount_type>::min(), 0)
         ,std::overflow_error
-        ,range_error
+        ,overflow_msg
         );
 
-    static char const* const subunits_error = "Invalid amount of currency 
subunits.";
-    BOOST_TEST_THROW(currency(1, 100), std::runtime_error, subunits_error);
-    BOOST_TEST_THROW(currency(1, 101), std::runtime_error, subunits_error);
-    BOOST_TEST_THROW(currency(1, -1), std::runtime_error, subunits_error);
+    static char const* const cents_msg = "Invalid number of cents.";
+    BOOST_TEST_THROW(currency(1, 100), std::runtime_error, cents_msg);
+    BOOST_TEST_THROW(currency(1, 101), std::runtime_error, cents_msg);
+    BOOST_TEST_THROW(currency(1,  -1), std::runtime_error, cents_msg);
 }
 
 void test_accessors()
 {
     auto c = currency(1234, 56);
-    BOOST_TEST_EQUAL(c.units(), 1234);
-    BOOST_TEST_EQUAL(c.subunits(), 56);
+    BOOST_TEST_EQUAL(c.dollars(), 1234);
+    BOOST_TEST_EQUAL(c.cents()  , 56);
 
     c = -currency(9876543, 21);
-    BOOST_TEST_EQUAL(c.units(), -9876543);
-    BOOST_TEST_EQUAL(c.subunits(), -21);
+    BOOST_TEST_EQUAL(c.dollars(), -9876543);
+    BOOST_TEST_EQUAL(c.cents()  , -21);
 
     c = -currency(0, 99);
-    BOOST_TEST_EQUAL(c.units(), 0);
-    BOOST_TEST_EQUAL(c.subunits(), -99);
+    BOOST_TEST_EQUAL(c.dollars(), 0);
+    BOOST_TEST_EQUAL(c.cents()  , -99);
 
     c = -c;
-    BOOST_TEST_EQUAL(c.units(), 0);
-    BOOST_TEST_EQUAL(c.subunits(), 99);
+    BOOST_TEST_EQUAL(c.dollars(), 0);
+    BOOST_TEST_EQUAL(c.cents()  , 99);
 }
 
 void test_comparison()
 {
-    BOOST_TEST( currency(1, 23) < currency(1, 24) );
-    BOOST_TEST( -currency(1, 23) > -currency(1, 24) );
+    BOOST_TEST( currency(1, 23) <  currency(1, 24));
+    BOOST_TEST(-currency(1, 23) > -currency(1, 24));
 
-    BOOST_TEST( currency(1, 23) <= currency(1, 23) );
-    BOOST_TEST( currency(1, 23) == currency(1, 23) );
-    BOOST_TEST( currency(1, 23) != currency(1, 24) );
-    BOOST_TEST( currency(1, 23) >= currency(1, 23) );
+    BOOST_TEST( currency(1, 23) <= currency(1, 23));
+    BOOST_TEST( currency(1, 23) == currency(1, 23));
+    BOOST_TEST( currency(1, 23) != currency(1, 24));
+    BOOST_TEST( currency(1, 23) >= currency(1, 23));
 }
 
 void test_arithmetic()
 {
     auto c = currency(1, 23) + currency(4, 77);
-    BOOST_TEST_EQUAL(c.total_subunits(), 600);
+    BOOST_TEST_EQUAL(c.total_cents(), 600);
 
     c *= 12;
-    BOOST_TEST_EQUAL(c.total_subunits(), 7200);
+    BOOST_TEST_EQUAL(c.total_cents(), 7200);
 
+    // $72.00 - $80.10 = $8.10
     auto d = c - currency(80, 10);
-    BOOST_TEST_EQUAL(d.total_subunits(), -810);
+    BOOST_TEST_EQUAL(d.total_cents(), -810);
 }
 
 void test_double()
 {
-    BOOST_TEST_EQUAL(currency::from_value(1.23).total_subunits(), 123);
-    BOOST_TEST_EQUAL(currency::from_value(-1.23).total_subunits(), -123);
+    BOOST_TEST_EQUAL(currency::from_value( 1.23).total_cents(),  123);
+    BOOST_TEST_EQUAL(currency::from_value(-1.23).total_cents(), -123);
 
-    BOOST_TEST_EQUAL(currency::from_value(0.005).total_subunits(), 1);
-    BOOST_TEST_EQUAL(currency::from_value(-0.005).total_subunits(), -1);
+    BOOST_TEST_EQUAL(currency::from_value( 0.005).total_cents(),  1);
+    BOOST_TEST_EQUAL(currency::from_value(-0.005).total_cents(), -1);
 
-    auto c = currency::from_value(14857345.859999999404);
-    BOOST_TEST_EQUAL(c.total_subunits() ,1485734586);
-    BOOST_TEST_EQUAL(c.value(), 14857345.86);
+    auto c = currency::from_value(    14857345.859999999404);
+    BOOST_TEST_EQUAL(c.total_cents() ,1485734586);
+    BOOST_TEST_EQUAL(c.value()       ,14857345.86);
 }
 
 void test_stream_roundtrip



reply via email to

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