lmi-commits
[Top][All Lists]
Advanced

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

[lmi-commits] [lmi] valyuta/005 4902da4 01/17: Reinvent currency ab ovo


From: Greg Chicares
Subject: [lmi-commits] [lmi] valyuta/005 4902da4 01/17: Reinvent currency ab ovo
Date: Sat, 16 Jan 2021 21:06:16 -0500 (EST)

branch: valyuta/005
commit 4902da4ed67f6b185ce7e4491fda39fb5918c9b1
Author: Gregory W. Chicares <gchicares@sbcglobal.net>
Commit: Gregory W. Chicares <gchicares@sbcglobal.net>

    Reinvent currency ab ovo
    
    Unit tests pass, but lmi doesn't yet build.
---
 currency.hpp      | 242 ++++++++----------------------------------------------
 currency_test.cpp |  49 ++++++++---
 round_to.hpp      |  76 ++++-------------
 3 files changed, 82 insertions(+), 285 deletions(-)

diff --git a/currency.hpp b/currency.hpp
index 753c732..9bc8df5 100644
--- a/currency.hpp
+++ b/currency.hpp
@@ -24,237 +24,59 @@
 
 #include "config.hpp"
 
-#include "assert_lmi.hpp"
-#include "bourn_cast.hpp"
-////#include "round_to.hpp"
+#include "so_attributes.hpp"
 
-#include <cfenv>                        // fesetround()
-#include <cmath>                        // nearbyint()
-#include <cstdint>                      // int64_t
-#include <iostream>                     // ostream
-#include <vector>
+#include <ostream>
 
-#define CURRENCY_UNIT_IS_CENTS
+class raw_cents {};
 
