lmi-commits
[Top][All Lists]
Advanced

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

[lmi-commits] [lmi] master 306ba4e 02/15: Introduce optional (for now) c


From: Greg Chicares
Subject: [lmi-commits] [lmi] master 306ba4e 02/15: Introduce optional (for now) currency class
Date: Mon, 25 Jan 2021 09:58:04 -0500 (EST)

branch: master
commit 306ba4ed16d811afca4f4e8ec7fc6262ddc88c3c
Author: Gregory W. Chicares <gchicares@sbcglobal.net>
Commit: Gregory W. Chicares <gchicares@sbcglobal.net>

    Introduce optional (for now) currency class
    
    Began importing currencyization from valyuta/005 branch.
    
    Speed seems to have worsened by about a percent, which is surprising
    because the currency class is ifdef'd out; but maybe it's just noise.
---
 Speed_gcc_i686-w64-mingw32    |  12 +-
 Speed_gcc_x86_64-pc-linux-gnu |  12 +-
 Speed_gcc_x86_64-w64-mingw32  |  12 +-
 currency.hpp                  | 143 ++++++++++++++++++++++++
 currency_test.cpp             | 247 +++++++++++++++++++++++++++++++++++++++++-
 round_to.hpp                  |  67 ++++++++++++
 6 files changed, 472 insertions(+), 21 deletions(-)

diff --git a/Speed_gcc_i686-w64-mingw32 b/Speed_gcc_i686-w64-mingw32
index d454565..640ee4a 100644
--- a/Speed_gcc_i686-w64-mingw32
+++ b/Speed_gcc_i686-w64-mingw32
@@ -1,7 +1,7 @@
 Test speed:
-  naic, no solve      : 5.266e-02 s mean;      52033 us least of  19 runs
-  naic, specamt solve : 9.338e-02 s mean;      92473 us least of  11 runs
-  naic, ee prem solve : 8.551e-02 s mean;      84842 us least of  12 runs
-  finra, no solve     : 2.208e-02 s mean;      21730 us least of  46 runs
-  finra, specamt solve: 5.952e-02 s mean;      58993 us least of  17 runs
-  finra, ee prem solve: 5.544e-02 s mean;      54749 us least of  19 runs
+  naic, no solve      : 5.265e-02 s mean;      52301 us least of  19 runs
+  naic, specamt solve : 9.346e-02 s mean;      93081 us least of  11 runs
+  naic, ee prem solve : 8.585e-02 s mean;      85448 us least of  12 runs
+  finra, no solve     : 2.191e-02 s mean;      21728 us least of  46 runs
+  finra, specamt solve: 5.958e-02 s mean;      59178 us least of  17 runs
+  finra, ee prem solve: 5.542e-02 s mean;      55103 us least of  19 runs
diff --git a/Speed_gcc_x86_64-pc-linux-gnu b/Speed_gcc_x86_64-pc-linux-gnu
index 82b22a1..0b6a909 100644
--- a/Speed_gcc_x86_64-pc-linux-gnu
+++ b/Speed_gcc_x86_64-pc-linux-gnu
@@ -1,7 +1,7 @@
 Test speed:
-  naic, no solve      : 1.919e-02 s mean;      18722 us least of  53 runs
-  naic, specamt solve : 3.322e-02 s mean;      32686 us least of  31 runs
-  naic, ee prem solve : 3.058e-02 s mean;      29995 us least of  33 runs
-  finra, no solve     : 7.656e-03 s mean;       7307 us least of 100 runs
-  finra, specamt solve: 2.072e-02 s mean;      20184 us least of  49 runs
-  finra, ee prem solve: 1.929e-02 s mean;      18870 us least of  52 runs
+  naic, no solve      : 1.969e-02 s mean;      18830 us least of  51 runs
+  naic, specamt solve : 3.309e-02 s mean;      32682 us least of  31 runs
+  naic, ee prem solve : 3.040e-02 s mean;      30144 us least of  33 runs
+  finra, no solve     : 8.147e-03 s mean;       7401 us least of 100 runs
+  finra, specamt solve: 2.064e-02 s mean;      20233 us least of  49 runs
+  finra, ee prem solve: 1.930e-02 s mean;      18924 us least of  52 runs
diff --git a/Speed_gcc_x86_64-w64-mingw32 b/Speed_gcc_x86_64-w64-mingw32
index b884f04..e3cfd75 100644
--- a/Speed_gcc_x86_64-w64-mingw32
+++ b/Speed_gcc_x86_64-w64-mingw32
@@ -1,7 +1,7 @@
 Test speed:
