[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;
}
- [lmi-commits] [lmi] branch valyuta/005 created (now 4401725), Greg Chicares, 2021/01/16
- [lmi-commits] [lmi] valyuta/005 fd74b61 03/17: Correct an obvious mistake, Greg Chicares, 2021/01/16
- [lmi-commits] [lmi] valyuta/005 7d6e748 02/17: Improve currency class and unit tests, Greg Chicares, 2021/01/16
- [lmi-commits] [lmi] valyuta/005 4902da4 01/17: Reinvent currency ab ovo,
Greg Chicares <=
- [lmi-commits] [lmi] valyuta/005 ffa2ce4 09/17: Redesign unit test, Greg Chicares, 2021/01/16
- [lmi-commits] [lmi] valyuta/005 90d7483 10/17: Improve currency class, Greg Chicares, 2021/01/16
- [lmi-commits] [lmi] valyuta/005 3d44f42 13/17: Save a sorted list of regressions, Greg Chicares, 2021/01/16
- [lmi-commits] [lmi] valyuta/005 c8864d3 04/17: Add commented-out relops, Greg Chicares, 2021/01/16
- [lmi-commits] [lmi] valyuta/005 f3219f8 12/17: Use CURRENCY_UNIT_IS_CENTS appropriately, Greg Chicares, 2021/01/16
- [lmi-commits] [lmi] valyuta/005 aad51dd 07/17: Inline more, Greg Chicares, 2021/01/16
- [lmi-commits] [lmi] valyuta/005 b852a68 05/17: Add features, Greg Chicares, 2021/01/16
- [lmi-commits] [lmi] valyuta/005 d69f05c 06/17: Ratify some regression-testing differences, Greg Chicares, 2021/01/16
- [lmi-commits] [lmi] valyuta/005 2aae91a 14/17: Improve incrementally, Greg Chicares, 2021/01/16
- [lmi-commits] [lmi] valyuta/005 0735250 15/17: Improve incrementally, Greg Chicares, 2021/01/16