-//#define CURRENCY_HAS_INTEGER_DATATYPE
-
-#if !defined CURRENCY_UNIT_IS_CENTS && defined CURRENCY_HAS_INTEGER_DATATYPE
-#   error No can do!
-#endif // !defined CURRENCY_UNIT_IS_CENTS && defined 
CURRENCY_HAS_INTEGER_DATATYPE
-
-// Similar restrictions apply to these macros, but they'd be tedious
-// to write. Use them only with floating-point dollars.
-//#define MAKE_IT_FASTER
-//#define MAKE_IT_EVEN_FASTER
-
-#if !defined CURRENCY_HAS_INTEGER_DATATYPE
-#   if defined __GNUC__
-#       pragma GCC diagnostic push
-#       pragma GCC diagnostic ignored "-Wuseless-cast"
-#   endif // defined __GNUC__
-#endif // !CURRENCY_HAS_INTEGER_DATATYPE
-
-class raw_cents {}; // Tag class.
-
-class currency
+class LMI_SO currency
 {
-    friend std::ostream& operator<<(std::ostream&, currency const&);
     friend class currency_test;
-    template<typename T> friend class round_to;
     friend class round_to_test;
-
-#if defined CURRENCY_UNIT_IS_CENTS
-    static constexpr int cents_digits = 2;
-    static constexpr double cents_per_dollar = 100.0; // 10 ^ cents_digits
-#else  // !defined CURRENCY_UNIT_IS_CENTS
-    static constexpr int cents_digits = 0;
-    static constexpr double cents_per_dollar =   1.0; // 10 ^ cents_digits
-#endif // !defined CURRENCY_UNIT_IS_CENTS
+    template<typename T> friend class round_to;
+    using data_type=double;
+#if 1
+    static constexpr int cents_digits     = 2;
+    static constexpr int cents_per_dollar = 100;
+#endif // 1
 
   public:
-#if defined CURRENCY_HAS_INTEGER_DATATYPE
-    using data_type = std::int64_t;
-#else  // !defined CURRENCY_HAS_INTEGER_DATATYPE
-//  using data_type = long double;
-    using data_type = double;
-#endif // !defined CURRENCY_HAS_INTEGER_DATATYPE
-
-    currency() = default;
-    currency(currency const&) = default;
-    ~currency() = default;
-
-    // The extra, ignored argument ensures that this special-purpose
-    // ctor ambiguates no other. (But that doesn't matter now that
-    // there is no other non-default ctor.)
-    //
-    // CURRENCY !! Probably the first argument should be asserted to
-    // have an exact integral value.
-    explicit currency(data_type c, raw_cents) : m_ {c} {}
-// Converting ctors are preferably avoided:
-#if 0 // Apparently unused.
-    explicit currency(int       i)            : m_ {from_int   (i)} {}
-#endif // 0
-// The underlying value 'm_' is intended to have an exact integral
-// value. A naive conversion from (e.g.) 3.14 would violate that
-// eminently desirable postcondition; a more complicated one would
-// either require cumbersome extra arguments to specify rounding, or
-// use some built-in rounding rule that would be inappropriate in
-// some circumstance. It seems better to avoid a converting ctor.
-//
-// Conversion from (e.g.) 3.5 would yield exactly 350 cents,
-// satisfying the integral postcondition, but would require a runtime
-// test that in practice would succeed so seldom that the idea is not
-// worth pursuing.
-//  explicit currency(double    d)            : m_ {from_double(d)} {}
-
-    currency& operator=(currency const&) = default;
+    currency() : m_ {0} {}
+    explicit currency(data_type z, raw_cents) :m_ {z} {}
 
-    double d() const {return to_double();}
-
-    // Is this the ideal signature for this operator?
-    currency operator-() const {return currency(-m_, raw_cents{});}
-
-    currency& operator+=(currency z) {m_ += z.m_; return *this;}
-    currency& operator-=(currency z) {m_ -= z.m_; return *this;}
-    // There can be no operator*() that returns a result in dollars^2.
-
-    // Mixed-mode arithmetic is generally to be avoided, but it is
-    // safe to multiply currency by an integer such as twelve:
-    // $1 monthly really does equal $12 annually. No operator/(int)
-    // is provided because $1 annually doesn't equal any integral
-    // number of cents per month.
-    //
-    // Ignore the possibility of overflow, at least for now:
-    //  - multiplying by an enormous integer would be unreasonable;
-    //  - C++ can't detect integer overflow very well anyway; and
-    // ultimately this class might use a floating data_type, which is
-    // highly unlikely to overflow on multiplication by an int.
-    currency const& operator*=(int z) {m_ *= z; return *this;}
+    currency& operator+=(currency const& z) {m_ += z.m_; return *this;}
+// !! no--mutates (negates) *this
+//  currency const& operator-() {m_ = -m_; return *this;}
+    currency const operator-() const {return currency(-cents(), raw_cents {});}
 
     data_type cents() const {return m_;}
+    // !! possible underflow?
+    double d() const {return m_ / cents_per_dollar;}
 
   private:
-#if 0 // Apparently unused.
-    data_type from_int(int i) const
-        {return cents_per_dollar * bourn_cast<data_type>(i);}
-#endif // 0
-    // Want something just slightly more permissive:
-//  data_type from_double(double d) const {return bourn_cast<data_type>(100.0 
* d);}
-    // Far too permissive:
-//  data_type from_double(double d) const {return static_cast<data_type>(100.0 
* d);}
-    // ...and a bit insidious:
-//  data_type from_double(double d) const {return 
static_cast<data_type>(100.000000000001 * d);}
-    // ...less bad:
-//  data_type from_double(double d) const {return 
bourn_cast<data_type>(round(cents_per_dollar * d));}
-#if !defined MAKE_IT_EVEN_FASTER
-#   if defined CURRENCY_HAS_INTEGER_DATATYPE
-    // Convert double <-> integer: prefer explicit rounding to
-    // implicit truncation.
-    data_type from_double(double d) const {return round(cents_per_dollar * d);}
-    // Here, bourn_cast actually does something:
-    double to_double() const {return bourn_cast<double>(m_) / 
cents_per_dollar;}
-#   else  // !defined CURRENCY_HAS_INTEGER_DATATYPE
-    // Converting double <-> double involves no implicit change.
-    // Certainly 1.23 * 100.0 is unlikely to equal 123 exactly, but
-    // that's why this conversion really should be avoided: instead
-    // of calling some arbitrary rounding function here, something
-    // suitable for the context should be used, e.g.:
-    //   double d = loan_value()
-    //   currency c = round_loan_.c(d);
-//  data_type from_double(double d) const {return       cents_per_dollar * d ;}
-    data_type from_double(double d) const
-        {
-//      LMI_ASSERT(cents_per_dollar * d == round(cents_per_dollar * d));
-        return cents_per_dollar * d;
-        }
-    // Here, bourn_cast costs something, but does nothing:
-//  double to_double() const {return bourn_cast<double>(m_) / 
cents_per_dollar;}
-    double to_double() const {return static_cast<double>(m_) / 
cents_per_dollar;}
-#   endif // !defined CURRENCY_HAS_INTEGER_DATATYPE
-#else  // defined MAKE_IT_EVEN_FASTER
-    data_type from_double(double d) const {return d;}
-    double to_double() const {return m_;}
-#endif // defined MAKE_IT_EVEN_FASTER
-#if 0 // will a fwd decl be wanted somewhere?
-    data_type round(double d) const
-        {
-        static round_to<double> const r(0, r_to_nearest);
-        return static_cast<data_type>(r(d));
-        }
-#else // 1
-    data_type round(double d) const
-        {
-#if !defined MAKE_IT_FASTER
-        std::fesetround(FE_TONEAREST);
-        return bourn_cast<data_type>(std::nearbyint(d));
-#else  // defined MAKE_IT_FASTER
-        static_assert(std::is_floating_point<data_type>::value);
-        return d;
-#endif // defined MAKE_IT_FASTER
-        }
-#endif // 1
-    data_type m_ = {0};
+    data_type m_;
 };
 