-  naic, no solve      : 2.679e-02 s mean;      26405 us least of  38 runs
-  naic, specamt solve : 4.213e-02 s mean;      41608 us least of  24 runs
-  naic, ee prem solve : 3.933e-02 s mean;      38708 us least of  26 runs
-  finra, no solve     : 1.489e-02 s mean;      14508 us least of  68 runs
-  finra, specamt solve: 2.912e-02 s mean;      28617 us least of  35 runs
-  finra, ee prem solve: 2.753e-02 s mean;      27051 us least of  37 runs
+  naic, no solve      : 2.662e-02 s mean;      26397 us least of  38 runs
+  naic, specamt solve : 4.193e-02 s mean;      41587 us least of  24 runs
+  naic, ee prem solve : 3.905e-02 s mean;      38725 us least of  26 runs
+  finra, no solve     : 1.465e-02 s mean;      14467 us least of  69 runs
+  finra, specamt solve: 2.887e-02 s mean;      28679 us least of  35 runs
+  finra, ee prem solve: 2.730e-02 s mean;      27102 us least of  37 runs
diff --git a/currency.hpp b/currency.hpp
index 65ec7f2..1183868 100644
--- a/currency.hpp
+++ b/currency.hpp
@@ -24,6 +24,149 @@
 
 #include "config.hpp"
 
+#include <cmath>                        // rint()
+#include <ostream>
+#include <stdexcept>                    // runtime_error
+#include <vector>
+
+// Macros USE_CURRENCY_CLASS and CURRENCY_UNIT_IS_CENTS are used
+// elsewhere. Eventually they'll both be eliminated, along with
+// all code along paths where they aren't both defined.
+
+// #define USE_CURRENCY_CLASS
+
+#if !defined USE_CURRENCY_CLASS
+#   undef CURRENCY_UNIT_IS_CENTS // Requires currency class.
+
 using currency = double;
 
