Last active
November 22, 2019 17:07
-
-
Save randomphrase/73a520b8a57869998ab71693b4c9e99c to your computer and use it in GitHub Desktop.
Price type experimentation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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