-inline bool operator==(currency const& lhs, currency const& rhs)
-    {return lhs.cents() == rhs.cents();}
-inline bool operator< (currency const& lhs, currency const& rhs)
-    {return lhs.cents() <  rhs.cents();}
-inline bool operator!=(currency const& lhs, currency const& rhs)
-    {return !operator==(lhs,rhs);}
-inline bool operator> (currency const& lhs, currency const& rhs)
-    {return  operator< (rhs,lhs);}
-inline bool operator<=(currency const& lhs, currency const& rhs)
-    {return !operator> (lhs,rhs);}
-inline bool operator>=(currency const& lhs, currency const& rhs)
-    {return !operator< (lhs,rhs);}
-
-inline currency operator+(currency lhs, currency rhs) {return lhs += rhs;}
-inline currency operator-(currency lhs, currency rhs) {return lhs -= rhs;}
-
-inline currency operator*(currency lhs, int    rhs) {return lhs *= rhs;}
-inline currency operator*(int    lhs, currency rhs) {return rhs *= lhs;}
-
-// There is deliberately no operator*=(double) or operator/=(double).
-// These operators are safe because they return double. Yet because
-// they are implicit, they hide conversions. At least the conversions
-// they hide are the comparatively inexpensive and value-preserving
-// to_double() instead of the expensive from_double() that does not
-// preserve value. Probably either analogous additive operators should
-// be provided, or these should not be provided.
-inline double operator*(currency lhs, double rhs) {return lhs.d() *  rhs;}
-inline double operator*(double lhs, currency rhs) {return lhs *  rhs.d();}
-inline double operator/(currency lhs, double rhs) {return lhs.d() /  rhs;}
-inline double operator/(double lhs, currency rhs) {return lhs /  rhs.d();}
-
-inline std::ostream& operator<<(std::ostream& os, currency const& c)
+bool operator==(currency const& lhs, currency const& rhs)
 {
-    return os << c.to_double();
+    return lhs.cents() == rhs.cents();
 }
 
-inline std::vector<double> doubleize(std::vector<currency> const& z)
+currency const operator+(currency const& lhs, currency const& rhs)
 {
-    std::vector<double> r;
-    r.reserve(z.size());
-    for(auto const& i : z)
-        r.push_back(i.d());
-    return r;
+    return currency {lhs} += rhs;
 }
 
-/// Zero cents--akin to a user-defined literal.
-///
-/// UDLs seem less convenient because the obvious "0_c" is likely to
-/// collide with some other UDL, and "currency::0_c" is too verbose.
-/// "0_cents" may avoid both those problems, but "C0" is terser.
-/// "C0" is chosen instead of "c0" only for the pixilated reason that
-/// the capital letter looks kind of like a "0".
-
-inline constexpr currency C0 = {};
+currency const operator-(currency const& lhs, currency const& rhs)
+{
+    return currency {lhs} += -currency {rhs};
+}
 
-#if !defined CURRENCY_HAS_INTEGER_DATATYPE
-#   if defined __GNUC__
-#       pragma GCC diagnostic pop
-#   endif // defined __GNUC__
-#endif // !CURRENCY_HAS_INTEGER_DATATYPE
+static inline currency const C0 {{}, raw_cents {}};
 
+std::ostream& operator<<(std::ostream& os, currency const& z)
+{
+    os << z.d(); return os;
+}
 #endif // currency_hpp