+inline currency from_cents(double z) {return z / 100.0;}
+
+inline double dblize(currency z) {return z;}
+
+inline std::vector<double> dblize(std::vector<currency> const& z)
+{
+    return z;
+}
+
+#else // defined USE_CURRENCY_CLASS
+
+#   define CURRENCY_UNIT_IS_CENTS
+
+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 class round_to_test;               // currency::cents_digits
+
+#   if defined CURRENCY_UNIT_IS_CENTS
+    static constexpr int    cents_digits     = 2;
+    static constexpr double cents_per_dollar = 100.0;
+#   else  // !defined CURRENCY_UNIT_IS_CENTS
+    static constexpr int    cents_digits     = 0;
+    static constexpr double cents_per_dollar = 1.0;
+#   endif // !defined CURRENCY_UNIT_IS_CENTS
+
+  public:
+    using data_type = double;
+
+    currency() = default;
+    currency(currency const&) = default;
+    currency& operator=(currency const&) = default;
+    ~currency() = default;
+
+    currency& operator+=(currency z) {m_ += z.m_; return *this;}
+    currency& operator-=(currency z) {m_ -= z.m_; return *this;}
+
+    currency& operator*=(int      z) {m_ *= z   ; return *this;}
+
+    currency operator-() const {return currency(-cents(), raw_cents {});}
+
+    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} {}
+
+    data_type m_ = {};
+};
+
+inline bool operator==(currency lhs, currency rhs)
+    {return lhs.cents() == rhs.cents();}
+inline bool operator< (currency lhs, currency rhs)
+    {return lhs.cents() <  rhs.cents();}
+inline bool operator!=(currency lhs, currency rhs)
+    {return !operator==(lhs, rhs);}
+inline bool operator> (currency lhs, currency rhs)
+    {return  operator< (rhs, lhs);}
+inline bool operator<=(currency lhs, currency rhs)
+    {return !operator> (lhs, rhs);}
+inline bool operator>=(currency lhs, currency rhs)
+    {return !operator< (lhs, rhs);}
+
+inline currency operator+(currency lhs, currency rhs)
+    {return currency {lhs} += rhs;}
+inline currency operator-(currency lhs, currency rhs)
+    {return currency {lhs} -= rhs;}
+
+inline currency operator*(currency lhs, int rhs)
+    {return currency {lhs} *= rhs;}
+inline currency operator*(int lhs, currency rhs)
+    {return currency {rhs} *= lhs;}
+
+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 std::ostream& operator<<(std::ostream& os, currency z)
+    {return os << z.d();}
+
+inline currency from_cents(double z)
+    {
+#   if defined CURRENCY_UNIT_IS_CENTS
+    if(z != std::rint(z)) throw std::runtime_error("Nonintegral cents.");
+    return currency(z, raw_cents{});
+#   else  // !defined CURRENCY_UNIT_IS_CENTS
+    // If currency unit is dollars rather than cents, then:
+    //  - dividing by 100 is the only reasonable thing to do here, even
+    //    though 'cents_per_dollar' is unity; and
+    //  - a value such as $.01 cannot be integral, so the desired
+    //    invariant that the result is integral must be sacrificed.
+    return currency(z / 100.0, raw_cents{});
+#   endif // !defined CURRENCY_UNIT_IS_CENTS
+    }
+
+inline double dblize(currency z) {return z.d();}
+
+inline std::vector<double> dblize(std::vector<currency> const& z)
+{
+    std::vector<double> r;
+    r.reserve(z.size());
+    for(auto const& i : z)
+        {
+        r.emplace_back(i.d());
+        }
+    return r;
+}
+
+#   endif // defined USE_CURRENCY_CLASS
+
+/// 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 = {};
+
 #endif // currency_hpp
diff --git a/currency_test.cpp b/currency_test.cpp
index 192d95a..ecd1872 100644
--- a/currency_test.cpp
+++ b/currency_test.cpp
@@ -23,26 +23,267 @@
 
 #include "currency.hpp"
 
+#include "round_to.hpp"
 #include "test_tools.hpp"
 
