lmi-commits
[Top][All Lists]
Advanced

[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{});



reply via email to

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