diff --git a/currency_test.cpp b/currency_test.cpp
index 52ec786..8484530 100644
--- a/currency_test.cpp
+++ b/currency_test.cpp
@@ -49,33 +49,54 @@ void currency_test::test()
 
 void currency_test::test_something()
 {
+    // default ctor
     currency const a0;
     std::cout << a0 << std::endl;
     BOOST_TEST(0.00 == a0.d());
     BOOST_TEST(   0 == a0.m_);
+    // operator==()
+    BOOST_TEST(  C0 == a0);
 
 // Figure out what to do about this:
 //  currency a1(3.14);
-
 // It seems best to provide no converting ctor:
 //  currency a1(3.25);
+
+    // explicit ctor
     currency a1(325, raw_cents{});
-    BOOST_TEST(3.25 == a1.d());
-    BOOST_TEST( 325 == a1.m_);
-    a1 += a1;
-    BOOST_TEST(6.50 == a1.d());
-    BOOST_TEST( 650 == a1.m_);
+    BOOST_TEST_EQUAL(3.25, a1.d());
+    BOOST_TEST_EQUAL( 325, a1.m_);
 
-    currency a2 = currency() - a1;
-    BOOST_TEST(-6.50 == a2.d());
-    BOOST_TEST( -650 == a2.m_);
+    // operator+=()
+    a1 += a1;
+    BOOST_TEST_EQUAL(6.50, a1.d());
+    BOOST_TEST_EQUAL( 650, a1.m_);
+
+    // unary operator-()
+    -a1;
+    BOOST_TEST_EQUAL(6.50, a1.d());
+    BOOST_TEST_EQUAL( 650, a1.m_);
+    BOOST_TEST_EQUAL(-6.50, (-a1).d());
+    BOOST_TEST_EQUAL( -650, (-a1).m_);
+
+    // binary operator+()
+    currency a2 = currency() + a1 + a1;
+    BOOST_TEST_EQUAL(13.00, a2.d());
+    BOOST_TEST_EQUAL( 1300, a2.m_);
+
+    // binary operator-()
+    a2 = currency() - a1;
+    BOOST_TEST_EQUAL(-6.50, a2.d());
+    BOOST_TEST_EQUAL( -650, a2.m_);
     a2 = C0 - a1;
-    BOOST_TEST(-6.50 == a2.d());
-    BOOST_TEST( -650 == a2.m_);
+    BOOST_TEST_EQUAL(-6.50, a2.d());
+    BOOST_TEST_EQUAL( -650, a2.m_);
+    // unary operator-() [bis]
     a2 = -a1;
-    BOOST_TEST(-6.50 == a2.d());
-    BOOST_TEST( -650 == a2.m_);
+    BOOST_TEST_EQUAL(-6.50, a2.d());
+    BOOST_TEST_EQUAL( -650, a2.m_);
 
+    // round_to<>.c()
     double d0 = 123.99999999999;
     currency c0 = round_to_cents.c(d0);
 std::cout << c0 << " converted from 123.999..." << std::endl;
@@ -86,6 +107,7 @@ std::cout << c1 << " converted from 1.0 + epsilon..." << 
std::endl;
     currency c2 = round_to_cents.c(d2);
 std::cout << c2 << " converted from 1.0 - epsilon..." << std::endl;
 
+    // overflow?
     double big_num = 1.0e100;
 #pragma GCC diagnostic ignored "-Wfloat-conversion"
     currency::data_type big_int0 = big_num;
@@ -135,6 +157,7 @@ std::cout << "int4: " << big_int4 << std::endl;
         );
 #endif // !defined CURRENCY_HAS_INTEGER_DATATYPE
 
+    // quodlibet
     currency b0 = round_to_cents.c(464.180000000000006821);
     currency b1 = round_to_cents.c(263.01999999999998181);
     currency b2 = round_to_cents.c(0.0);
diff --git a/round_to.hpp b/round_to.hpp
index 6cfba45..66b1d62 100644
--- a/round_to.hpp
+++ b/round_to.hpp
@@ -24,8 +24,6 @@
 
 #include "config.hpp"
 
-#include "assert_lmi.hpp"
-#include "bourn_cast.hpp"
 #include "currency.hpp"
 #include "mc_enum_type_enums.hpp"       // enum rounding_style
 #include "stl_extensions.hpp"           // nonstd::power()
@@ -272,10 +270,6 @@ class round_to
     currency c(RealType r) const;
     std::vector<currency> c(std::vector<RealType> r) const;
 
-    currency c(currency z) const;
-// is this wanted?
-//  std::vector<currency> c(std::vector<RealType> r) const;
-
     int decimals() const;
     rounding_style style() const;
 
@@ -286,9 +280,10 @@ class round_to
     int decimals_                    {0};
     rounding_style style_            {r_indeterminate};
     max_prec_real scale_fwd_         {1.0};