+#include <limits>
+#include <sstream>
+
+namespace
+{
+    round_to<double> const round_to_nearest_cent(2, r_to_nearest);
+} // Unnamed namespace.
+
 class currency_test
 {
   public:
     static void test();
 
   private:
-    static void test_something();
+    static void test_default_ctor();
+    static void test_copy_ctor();
+    static void test_explicit_ctor();
+    static void test_negation();
+    static void test_plus_or_minus_eq();
+    static void test_plus_or_minus();
+    static void test_times_int();
+    static void test_times_double();
+    static void test_divide_by_double();
+    static void test_relops();
+    static void test_stream_inserter();
+    static void test_dollars();
+    static void test_round_double();
+    static void test_round_currency();
+    static void test_quodlibet();
 };
 
 void currency_test::test()
 {
-    test_something();
+#if defined USE_CURRENCY_CLASS
+    test_default_ctor();
+    test_copy_ctor();
+    test_explicit_ctor();
+    test_negation();
+// CURRENCY !! Most of these tests assume that the currency unit is
+// cents. It's not worth adapting them to any other case because
+// soon this macro will, in effect, always be defined.
+#   if defined CURRENCY_UNIT_IS_CENTS
+    test_plus_or_minus_eq();
+    test_plus_or_minus();
+    test_times_int();
+    test_times_double();
+    test_divide_by_double();
+    test_relops();
+    test_stream_inserter();
+    test_dollars();
+    test_round_double();
+    test_round_currency();
+    test_quodlibet();
+#   endif // defined CURRENCY_UNIT_IS_CENTS
+#endif // defined USE_CURRENCY_CLASS
+}
+
+#if defined USE_CURRENCY_CLASS
+void currency_test::test_default_ctor()
+{
+    // default ctor
+    currency const a0;
+    BOOST_TEST(0.00 == a0.d());
+    BOOST_TEST(   0 == a0.m_);
+    constexpr currency zero {};
+    BOOST_TEST(   0 == a0.m_);
+}
+
+void currency_test::test_copy_ctor()
+{
+    currency const a1(325, raw_cents{});
+    currency const copy0 = a1;
+    BOOST_TEST_EQUAL( 325, copy0.m_);
+    currency const copy1 {a1};
+    BOOST_TEST_EQUAL( 325, copy1.m_);
+}
+
+void currency_test::test_explicit_ctor()
+{
+    currency const a1(325, raw_cents{});
+    BOOST_TEST_EQUAL( 325, a1.m_);
+#if defined DETECT_NONINTEGRAL_CENTS
+    // 1/64 is an exact binary constant, so 100/64 cents could be
+    // converted to 1/64 dollars and back without loss of precision;
+    // but that's outside the intended scope of the currency class.
+    BOOST_TEST_THROW
+        ((currency {1.5625, raw_cents {}})
+        ,std::runtime_error
+        ,"Nonintegral cents."
+        );
+#endif // defined DETECT_NONINTEGRAL_CENTS
+}
+
+void currency_test::test_negation()
+{
+    currency const a1(321, raw_cents{});
+    -a1;
+    // make sure that didn't mutate the object
+    // (making negation a nonmember makes that mistake less likely)
+    BOOST_TEST_EQUAL( 321, a1.m_);
+    BOOST_TEST_EQUAL(-321, (-a1).m_);
+
+    currency const a2 = -a1;
+    BOOST_TEST_EQUAL(-321, a2.m_);
+}
+
+void currency_test::test_plus_or_minus_eq()
+{
+    currency a1(325, raw_cents{});
+    a1 += a1;
+    BOOST_TEST_EQUAL( 650, a1.m_);
+
+    a1 -= currency {123, raw_cents {}};
+    BOOST_TEST_EQUAL(527, a1.m_);
+}
+
+void currency_test::test_plus_or_minus()
+{
+    currency const a1(650, raw_cents{});
+    currency a2 = currency() + a1 + a1;
+    BOOST_TEST_EQUAL(13.00, a2.d());
+    BOOST_TEST_EQUAL( 1300, a2.m_);
+
+    a2 = currency() - a1;
+    BOOST_TEST_EQUAL(-6.50, a2.d());
+    BOOST_TEST_EQUAL( -650, a2.m_);
+    a2 = C0 - a1;
+    BOOST_TEST_EQUAL(-6.50, a2.d());
+    BOOST_TEST_EQUAL( -650, a2.m_);
+}
+
+void currency_test::test_times_int()
+{
+    // currency * int returns currency
+    currency const mult2 {3125, raw_cents {}};
+    BOOST_TEST_EQUAL(1000.0,  (32 * mult2).d());
+    BOOST_TEST_EQUAL(1000.0, dblize(32 * mult2));
+    BOOST_TEST_EQUAL(100000, (mult2 * 32).m_);
 }
 
-void currency_test::test_something()
+void currency_test::test_times_double()
 {
+    currency const mult2 {3125, raw_cents {}};
+    // currency * double returns double
+    BOOST_TEST_EQUAL(1000.0, 32.0 * mult2);
+    BOOST_TEST_EQUAL(1000.0, mult2 * 32.0);
 }
 
