Skip to content

Instantly share code, notes, and snippets.

@randomphrase
Last active November 22, 2019 17:07
Show Gist options
  • Save randomphrase/73a520b8a57869998ab71693b4c9e99c to your computer and use it in GitHub Desktop.
Save randomphrase/73a520b8a57869998ab71693b4c9e99c to your computer and use it in GitHub Desktop.
Price type experimentation
#define BOOST_TEST_MODULE sides
#include <boost/test/unit_test.hpp>
#include <boost/test/tools/output_test_stream.hpp>
#include <thread>
#include <chrono>
namespace btt = boost::test_tools;
namespace demo {
class activity_monitor {
public:
using clock_t = std::chrono::system_clock;
using time_point = typename clock_t::time_point;
static constexpr auto timeout = std::chrono::milliseconds(100);
activity_monitor(time_point now = clock_t::now())
: last_activity_{now}
{}
~activity_monitor() {
if (thread_ && thread_->joinable())
thread_->join();
}
void notify_activity() {
deadmans_switch_.clear(std::memory_order_release);
}
void run_once(time_point now = clock_t::now()) {
if (!deadmans_switch_.test_and_set(std::memory_order_acquire)) {
// flag was clear, therefoe activity was notified
last_activity_ = now;
} else if (now - last_activity_ > timeout) {
throw std::runtime_error{"activity_monitor timeout"};
}
}
void run(std::atomic_bool& quit_flag) {
while (!quit_flag) {
run_once();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void start(std::atomic_bool& quit_flag) {
thread_ = std::make_unique<std::thread>([this, &quit_flag] {run(quit_flag);});
}
private:
std::unique_ptr<std::thread> thread_;
std::atomic_flag deadmans_switch_;
clock_t::time_point last_activity_;
};
BOOST_AUTO_TEST_CASE(throws_on_inactivity) {
using namespace std::chrono;
auto now = activity_monitor::clock_t::now();
activity_monitor monitor{now};
monitor.run_once(now += milliseconds(1));
monitor.notify_activity();
monitor.run_once(now += milliseconds(1));
monitor.run_once(now += milliseconds(1));
monitor.run_once(now += milliseconds(1));
// notify activity then nothing for 100 millis -> exception
monitor.notify_activity();
monitor.run_once(now += milliseconds(1));
monitor.run_once(now += milliseconds(50));
BOOST_CHECK_THROW(monitor.run_once(now += milliseconds(51)), std::runtime_error);
}
BOOST_AUTO_TEST_CASE(run_test) {
activity_monitor monitor{};
std::atomic_bool quit;
BOOST_CHECK_THROW(monitor.run(quit), std::runtime_error);
}
}
#define BOOST_TEST_MODULE sides
#include <boost/test/unit_test.hpp>
#include <boost/test/tools/output_test_stream.hpp>
#include <ratio>
#include <numeric>
#include <ostream>
#include <iomanip>
namespace btt = boost::test_tools;
namespace demo {
template <typename T>
constexpr std::enable_if_t<std::is_integral_v<T>, T> digits10(T param) {
assert(param >= T{});
T result{1};
while (T{} != (param /= T{10}))
++result;
return result;
}
static_assert(digits10(0) == 1);
static_assert(digits10(1) == 1);
static_assert(digits10(10) == 2);
static_assert(digits10(100) == 3);
template <typename T>
struct price_traits {
static /*constexpr*/ T add(T a, T b) {
T res;
if (__builtin_add_overflow(a, b, &res)) {
[[unlikely]] throw std::overflow_error{""};
}
return res;
}
static /*constexpr*/ T subtract(T a, T b) {
T res;
if (__builtin_sub_overflow(a, b, &res)) {
[[unlikely]] throw std::overflow_error{""};
}
return res;
}
};
template <typename Rep, typename Scale, typename Traits=price_traits<Rep>>
class price {
Rep val_;
public:
using rep = Rep;
constexpr explicit price(Rep val) : val_{val} {}
constexpr price(const price&) = default;
constexpr price& operator= (const price&) = default;
constexpr explicit operator Rep () const { return val_; }
// can convert from a price with a greater or equal scale value - we don't want to lose precision
template <typename ORep, typename OScale,
std::enable_if_t<std::ratio_greater_equal_v<OScale, Scale>> * = nullptr>
constexpr explicit price(const price<ORep, OScale> &other)
: val_{static_cast<Rep>(static_cast<ORep>(other)) *
static_cast<Rep>(std::ratio_divide<OScale, Scale>::num)}
{
}
friend std::ostream &operator<<(std::ostream &os, price px) {
const auto [dollars, cents] = std::div(px.val_ * Scale::num, Scale::den);
const auto oldfill = os.fill('0');
return os << dollars << '.' << std::setw(std::max(2l, digits10(Scale::den - 1))) << cents << std::setfill(oldfill);
}
// comparison operators
#define OPERATOR(xx) \
constexpr bool operator xx (price other) const { \
return val_ xx static_cast<Rep>(other); \
}
OPERATOR(==)
OPERATOR(!=)
OPERATOR(>)
OPERATOR(>=)
OPERATOR(<)
OPERATOR(<=)
#undef OPERATOR
// arithmetic operators
#define OPERATOR(xx, fn) \
constexpr auto operator xx (price other) const { \
return price{Traits::fn(val_, other.val_)}; \
}
OPERATOR(+, add)
OPERATOR(-, subtract)
#undef OPERATOR
};
template <typename scale>
using int_price = price<int, scale>;
using mega_bucks = int_price<std::ratio<1'000'000, 1>>;
/// int_price scaled to 1/den
template <int den>
using int_frac_price = int_price<std::ratio<1, den>>;
using dollars = int_frac_price<1>;
using cents = int_frac_price<100>;
using dollar_64ths = int_frac_price<64>;
} // namespace demo
namespace std {
template <typename Rep, typename Scale>
struct numeric_limits<demo::price<Rep, Scale>> : numeric_limits<Rep> {
using price = demo::price<Rep, Scale>;
static constexpr auto min() { return price{numeric_limits<Rep>::min()}; };
static constexpr auto max() { return price{numeric_limits<Rep>::max()}; };
};
template <typename RepA, typename ScaleA, typename RepB, typename ScaleB>
struct common_type<demo::price<RepA, ScaleA>, demo::price<RepB, ScaleB>> {
using type = demo::price<
common_type_t<RepA, RepB>,
ratio<gcd(ScaleA::num, ScaleB::num), lcm(ScaleA::den, ScaleB::den)>>;
};
} // namespace std
namespace demo {
static_assert(static_cast<int>(std::numeric_limits<dollars>::min()) == std::numeric_limits<int>::min());
static_assert(std::is_same_v<std::common_type_t<dollars, cents>, cents>);
static_assert(std::is_same_v<std::common_type_t<mega_bucks, dollars>, dollars>);
static_assert(std::is_same_v<std::common_type_t<cents, dollar_64ths>, int_frac_price<1600>>);
// converting operators
#define OPERATOR(xx) \
template <typename RepA, typename ScaleA, typename RepB, typename ScaleB, \
std::enable_if_t<!std::is_same_v<RepA, RepB> || \
!std::is_same_v<ScaleA, ScaleB>>* = nullptr> \
constexpr auto operator xx (price<RepA, ScaleA> a, price<RepB, ScaleB> b) { \
using common_t = std::common_type_t<price<RepA, ScaleA>, price<RepB, ScaleB>>; \
return common_t{a} xx common_t{b}; \
}
OPERATOR(==)
OPERATOR(!=)
OPERATOR(>)
OPERATOR(>=)
OPERATOR(<)
OPERATOR(<=)
OPERATOR(+)
OPERATOR(-)
#undef OPERATOR
constexpr dollars one_dollar {1};
constexpr dollars two_dollars {2};
constexpr cents hundred_cents {100};
constexpr dollar_64ths one_dollar_64ths {64};
constexpr mega_bucks one_mega {1};
static_assert(static_cast<int>(one_dollar) == 1);
static_assert(static_cast<int>(hundred_cents) == 100);
static_assert(static_cast<int>(one_dollar_64ths) == 64);
static_assert(static_cast<int>(one_mega) == 1);
static_assert(one_dollar == one_dollar);
static_assert(one_dollar == hundred_cents);
static_assert(one_dollar == one_dollar_64ths);
static_assert(hundred_cents == one_dollar_64ths);
static_assert(one_dollar != two_dollars);
static_assert(one_dollar < two_dollars);
static_assert(two_dollars >= hundred_cents);
BOOST_AUTO_TEST_CASE(addition_subtraction_test) {
BOOST_TEST(one_dollar + two_dollars == dollars{3});
BOOST_TEST(one_dollar + hundred_cents == cents{200});
BOOST_TEST(cents{150} - one_dollar == cents{50});
BOOST_TEST(cents{150} - int_frac_price<64>(32) == one_dollar_64ths);
BOOST_TEST(one_mega + one_dollar == dollars{1'000'001});
}
BOOST_AUTO_TEST_CASE(throw_on_overflow) {
BOOST_CHECK_THROW(dollars{std::numeric_limits<int>::max()} + dollars{1},
std::overflow_error);
// TODO: need a better way of detecting overflow in the converting
// constructor, until then this doesn't work
// BOOST_CHECK_THROW(mega_bucks{1'000'000} + dollars{1}, std::overflow_error);
}
BOOST_AUTO_TEST_CASE(price_ostream_operator) {
btt::output_test_stream out;
out << cents{101} << ' ' << two_dollars << ' ' << one_mega;
BOOST_TEST(out.is_equal("1.01 2.00 1000000.00"));
}
enum class side_t : std::uint8_t {
bid, ask
};
std::ostream& operator<< (std::ostream& os, side_t s) {
switch (s) {
case side_t::bid: os << "bid"; break;
case side_t::ask: os << "ask"; break;
}
return os;
}
template <side_t Side>
using side_constant = std::integral_constant<side_t, Side>;
using bid_t = side_constant<side_t::bid>;
using ask_t = side_constant<side_t::ask>;
inline constexpr bid_t bid_v {};
inline constexpr ask_t ask_v {};
template <typename price_a, typename price_b>
constexpr bool price_wider(price_a a, price_b b, bid_t) {
return a < b;
}
template <typename price_a, typename price_b>
constexpr bool price_wider(price_a a, price_b b, ask_t) {
return a > b;
}
static_assert(price_wider(hundred_cents, two_dollars, bid_v));
static_assert(!price_wider(one_dollar, two_dollars, ask_v));
static_assert(price_wider(two_dollars, hundred_cents, ask_v));
static_assert(!price_wider(two_dollars, one_dollar, bid_v));
// how much wider is a than b? will be -ve if a is narrower than b
template <typename price_a, typename price_b>
constexpr auto price_wider_by(price_a a, price_b b, bid_t) {
return b - a;
}
// how much wider is a than b? will be -ve if a is narrower than b
template <typename price_a, typename price_b>
constexpr auto price_wider_by(price_a a, price_b b, ask_t) {
return a - b;
}
BOOST_AUTO_TEST_CASE(price_wider_by_test) {
BOOST_TEST(price_wider_by(cents{25}, one_dollar, bid_v) == cents{75});
BOOST_TEST(price_wider_by(two_dollars, one_dollar, ask_v) == one_dollar);
}
template <typename T>
struct double_sided {
T bid_;
T ask_;
constexpr T& operator[] (bid_t) { return bid_; }
constexpr T& operator[] (ask_t) { return ask_; }
constexpr const T& operator[] (bid_t) const { return bid_; }
constexpr const T& operator[] (ask_t) const { return ask_; }
};
using priceband_cents = double_sided<cents>;
inline constexpr priceband_cents one_two { cents{100}, cents{200} };
template <typename price_t>
inline constexpr double_sided<price_t> min_max { std::numeric_limits<price_t>::min(), std::numeric_limits<price_t>::max() };
static_assert(one_two[bid_v] == cents{100});
static_assert(one_two[ask_v] == cents{200});
template <typename price_a, typename price_b>
constexpr bool priceband_contains(const double_sided<price_a>& pb, price_b px) {
return pb[bid_v] < px && pb[ask_v] > px;
}
} // namespace demo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment