[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[lmi-commits] [lmi] master cd72f25 1/2: Add and test currency operator""
From: |
Greg Chicares |
Subject: |
[lmi-commits] [lmi] master cd72f25 1/2: Add and test currency operator"" _cents() |
Date: |
Sat, 8 May 2021 13:35:51 -0400 (EDT) |
branch: master
commit cd72f25923973df9efa80a34ea28b224064f815b
Author: Gregory W. Chicares <gchicares@sbcglobal.net>
Commit: Gregory W. Chicares <gchicares@sbcglobal.net>
Add and test currency operator"" _cents()
Made some member functions constexpr so that operator"" could be, too.
Rewrote operator-() to use 'm_' directly, as the other member functions
already did.
Incidentally, referred to the private ctor as 'private' rather than
'explicit' where its privacy matters more than its explicitude.
---
currency.hpp | 25 ++++++++++++++++----
currency_test.cpp | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
2 files changed, 87 insertions(+), 8 deletions(-)
diff --git a/currency.hpp b/currency.hpp
index ce04d75..3f4a6f7 100644
--- a/currency.hpp
+++ b/currency.hpp
@@ -25,8 +25,10 @@
#include "config.hpp"
#include <cmath> // rint()
+#include <limits>
#include <ostream>
#include <stdexcept> // runtime_error
+#include <string> // to_string()
#include <vector>
class raw_cents {}; // Tag class.
@@ -34,9 +36,10 @@ class raw_cents {}; // Tag class.
class currency
{
friend class currency_test;
- friend currency from_cents(double); // explicit ctor
- template<typename> friend class round_to; // explicit ctor
+ friend currency from_cents(double); // private ctor
+ template<typename> friend class round_to; // private ctor
friend class round_to_test; // currency::cents_digits
+ friend constexpr currency operator"" _cents(unsigned long long int);
static constexpr int cents_digits = 2;
static constexpr double cents_per_dollar = 100.0;
@@ -54,19 +57,31 @@ class currency
currency& operator*=(int z) {m_ *= z ; return *this;}
- currency operator-() const {return currency(-cents(), raw_cents {});}
+ constexpr currency operator-() const {return currency(-m_, raw_cents {});}
- data_type cents() const {return m_;}
+ constexpr data_type cents() const {return m_;}
// CURRENCY !! add a unit test for possible underflow
// CURRENCY !! is multiplication by reciprocal faster or more accurate?
double d() const {return m_ / cents_per_dollar;}
private:
- explicit currency(data_type z, raw_cents) : m_ {z} {}
+ constexpr explicit currency(data_type z, raw_cents) : m_ {z} {}
data_type m_ = {};
};
+constexpr currency operator"" _cents(unsigned long long int cents)
+{
+ constexpr auto mant_dig = std::numeric_limits<currency::data_type>::digits;
+ constexpr unsigned long long int limit = 1ULL << mant_dig;
+ return
+ cents <= limit
+ ? currency(static_cast<currency::data_type>(cents), raw_cents{})
+ : throw std::runtime_error
+ ("currency: " + std::to_string(cents) + " out of bounds");
+ ;
+}
+
inline bool operator==(currency lhs, currency rhs)
{return lhs.cents() == rhs.cents();}
inline bool operator< (currency lhs, currency rhs)
diff --git a/currency_test.cpp b/currency_test.cpp
index 95add74..c0f08f2 100644
--- a/currency_test.cpp
+++ b/currency_test.cpp
@@ -44,7 +44,8 @@ class currency_test
private:
static void test_default_ctor();
static void test_copy_ctor();
- static void test_explicit_ctor();
+ static void test_private_ctor();
+ static void test_literals();
static void test_negation();
static void test_plus_or_minus_eq();
static void test_plus_or_minus();
@@ -66,7 +67,8 @@ void currency_test::test()
{
test_default_ctor();
test_copy_ctor();
- test_explicit_ctor();
+ test_private_ctor();
+ test_literals();
test_negation();
test_plus_or_minus_eq();
test_plus_or_minus();
@@ -101,7 +103,7 @@ void currency_test::test_copy_ctor()
LMI_TEST_EQUAL( 325, copy1.m_);
}
-void currency_test::test_explicit_ctor()
+void currency_test::test_private_ctor()
{
currency const a1(325, raw_cents{});
LMI_TEST_EQUAL( 325, a1.m_);
@@ -121,6 +123,68 @@ void currency_test::test_explicit_ctor()
);
}
+void currency_test::test_literals()
+{
+ currency const c0(0_cents);
+ LMI_TEST_EQUAL(0, c0.m_);
+
+ // For an integer argument, these are equivalent:
+ // from_cents(237)
+ // 237_cents
+ // but the latter is terser and faster.
+ currency const a237 = from_cents(237);
+ LMI_TEST_EQUAL( 237, a237.m_);
+ currency const c237( 237_cents);
+ LMI_TEST_EQUAL( 237, c237.m_);
+ LMI_TEST_EQUAL( a237, c237);
+
+ // There is no such thing as a negative literal.
+ // This is the negation of a positive literal.
+ currency const nc237( -237_cents);
+ LMI_TEST_EQUAL( -237, nc237.m_);
+
+ // Separators may make dollars-and-cents literals easier to read:
+ // $-1,234,567.89
+ currency const qc123456789( -1'234'567'89_cents);
+ LMI_TEST_EQUAL( -123456789, qc123456789.m_);
+
+ // C macros such as ULLONG_MAX expand to constant expressions that
+ // are not necessarily literals, so attempting to concatenate "_c"
+ // isn't guaranteed to work. Instead of trying that, just assert
+ // that IEEE754 double-precision arithmetic is used.
+ constexpr auto mant_dig = std::numeric_limits<currency::data_type>::digits;
+ constexpr unsigned long long int limit = 1ULL << mant_dig;
+ LMI_TEST_EQUAL(53, mant_dig);
+ LMI_TEST_EQUAL(9007199254740992, limit);
+
+ // These are okay:
+ currency const c9007199254740992( 9007199254740992_cents);
+ LMI_TEST_EQUAL( 9007199254740992, c9007199254740992.m_);
+ currency const nc9007199254740992( -9007199254740992_cents);
+ LMI_TEST_EQUAL( -9007199254740992, nc9007199254740992.m_);
+
+ // These are run-time errors:
+ LMI_TEST_THROW
+ (9007199254740993_cents
+ ,std::runtime_error
+ ,"currency: 9007199254740993 out of bounds"
+ );
+ // In this error message, the positive literal is invalid, so
+ // an exception is thrown before the return value can be negated.
+ LMI_TEST_THROW
+ (-9007199254740993_cents
+ ,std::runtime_error
+ ,"currency: 9007199254740993 out of bounds"
+ );
+
+ // These are evaluated at compile time:
+ constexpr currency compile_time_constant_pos( 9007199254740992_cents);
+ constexpr currency compile_time_constant_neg(-9007199254740992_cents);
+ // These would be compile-time errors:
+// constexpr currency error_at_compile_time_pos( 9007199254740993_cents);
+// constexpr currency error_at_compile_time_neg(-9007199254740993_cents);
+}
+
void currency_test::test_negation()
{
currency const a1(321, raw_cents{});