[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_;
- [lmi-commits] [lmi] master d280c59 05/15: Fix a unit test, (continued)
- [lmi-commits] [lmi] master d280c59 05/15: Fix a unit test, Greg Chicares, 2021/01/25
- [lmi-commits] [lmi] master 7b51fab 07/15: Adapt to currency class, Greg Chicares, 2021/01/25
- [lmi-commits] [lmi] master f7a1129 08/15: Store policy fees as currency, Greg Chicares, 2021/01/25
- [lmi-commits] [lmi] master 2666c48 06/15: Test rounding double to currency, Greg Chicares, 2021/01/25
- [lmi-commits] [lmi] master 5ed8f75 04/15: Refactor a unit test, Greg Chicares, 2021/01/25
- [lmi-commits] [lmi] master 6a6ebe3 11/15: Adapt to currency class, Greg Chicares, 2021/01/25
- [lmi-commits] [lmi] master 1d5c923 01/15: Change some internal names, Greg Chicares, 2021/01/25
- [lmi-commits] [lmi] master 1e5d1ea 12/15: Adapt to currency class, Greg Chicares, 2021/01/25
- [lmi-commits] [lmi] master 27bd748 13/15: Write $0.00 as a currency rather than floating-point constant, Greg Chicares, 2021/01/25
- [lmi-commits] [lmi] master 9d991b0 15/15: Round currency as such, Greg Chicares, 2021/01/25
- [lmi-commits] [lmi] master 306ba4e 02/15: Introduce optional (for now) currency class,
Greg Chicares <=
- [lmi-commits] [lmi] master fd106fc 14/15: Reformat, Greg Chicares, 2021/01/25