+void currency_test::test_divide_by_double()
+{
+    // currency / double returns double
+    currency const div2 {3300, raw_cents {}};
+    BOOST_TEST_EQUAL(1.0, div2 / 33);
+}
+
+void currency_test::test_relops()
+{
+    currency const a0;
+    currency const a1(1728, raw_cents{});
+    BOOST_TEST(  C0 == a0);
+    BOOST_TEST(  a1 == a1);
+    BOOST_TEST(  a0 <  a1);
+    BOOST_TEST(  a0 <= a1);
+    BOOST_TEST(  a1 <= a1);
+    BOOST_TEST(  a1 >  a0);
+    BOOST_TEST(  a1 >= a0);
+    BOOST_TEST(  a1 >= a1);
+}
+
+void currency_test::test_stream_inserter()
+{
+    currency const a3 {123456, raw_cents {}};
+    std::ostringstream oss;
+    oss << a3;
+    BOOST_TEST_EQUAL("1234.56", oss.str());
+}
+
+void currency_test::test_dollars()
+{
+    currency const a0;
+    BOOST_TEST(0.00 == a0.d());
+
+    currency const a1(325, raw_cents{});
+    BOOST_TEST_EQUAL( 325, a1.m_);
+    BOOST_TEST_EQUAL( 325, a1.cents());
+    // 3.25 is an exact binary constant
+    BOOST_TEST_EQUAL(3.25, a1.d());
+}
+
+void currency_test::test_round_double()
+{
+    double d0 = 123.99999999999;
+    currency c0 = round_to_nearest_cent.c(d0);
+    BOOST_TEST_EQUAL(12400, c0.m_);
+    double d1 = 1.0 + std::numeric_limits<double>::epsilon();
+    currency c1 = round_to_nearest_cent.c(d1);
+    BOOST_TEST_EQUAL(100, c1.m_);
+    double d2 = 1.0 - std::numeric_limits<double>::epsilon();
+    currency c2 = round_to_nearest_cent.c(d2);
+    BOOST_TEST_EQUAL(100, c2.m_);
+}
+
+void currency_test::test_round_currency()
+{
+}
+
+void currency_test::test_quodlibet()
+{
+    currency const a0(325, raw_cents{});
+    BOOST_TEST_EQUAL(3.25, a0.d());
+    BOOST_TEST_EQUAL(3.25, dblize(a0));
+    currency       a1(475, raw_cents{});
+    BOOST_TEST_EQUAL(4.75, a1.d());
+    BOOST_TEST_EQUAL(4.75, dblize(a1));
+    currency const a2 = from_cents(125);
+    BOOST_TEST_EQUAL(1.25, dblize(a2));
+
+    currency b0 = round_to_nearest_cent.c(464.180000000000006821);
+    currency b1 = round_to_nearest_cent.c(263.01999999999998181);
+    currency b2 = round_to_nearest_cent.c(0.0);
+    b2 += b0;
+    b2 += b1;
+    currency b3 = b0 + b1;
+    BOOST_TEST_EQUAL(b2.cents(), b3.cents());
+    BOOST_TEST_EQUAL(b2, b3);
+}
+
+// CURRENCY !! Ideas for testing overflow or underflow.
+#if 0
+//  double big_num = nonstd::power(2.0, 53);
+    double big_num = 1.0e100;
+    currency::data_type big_int1 =   1.0 * big_num;
+    BOOST_TEST_EQUAL(1.0e100, big_int1);
+    currency::data_type big_int2 =  10.0 * big_num;
+    BOOST_TEST_EQUAL(1.0e101, big_int2);
+    currency::data_type big_int3 = 100.0 * big_num;
+    BOOST_TEST_EQUAL(1.0e102, big_int3);
+    round_to_nearest_cent.c(d0);
+    std::cout << std::fixed;
+std::cout << big_int3 << '\n' << 1.0e102 << '\n' << big_int3 - 1.0e102 << 
std::endl;
+
+    BOOST_TEST_THROW
+        (round_to_nearest_cent.c(big_num / 1000.0)
+        ,std::runtime_error
+        ,"Cast would transgress upper limit."
+        );
+
+    double too_big = std::numeric_limits<double>::max();
+    BOOST_TEST_THROW
+        (round_to_nearest_cent.c(too_big)
+        ,std::runtime_error
+//      ,"Cast would transgress upper limit."
+        ,"Cannot cast infinite to integral."
+        );
+#endif // 0
+
+#endif // defined USE_CURRENCY_CLASS
+
 int test_main(int, char*[])
 {
     currency_test::test();
diff --git a/round_to.hpp b/round_to.hpp
index e986f25..d1f63b0 100644
--- a/round_to.hpp
+++ b/round_to.hpp
@@ -24,6 +24,7 @@
 
 #include "config.hpp"
 
+#include "currency.hpp"
 #include "mc_enum_type_enums.hpp"       // enum rounding_style
 #include "stl_extensions.hpp"           // nonstd::power()
 
@@ -266,6 +267,14 @@ class round_to
     RealType operator()(RealType) const;
     std::vector<RealType> operator()(std::vector<RealType> const&) const;
 
+    currency c(RealType) const;
+    std::vector<currency> c(std::vector<RealType> const&) const;
+
+#if defined USE_CURRENCY_CLASS
+    currency c(currency) const;
+    std::vector<currency> c(std::vector<currency> const&) const;
+#endif // defined USE_CURRENCY_CLASS
+
     int decimals() const;
     rounding_style style() const;
 
@@ -277,6 +286,9 @@ class round_to
     rounding_style style_            {r_indeterminate};
     max_prec_real scale_fwd_         {1.0};
     max_prec_real scale_back_        {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};
 };
 
