309 lines
9.3 KiB
C++
309 lines
9.3 KiB
C++
///////////////////////////////////////////////////////////////////////////////
|
|
// Copyright 2012 John Maddock.
|
|
// Copyright 2012 Phil Endecott
|
|
// Distributed under the Boost
|
|
// Software License, Version 1.0. (See accompanying file
|
|
// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|
|
|
#include <boost/multiprecision/cpp_int.hpp>
|
|
#include "arithmetic_backend.hpp"
|
|
#include <boost/chrono.hpp>
|
|
#include <boost/random/mersenne_twister.hpp>
|
|
#include <boost/random/uniform_int_distribution.hpp>
|
|
|
|
#include <fstream>
|
|
#include <iomanip>
|
|
|
|
template <class Clock>
|
|
struct stopwatch
|
|
{
|
|
typedef typename Clock::duration duration;
|
|
stopwatch()
|
|
{
|
|
m_start = Clock::now();
|
|
}
|
|
duration elapsed()
|
|
{
|
|
return Clock::now() - m_start;
|
|
}
|
|
void reset()
|
|
{
|
|
m_start = Clock::now();
|
|
}
|
|
|
|
private:
|
|
typename Clock::time_point m_start;
|
|
};
|
|
|
|
// Custom 128-bit maths used for exact calculation of the Delaunay test.
|
|
// Only the few operators actually needed here are implemented.
|
|
|
|
struct int128_t
|
|
{
|
|
int64_t high;
|
|
uint64_t low;
|
|
|
|
int128_t() {}
|
|
int128_t(int32_t i) : high(i >> 31), low(static_cast<int64_t>(i)) {}
|
|
int128_t(uint32_t i) : high(0), low(i) {}
|
|
int128_t(int64_t i) : high(i >> 63), low(i) {}
|
|
int128_t(uint64_t i) : high(0), low(i) {}
|
|
};
|
|
|
|
inline int128_t operator<<(int128_t val, int amt)
|
|
{
|
|
int128_t r;
|
|
r.low = val.low << amt;
|
|
r.high = val.low >> (64 - amt);
|
|
r.high |= val.high << amt;
|
|
return r;
|
|
}
|
|
|
|
inline int128_t& operator+=(int128_t& l, int128_t r)
|
|
{
|
|
l.low += r.low;
|
|
bool carry = l.low < r.low;
|
|
l.high += r.high;
|
|
if (carry)
|
|
++l.high;
|
|
return l;
|
|
}
|
|
|
|
inline int128_t operator-(int128_t val)
|
|
{
|
|
val.low = ~val.low;
|
|
val.high = ~val.high;
|
|
val.low += 1;
|
|
if (val.low == 0)
|
|
val.high += 1;
|
|
return val;
|
|
}
|
|
|
|
inline int128_t operator+(int128_t l, int128_t r)
|
|
{
|
|
l += r;
|
|
return l;
|
|
}
|
|
|
|
inline bool operator<(int128_t l, int128_t r)
|
|
{
|
|
if (l.high != r.high)
|
|
return l.high < r.high;
|
|
return l.low < r.low;
|
|
}
|
|
|
|
inline int128_t mult_64x64_to_128(int64_t a, int64_t b)
|
|
{
|
|
// Make life simple by dealing only with positive numbers:
|
|
bool neg = false;
|
|
if (a < 0)
|
|
{
|
|
neg = !neg;
|
|
a = -a;
|
|
}
|
|
if (b < 0)
|
|
{
|
|
neg = !neg;
|
|
b = -b;
|
|
}
|
|
|
|
// Divide input into 32-bit halves:
|
|
uint32_t ah = a >> 32;
|
|
uint32_t al = a & 0xffffffff;
|
|
uint32_t bh = b >> 32;
|
|
uint32_t bl = b & 0xffffffff;
|
|
|
|
// Long multiplication, with 64-bit temporaries:
|
|
|
|
// ah al
|
|
// * bh bl
|
|
// ----------------
|
|
// al*bl (t1)
|
|
// + ah*bl (t2)
|
|
// + al*bh (t3)
|
|
// + ah*bh (t4)
|
|
// ----------------
|
|
|
|
uint64_t t1 = static_cast<uint64_t>(al) * bl;
|
|
uint64_t t2 = static_cast<uint64_t>(ah) * bl;
|
|
uint64_t t3 = static_cast<uint64_t>(al) * bh;
|
|
uint64_t t4 = static_cast<uint64_t>(ah) * bh;
|
|
|
|
int128_t r(t1);
|
|
r.high = t4;
|
|
r += int128_t(t2) << 32;
|
|
r += int128_t(t3) << 32;
|
|
|
|
if (neg)
|
|
r = -r;
|
|
|
|
return r;
|
|
}
|
|
|
|
template <class R, class T>
|
|
BOOST_FORCEINLINE void mul_2n(R& r, const T& a, const T& b)
|
|
{
|
|
r = a;
|
|
r *= b;
|
|
}
|
|
|
|
template <class B, boost::multiprecision::expression_template_option ET, class T>
|
|
BOOST_FORCEINLINE void mul_2n(boost::multiprecision::number<B, ET>& r, const T& a, const T& b)
|
|
{
|
|
multiply(r, a, b);
|
|
}
|
|
|
|
BOOST_FORCEINLINE void mul_2n(int128_t& r, const boost::int64_t& a, const boost::int64_t& b)
|
|
{
|
|
r = mult_64x64_to_128(a, b);
|
|
}
|
|
|
|
template <class Traits>
|
|
inline bool delaunay_test(int32_t ax, int32_t ay, int32_t bx, int32_t by,
|
|
int32_t cx, int32_t cy, int32_t dx, int32_t dy)
|
|
{
|
|
// Test whether the quadrilateral ABCD's diagonal AC should be flipped to BD.
|
|
// This is the Cline & Renka method.
|
|
// Flip if the sum of the angles ABC and CDA is greater than 180 degrees.
|
|
// Equivalently, flip if sin(ABC + CDA) < 0.
|
|
// Trig identity: cos(ABC) * sin(CDA) + sin(ABC) * cos(CDA) < 0
|
|
// We can use scalar and vector products to find sin and cos, and simplify
|
|
// to the following code.
|
|
// Numerical robustness is important. This code addresses it by performing
|
|
// exact calculations with large integer types.
|
|
//
|
|
// NOTE: This routine is limited to inputs with up to 30 BIT PRECISION, which
|
|
// is to say all inputs must be in the range [INT_MIN/2, INT_MAX/2].
|
|
|
|
typedef typename Traits::i64_t i64;
|
|
typedef typename Traits::i128_t i128;
|
|
|
|
i64 cos_abc, t;
|
|
mul_2n(cos_abc, (ax - bx), (cx - bx)); // subtraction yields 31-bit values, multiplied to give 62-bit values
|
|
mul_2n(t, (ay - by), (cy - by));
|
|
cos_abc += t; // addition yields 63 bit value, leaving one left for the sign
|
|
|
|
i64 cos_cda;
|
|
mul_2n(cos_cda, (cx - dx), (ax - dx));
|
|
mul_2n(t, (cy - dy), (ay - dy));
|
|
cos_cda += t;
|
|
|
|
if (cos_abc >= 0 && cos_cda >= 0)
|
|
return false;
|
|
if (cos_abc < 0 && cos_cda < 0)
|
|
return true;
|
|
|
|
i64 sin_abc;
|
|
mul_2n(sin_abc, (ax - bx), (cy - by));
|
|
mul_2n(t, (cx - bx), (ay - by));
|
|
sin_abc -= t;
|
|
|
|
i64 sin_cda;
|
|
mul_2n(sin_cda, (cx - dx), (ay - dy));
|
|
mul_2n(t, (ax - dx), (cy - dy));
|
|
sin_cda -= t;
|
|
|
|
i128 sin_sum, t128;
|
|
mul_2n(sin_sum, sin_abc, cos_cda); // 63-bit inputs multiplied to 126-bit output
|
|
mul_2n(t128, cos_abc, sin_cda);
|
|
sin_sum += t128; // Addition yields 127 bit result, leaving one bit for the sign
|
|
|
|
return sin_sum < 0;
|
|
}
|
|
|
|
struct dt_dat
|
|
{
|
|
int32_t ax, ay, bx, by, cx, cy, dx, dy;
|
|
};
|
|
|
|
typedef std::vector<dt_dat> data_t;
|
|
data_t data;
|
|
|
|
template <class Traits>
|
|
void do_calc(const char* name)
|
|
{
|
|
std::cout << "Running calculations for: " << name << std::endl;
|
|
|
|
stopwatch<boost::chrono::high_resolution_clock> w;
|
|
|
|
boost::uint64_t flips = 0;
|
|
boost::uint64_t calcs = 0;
|
|
|
|
for (int j = 0; j < 1000; ++j)
|
|
{
|
|
for (data_t::const_iterator i = data.begin(); i != data.end(); ++i)
|
|
{
|
|
const dt_dat& d = *i;
|
|
bool flip = delaunay_test<Traits>(d.ax, d.ay, d.bx, d.by, d.cx, d.cy, d.dx, d.dy);
|
|
if (flip)
|
|
++flips;
|
|
++calcs;
|
|
}
|
|
}
|
|
double t = boost::chrono::duration_cast<boost::chrono::duration<double> >(w.elapsed()).count();
|
|
|
|
std::cout << "Number of calculations = " << calcs << std::endl;
|
|
std::cout << "Number of flips = " << flips << std::endl;
|
|
std::cout << "Total execution time = " << t << std::endl;
|
|
std::cout << "Time per calculation = " << t / calcs << std::endl
|
|
<< std::endl;
|
|
}
|
|
|
|
template <class I64, class I128>
|
|
struct test_traits
|
|
{
|
|
typedef I64 i64_t;
|
|
typedef I128 i128_t;
|
|
};
|
|
|
|
dt_dat generate_quadrilateral()
|
|
{
|
|
static boost::random::mt19937 gen;
|
|
static boost::random::uniform_int_distribution<> dist(INT_MIN / 2, INT_MAX / 2);
|
|
|
|
dt_dat result;
|
|
|
|
result.ax = dist(gen);
|
|
result.ay = dist(gen);
|
|
result.bx = boost::random::uniform_int_distribution<>(result.ax, INT_MAX / 2)(gen); // bx is to the right of ax.
|
|
result.by = dist(gen);
|
|
result.cx = dist(gen);
|
|
result.cy = boost::random::uniform_int_distribution<>(result.cx > result.bx ? result.by : result.ay, INT_MAX / 2)(gen); // cy is below at least one of ay and by.
|
|
result.dx = boost::random::uniform_int_distribution<>(result.cx, INT_MAX / 2)(gen); // dx is to the right of cx.
|
|
result.dy = boost::random::uniform_int_distribution<>(result.cx > result.bx ? result.by : result.ay, INT_MAX / 2)(gen); // cy is below at least one of ay and by.
|
|
|
|
return result;
|
|
}
|
|
|
|
static void load_data()
|
|
{
|
|
for (unsigned i = 0; i < 100000; ++i)
|
|
data.push_back(generate_quadrilateral());
|
|
}
|
|
|
|
int main()
|
|
{
|
|
using namespace boost::multiprecision;
|
|
std::cout << "loading data...\n";
|
|
load_data();
|
|
|
|
std::cout << "calculating...\n";
|
|
|
|
do_calc<test_traits<boost::int64_t, boost::int64_t> >("int64_t, int64_t");
|
|
do_calc<test_traits<number<arithmetic_backend<boost::int64_t>, et_off>, number<arithmetic_backend<boost::int64_t>, et_off> > >("arithmetic_backend<int64_t>, arithmetic_backend<int64_t>");
|
|
do_calc<test_traits<boost::int64_t, number<arithmetic_backend<boost::int64_t>, et_off> > >("int64_t, arithmetic_backend<int64_t>");
|
|
do_calc<test_traits<number<cpp_int_backend<64, 64, boost::multiprecision::signed_magnitude, boost::multiprecision::unchecked, void>, et_off>, number<cpp_int_backend<64, 64, boost::multiprecision::signed_magnitude, boost::multiprecision::unchecked, void>, et_off> > >("multiprecision::int64_t, multiprecision::int64_t");
|
|
|
|
do_calc<test_traits<boost::int64_t, ::int128_t> >("int64_t, int128_t");
|
|
do_calc<test_traits<boost::int64_t, boost::multiprecision::int128_t> >("int64_t, boost::multiprecision::int128_t");
|
|
do_calc<test_traits<boost::int64_t, number<cpp_int_backend<128, 128, boost::multiprecision::signed_magnitude, boost::multiprecision::unchecked, void>, et_on> > >("int64_t, int128_t (ET)");
|
|
do_calc<test_traits<number<cpp_int_backend<64, 64, boost::multiprecision::signed_magnitude, boost::multiprecision::unchecked, void>, et_off>, boost::multiprecision::int128_t> >("multiprecision::int64_t, multiprecision::int128_t");
|
|
|
|
do_calc<test_traits<boost::int64_t, cpp_int> >("int64_t, cpp_int");
|
|
do_calc<test_traits<boost::int64_t, number<cpp_int_backend<>, et_off> > >("int64_t, cpp_int (no ET's)");
|
|
do_calc<test_traits<boost::int64_t, number<cpp_int_backend<128> > > >("int64_t, cpp_int(128-bit cache)");
|
|
do_calc<test_traits<boost::int64_t, number<cpp_int_backend<128>, et_off> > >("int64_t, cpp_int (128-bit Cache no ET's)");
|
|
|
|
return 0;
|
|
}
|