-    max_prec_real scale_fwd_c_       {1.0};
     max_prec_real scale_back_        {1.0};
-    max_prec_real scale_back_c_      {1.0};
+    int decimals_cents_              {0};
+    max_prec_real scale_fwd_cents_   {1.0};
+    max_prec_real scale_back_cents_  {1.0};
     rounding_fn_t rounding_function_ {detail::erroneous_rounding_function};
 };
 
@@ -320,10 +315,11 @@ template<typename RealType>
 round_to<RealType>::round_to(int decimals, rounding_style a_style)
     :decimals_          {decimals}
     ,style_             {a_style}
-    ,scale_fwd_         {detail::perform_pow(max_prec_real(10.0), decimals)}
-    ,scale_fwd_c_       {detail::perform_pow(max_prec_real(10.0), decimals - 
currency::cents_digits)}
+    ,scale_fwd_         {detail::perform_pow(max_prec_real(10.0), decimals_)}
     ,scale_back_        {max_prec_real(1.0) / scale_fwd_}
-    ,scale_back_c_      {max_prec_real(1.0) / scale_fwd_c_}
+    ,decimals_cents_    {decimals - currency::cents_digits}
+    ,scale_fwd_cents_   {detail::perform_pow(max_prec_real(10.0), 
decimals_cents_)}
+    ,scale_back_cents_  {max_prec_real(1.0) / scale_fwd_cents_}
     ,rounding_function_ {select_rounding_function(a_style)}
 {
 /*
@@ -384,65 +380,21 @@ inline std::vector<RealType> 
round_to<RealType>::operator()(std::vector<RealType
 }
 
 template<typename RealType>
-inline currency round_to<RealType>::c(RealType r) const
+currency round_to<RealType>::c(RealType r) const
 {
-    // ROUNDING move this restriction to rounding_rules
-    // enabling it here imposes a three-percent overhead
-//  LMI_ASSERT(decimals() <= 2);
-#if defined CURRENCY_HAS_INTEGER_DATATYPE
-// This conditional didn't have zero overhead with 'double';
-// perhaps 'if constexpr' would have worked, but the preprocessor
-// certainly does:
-//  if(std::numeric_limits<currency::data_type>::is_integer)
-        {
-        // Precondition for casting to integer below:
-        LMI_ASSERT(decimals() <= currency::cents_digits);
-        }
-#endif // defined CURRENCY_HAS_INTEGER_DATATYPE
-    RealType z = static_cast<RealType>
-        (rounding_function_(static_cast<RealType>(r * scale_fwd_)) * 
scale_back_c_
+    RealType const z = static_cast<RealType>
+        (rounding_function_(static_cast<RealType>(r * scale_fwd_)) * 
scale_back_cents_
         );
-// Writing 'bourn_cast' here instead of 'static_cast' slows lmi's
-// CLI '--self_test' down by five percent.
-//  return currency(bourn_cast<currency::data_type>(z), raw_cents{});
-    return currency(static_cast<currency::data_type>(z), raw_cents{});
-#if 0
-    // don't do this in production:
-    if(z != std::trunc(z))
-        warning()
-            << z << " does not equal\n"
-            << trunc(z) << "\n"
-            << LMI_FLUSH
-            ;
-    currency k(bourn_cast<currency::data_type>(z), raw_cents);
-    if(k.m() != bourn_cast<currency::data_type>(std::trunc(k.m())))
-        warning()
-            << k.m() << " does not equal\n"
-            << trunc(bourn_cast<double>(k.m())) << "\n"
-            << LMI_FLUSH
-            ;
-    return k;
-#endif // 0
+    // !! static_cast: possible range error
+    return currency(static_cast<currency::data_type>(z), raw_cents {});
 }
 
 template<typename RealType>
-inline std::vector<currency> round_to<RealType>::c(std::vector<RealType> r) 
const
+std::vector<currency> round_to<RealType>::c(std::vector<RealType> r) const
 {
     std::vector<currency> z;
     z.reserve(r.size());
-    for(auto const& i : r) {z.push_back(c(i));}
-    return z;
-}
-
-template<typename RealType>
-inline currency round_to<RealType>::c(currency z) const
-{
-    LMI_ASSERT(decimals() <= 2); // ROUNDING similarly restrict rounding_rules
-    // instead, this ought to verify proper rounding
-    if(decimals() < 2)
-        {
-        z = c(z.d());
-        }
+    for(auto const& i : r) {z.push_back(c()(i));}
     return z;
 }
 



reply via email to

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