@@ -310,6 +322,13 @@ round_to<RealType>::round_to(int a_decimals, 
rounding_style a_style)
     ,style_             {a_style}
     ,scale_fwd_         {detail::int_pow(max_prec_real(10.0), decimals_)}
     ,scale_back_        {max_prec_real(1.0) / scale_fwd_}
+#if defined USE_CURRENCY_CLASS
+    ,decimals_cents_    {decimals_ - currency::cents_digits}
+#else  // !defined USE_CURRENCY_CLASS
+    ,decimals_cents_    {decimals_ - 0}
+#endif // ! defined USE_CURRENCY_CLASS
+    ,scale_fwd_cents_   {detail::int_pow(max_prec_real(10.0), decimals_cents_)}
+    ,scale_back_cents_  {max_prec_real(1.0) / scale_fwd_cents_}
     ,rounding_function_ {select_rounding_function(style_)}
 {
 /*
@@ -372,6 +391,54 @@ inline std::vector<RealType> round_to<RealType>::operator()
 }
 
 template<typename RealType>
+inline currency round_to<RealType>::c(RealType r) const
+{
+    RealType const z = static_cast<RealType>
+        ( rounding_function_(static_cast<RealType>(r * scale_fwd_))
+        * scale_back_cents_
+        );
+#if defined USE_CURRENCY_CLASS
+    // CURRENCY !! static_cast: possible range error
+    return currency(static_cast<currency::data_type>(z), raw_cents {});
+#else  // !defined USE_CURRENCY_CLASS
+    return currency(z);
+#endif // ! defined USE_CURRENCY_CLASS
+}
+
+template<typename RealType>
+inline std::vector<currency> round_to<RealType>::c
+    (std::vector<RealType> const& v) const
+{
+    std::vector<currency> z;
+    z.reserve(v.size());
+    for(auto const& i : v) {z.push_back(c(i));}
+    return z;
+}
+
+#if defined USE_CURRENCY_CLASS
+// CURRENCY !! need unit tests
+template<typename RealType>
+inline currency round_to<RealType>::c(currency z) const
+{
+#   if defined CURRENCY_UNIT_IS_CENTS
+    return (decimals_ < currency::cents_digits) ? c(z.d()) : z;
+#   else  // !defined CURRENCY_UNIT_IS_CENTS
+    return c(z.d());
+#   endif // !defined CURRENCY_UNIT_IS_CENTS
+}
+
+template<typename RealType>
+inline std::vector<currency> round_to<RealType>::c
+    (std::vector<currency> const& v) const
+{
+    std::vector<currency> z;
+    z.reserve(v.size());
+    for(auto const& i : v) {z.push_back(c(i));}
+    return z;
+}
+#endif // defined USE_CURRENCY_CLASS
+
+template<typename RealType>
 int round_to<RealType>::decimals() const
 {
     return decimals_;



reply via email to

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