aboutsummaryrefslogtreecommitdiff
path: root/tests/libntp/lfpfunc.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/libntp/lfpfunc.cpp')
-rw-r--r--tests/libntp/lfpfunc.cpp547
1 files changed, 547 insertions, 0 deletions
diff --git a/tests/libntp/lfpfunc.cpp b/tests/libntp/lfpfunc.cpp
new file mode 100644
index 000000000000..275918cdc50f
--- /dev/null
+++ b/tests/libntp/lfpfunc.cpp
@@ -0,0 +1,547 @@
+#include "libntptest.h"
+#include "timestructs.h"
+
+extern "C" {
+#include "ntp_fp.h"
+}
+
+#include <float.h>
+#include <math.h>
+
+#include <string>
+#include <sstream>
+
+class lfpTest : public libntptest
+{
+ // nothing new right now
+};
+
+struct lfp_hl {
+ uint32_t h, l;
+};
+
+//----------------------------------------------------------------------
+// OO-wrapper for 'l_fp'
+//----------------------------------------------------------------------
+
+class LFP
+{
+public:
+ ~LFP();
+ LFP();
+ LFP(const LFP& rhs);
+ LFP(int32 i, u_int32 f);
+
+ LFP operator+ (const LFP &rhs) const;
+ LFP& operator+=(const LFP &rhs);
+
+ LFP operator- (const LFP &rhs) const;
+ LFP& operator-=(const LFP &rhs);
+
+ LFP& operator=(const LFP &rhs);
+ LFP operator-() const;
+
+ bool operator==(const LFP &rhs) const;
+
+ LFP neg() const;
+ LFP abs() const;
+ int signum() const;
+
+ bool l_isgt (const LFP &rhs) const
+ { return L_ISGT(&_v, &rhs._v); }
+ bool l_isgtu(const LFP &rhs) const
+ { return L_ISGTU(&_v, &rhs._v); }
+ bool l_ishis(const LFP &rhs) const
+ { return L_ISHIS(&_v, &rhs._v); }
+ bool l_isgeq(const LFP &rhs) const
+ { return L_ISGEQ(&_v, &rhs._v); }
+ bool l_isequ(const LFP &rhs) const
+ { return L_ISEQU(&_v, &rhs._v); }
+
+ int ucmp(const LFP & rhs) const;
+ int scmp(const LFP & rhs) const;
+
+ std::string toString() const;
+ std::ostream& toStream(std::ostream &oo) const;
+
+ operator double() const;
+ explicit LFP(double);
+
+protected:
+ LFP(const l_fp &rhs);
+
+ static int cmp_work(u_int32 a[3], u_int32 b[3]);
+
+ l_fp _v;
+};
+
+static std::ostream& operator<<(std::ostream &oo, const LFP& rhs)
+{
+ return rhs.toStream(oo);
+}
+
+//----------------------------------------------------------------------
+// reference comparision
+// This is implementad as a full signed MP-subtract in 3 limbs, where
+// the operands are zero or sign extended before the subtraction is
+// executed.
+//----------------------------------------------------------------------
+int LFP::scmp(const LFP & rhs) const
+{
+ u_int32 a[3], b[3];
+ const l_fp &op1(_v), &op2(rhs._v);
+
+ a[0] = op1.l_uf; a[1] = op1.l_ui; a[2] = 0;
+ b[0] = op2.l_uf; b[1] = op2.l_ui; b[2] = 0;
+
+ a[2] -= (op1.l_i < 0);
+ b[2] -= (op2.l_i < 0);
+
+ return cmp_work(a,b);
+}
+
+int LFP::ucmp(const LFP & rhs) const
+{
+ u_int32 a[3], b[3];
+ const l_fp &op1(_v), &op2(rhs._v);
+
+ a[0] = op1.l_uf; a[1] = op1.l_ui; a[2] = 0;
+ b[0] = op2.l_uf; b[1] = op2.l_ui; b[2] = 0;
+
+ return cmp_work(a,b);
+}
+
+int LFP::cmp_work(u_int32 a[3], u_int32 b[3])
+{
+ u_int32 cy, idx, tmp;
+ for (cy = idx = 0; idx < 3; ++idx) {
+ tmp = a[idx]; cy = (a[idx] -= cy ) > tmp;
+ tmp = a[idx]; cy |= (a[idx] -= b[idx]) > tmp;
+ }
+ if (a[2])
+ return -1;
+ return a[0] || a[1];
+}
+
+//----------------------------------------------------------------------
+// imlementation of the LFP stuff
+// This should be easy enough...
+//----------------------------------------------------------------------
+
+LFP::~LFP()
+{
+ // NOP
+}
+
+LFP::LFP()
+{
+ _v.l_ui = 0;
+ _v.l_uf = 0;
+}
+
+LFP::LFP(int32 i, u_int32 f)
+{
+ _v.l_i = i;
+ _v.l_uf = f;
+}
+
+LFP::LFP(const LFP &rhs)
+{
+ _v = rhs._v;
+}
+
+LFP::LFP(const l_fp & rhs)
+{
+ _v = rhs;
+}
+
+LFP& LFP::operator=(const LFP & rhs)
+{
+ _v = rhs._v;
+ return *this;
+}
+
+LFP& LFP::operator+=(const LFP & rhs)
+{
+ L_ADD(&_v, &rhs._v);
+ return *this;
+}
+
+LFP& LFP::operator-=(const LFP & rhs)
+{
+ L_SUB(&_v, &rhs._v);
+ return *this;
+}
+
+LFP LFP::operator+(const LFP &rhs) const
+{
+ LFP tmp(*this);
+ return tmp += rhs;
+}
+
+LFP LFP::operator-(const LFP &rhs) const
+{
+ LFP tmp(*this);
+ return tmp -= rhs;
+}
+
+LFP LFP::operator-() const
+{
+ LFP tmp(*this);
+ L_NEG(&tmp._v);
+ return tmp;
+}
+
+LFP
+LFP::neg() const
+{
+ LFP tmp(*this);
+ L_NEG(&tmp._v);
+ return tmp;
+}
+
+LFP
+LFP::abs() const
+{
+ LFP tmp(*this);
+ if (L_ISNEG(&tmp._v))
+ L_NEG(&tmp._v);
+ return tmp;
+}
+
+int
+LFP::signum() const
+{
+ if (_v.l_ui & 0x80000000u)
+ return -1;
+ return (_v.l_ui || _v.l_uf);
+}
+
+std::string
+LFP::toString() const
+{
+ std::ostringstream oss;
+ toStream(oss);
+ return oss.str();
+}
+
+std::ostream&
+LFP::toStream(std::ostream &os) const
+{
+ return os
+ << mfptoa(_v.l_ui, _v.l_uf, 9)
+ << " [$" << std::setw(8) << std::setfill('0') << std::hex << _v.l_ui
+ << ':' << std::setw(8) << std::setfill('0') << std::hex << _v.l_uf
+ << ']';
+}
+
+bool LFP::operator==(const LFP &rhs) const
+{
+ return L_ISEQU(&_v, &rhs._v);
+}
+
+
+LFP::operator double() const
+{
+ double res;
+ LFPTOD(&_v, res);
+ return res;
+}
+
+LFP::LFP(double rhs)
+{
+ DTOLFP(rhs, &_v);
+}
+
+
+//----------------------------------------------------------------------
+// testing the relational macros works better with proper predicate
+// formatting functions; it slows down the tests a bit, but makes for
+// readable failure messages.
+//----------------------------------------------------------------------
+
+testing::AssertionResult isgt_p(
+ const LFP &op1, const LFP &op2)
+{
+ if (op1.l_isgt(op2))
+ return testing::AssertionSuccess()
+ << "L_ISGT(" << op1 << "," << op2 << ") is true";
+ else
+ return testing::AssertionFailure()
+ << "L_ISGT(" << op1 << "," << op2 << ") is false";
+}
+
+testing::AssertionResult isgeq_p(
+ const LFP &op1, const LFP &op2)
+{
+ if (op1.l_isgeq(op2))
+ return testing::AssertionSuccess()
+ << "L_ISGEQ(" << op1 << "," << op2 << ") is true";
+ else
+ return testing::AssertionFailure()
+ << "L_ISGEQ(" << op1 << "," << op2 << ") is false";
+}
+
+testing::AssertionResult isgtu_p(
+ const LFP &op1, const LFP &op2)
+{
+ if (op1.l_isgtu(op2))
+ return testing::AssertionSuccess()
+ << "L_ISGTU(" << op1 << "," << op2 << ") is true";
+ else
+ return testing::AssertionFailure()
+ << "L_ISGTU(" << op1 << "," << op2 << ") is false";
+}
+
+testing::AssertionResult ishis_p(
+ const LFP &op1, const LFP &op2)
+{
+ if (op1.l_ishis(op2))
+ return testing::AssertionSuccess()
+ << "L_ISHIS(" << op1 << "," << op2 << ") is true";
+ else
+ return testing::AssertionFailure()
+ << "L_ISHIS(" << op1 << "," << op2 << ") is false";
+}
+
+testing::AssertionResult isequ_p(
+ const LFP &op1, const LFP &op2)
+{
+ if (op1.l_isequ(op2))
+ return testing::AssertionSuccess()
+ << "L_ISEQU(" << op1 << "," << op2 << ") is true";
+ else
+ return testing::AssertionFailure()
+ << "L_ISEQU(" << op1 << "," << op2 << ") is false";
+}
+
+//----------------------------------------------------------------------
+// test data table for add/sub and compare
+//----------------------------------------------------------------------
+
+static const lfp_hl addsub_tab[][3] = {
+ // trivial idendity:
+ {{0 ,0 }, { 0,0 }, { 0,0}},
+ // with carry from fraction and sign change:
+ {{-1,0x80000000}, { 0,0x80000000}, { 0,0}},
+ // without carry from fraction
+ {{ 1,0x40000000}, { 1,0x40000000}, { 2,0x80000000}},
+ // with carry from fraction:
+ {{ 1,0xC0000000}, { 1,0xC0000000}, { 3,0x80000000}},
+ // with carry from fraction and sign change:
+ {{0x7FFFFFFF, 0x7FFFFFFF}, {0x7FFFFFFF,0x7FFFFFFF}, {0xFFFFFFFE,0xFFFFFFFE}},
+ // two tests w/o carry (used for l_fp<-->double):
+ {{0x55555555,0xAAAAAAAA}, {0x11111111,0x11111111}, {0x66666666,0xBBBBBBBB}},
+ {{0x55555555,0x55555555}, {0x11111111,0x11111111}, {0x66666666,0x66666666}},
+ // wide-range test, triggers compare trouble
+ {{0x80000000,0x00000001}, {0xFFFFFFFF,0xFFFFFFFE}, {0x7FFFFFFF,0xFFFFFFFF}}
+};
+static const size_t addsub_cnt(sizeof(addsub_tab)/sizeof(addsub_tab[0]));
+static const size_t addsub_tot(sizeof(addsub_tab)/sizeof(addsub_tab[0][0]));
+
+
+//----------------------------------------------------------------------
+// epsilon estimation for the precision of a conversion double --> l_fp
+//
+// The error estimation limit is as follows:
+// * The 'l_fp' fixed point fraction has 32 bits precision, so we allow
+// for the LSB to toggle by clamping the epsilon to be at least 2^(-31)
+//
+// * The double mantissa has a precsion 54 bits, so the other minimum is
+// dval * (2^(-53))
+//
+// The maximum of those two boundaries is used for the check.
+//
+// Note: once there are more than 54 bits between the highest and lowest
+// '1'-bit of the l_fp value, the roundtrip *will* create truncation
+// errors. This is an inherent property caused by the 54-bit mantissa of
+// the 'double' type.
+double eps(double d)
+{
+ return std::max<double>(ldexp(1.0, -31), ldexp(fabs(d), -53));
+}
+
+//----------------------------------------------------------------------
+// test addition
+//----------------------------------------------------------------------
+TEST_F(lfpTest, AdditionLR) {
+ for (size_t idx=0; idx < addsub_cnt; ++idx) {
+ LFP op1(addsub_tab[idx][0].h, addsub_tab[idx][0].l);
+ LFP op2(addsub_tab[idx][1].h, addsub_tab[idx][1].l);
+ LFP exp(addsub_tab[idx][2].h, addsub_tab[idx][2].l);
+ LFP res(op1 + op2);
+
+ ASSERT_EQ(exp, res);
+ }
+}
+
+TEST_F(lfpTest, AdditionRL) {
+ for (size_t idx=0; idx < addsub_cnt; ++idx) {
+ LFP op2(addsub_tab[idx][0].h, addsub_tab[idx][0].l);
+ LFP op1(addsub_tab[idx][1].h, addsub_tab[idx][1].l);
+ LFP exp(addsub_tab[idx][2].h, addsub_tab[idx][2].l);
+ LFP res(op1 + op2);
+
+ ASSERT_EQ(exp, res);
+ }
+}
+
+//----------------------------------------------------------------------
+// test subtraction
+//----------------------------------------------------------------------
+TEST_F(lfpTest, SubtractionLR) {
+ for (size_t idx=0; idx < addsub_cnt; ++idx) {
+ LFP op2(addsub_tab[idx][0].h, addsub_tab[idx][0].l);
+ LFP exp(addsub_tab[idx][1].h, addsub_tab[idx][1].l);
+ LFP op1(addsub_tab[idx][2].h, addsub_tab[idx][2].l);
+ LFP res(op1 - op2);
+
+ ASSERT_EQ(exp, res);
+ }
+}
+
+TEST_F(lfpTest, SubtractionRL) {
+ for (size_t idx=0; idx < addsub_cnt; ++idx) {
+ LFP exp(addsub_tab[idx][0].h, addsub_tab[idx][0].l);
+ LFP op2(addsub_tab[idx][1].h, addsub_tab[idx][1].l);
+ LFP op1(addsub_tab[idx][2].h, addsub_tab[idx][2].l);
+ LFP res(op1 - op2);
+
+ ASSERT_EQ(exp, res);
+ }
+}
+
+//----------------------------------------------------------------------
+// test negation
+//----------------------------------------------------------------------
+TEST_F(lfpTest, Negation) {
+ for (size_t idx=0; idx < addsub_cnt; ++idx) {
+ LFP op1(addsub_tab[idx][0].h, addsub_tab[idx][0].l);
+ LFP op2(-op1);
+ LFP sum(op1 + op2);
+
+ ASSERT_EQ(LFP(0,0), sum);
+ }
+}
+
+//----------------------------------------------------------------------
+// test absolute value
+//----------------------------------------------------------------------
+TEST_F(lfpTest, Absolute) {
+ for (size_t idx=0; idx < addsub_cnt; ++idx) {
+ LFP op1(addsub_tab[idx][0].h, addsub_tab[idx][0].l);
+ LFP op2(op1.abs());
+
+ ASSERT_TRUE(op2.signum() >= 0);
+
+ if (op1.signum() >= 0)
+ op1 -= op2;
+ else
+ op1 += op2;
+ ASSERT_EQ(LFP(0,0), op1);
+ }
+
+ // There is one special case we have to check: the minimum
+ // value cannot be negated, or, to be more precise, the
+ // negation reproduces the original pattern.
+ LFP minVal(0x80000000, 0x00000000);
+ LFP minAbs(minVal.abs());
+ ASSERT_EQ(-1, minVal.signum());
+ ASSERT_EQ(minVal, minAbs);
+}
+
+//----------------------------------------------------------------------
+// fp -> double -> fp rountrip test
+//----------------------------------------------------------------------
+TEST_F(lfpTest, FDF_RoundTrip) {
+ // since a l_fp has 64 bits in it's mantissa and a double has
+ // only 54 bits available (including the hidden '1') we have to
+ // make a few concessions on the roundtrip precision. The 'eps()'
+ // function makes an educated guess about the avilable precision
+ // and checks the difference in the two 'l_fp' values against
+ // that limit.
+ for (size_t idx=0; idx < addsub_cnt; ++idx) {
+ LFP op1(addsub_tab[idx][0].h, addsub_tab[idx][0].l);
+ double op2(op1);
+ LFP op3(op2);
+ // for manual checks only:
+ // std::cout << std::setprecision(16) << op2 << std::endl;
+ ASSERT_LE(fabs(op1-op3), eps(op2));
+ }
+}
+
+//----------------------------------------------------------------------
+// test the compare stuff
+//
+// This uses the local compare and checks if the operations using the
+// macros in 'ntp_fp.h' produce mathing results.
+// ----------------------------------------------------------------------
+TEST_F(lfpTest, SignedRelOps) {
+ const lfp_hl * tv(&addsub_tab[0][0]);
+ for (size_t lc=addsub_tot-1; lc; --lc,++tv) {
+ LFP op1(tv[0].h,tv[0].l);
+ LFP op2(tv[1].h,tv[1].l);
+ int cmp(op1.scmp(op2));
+
+ switch (cmp) {
+ case -1:
+ std::swap(op1, op2);
+ case 1:
+ EXPECT_TRUE (isgt_p(op1,op2));
+ EXPECT_FALSE(isgt_p(op2,op1));
+
+ EXPECT_TRUE (isgeq_p(op1,op2));
+ EXPECT_FALSE(isgeq_p(op2,op1));
+
+ EXPECT_FALSE(isequ_p(op1,op2));
+ EXPECT_FALSE(isequ_p(op2,op1));
+ break;
+ case 0:
+ EXPECT_FALSE(isgt_p(op1,op2));
+ EXPECT_FALSE(isgt_p(op2,op1));
+
+ EXPECT_TRUE (isgeq_p(op1,op2));
+ EXPECT_TRUE (isgeq_p(op2,op1));
+
+ EXPECT_TRUE (isequ_p(op1,op2));
+ EXPECT_TRUE (isequ_p(op2,op1));
+ break;
+ default:
+ FAIL() << "unexpected SCMP result: " << cmp;
+ }
+ }
+}
+
+TEST_F(lfpTest, UnsignedRelOps) {
+ const lfp_hl * tv(&addsub_tab[0][0]);
+ for (size_t lc=addsub_tot-1; lc; --lc,++tv) {
+ LFP op1(tv[0].h,tv[0].l);
+ LFP op2(tv[1].h,tv[1].l);
+ int cmp(op1.ucmp(op2));
+
+ switch (cmp) {
+ case -1:
+ std::swap(op1, op2);
+ case 1:
+ EXPECT_TRUE (isgtu_p(op1,op2));
+ EXPECT_FALSE(isgtu_p(op2,op1));
+
+ EXPECT_TRUE (ishis_p(op1,op2));
+ EXPECT_FALSE(ishis_p(op2,op1));
+ break;
+ case 0:
+ EXPECT_FALSE(isgtu_p(op1,op2));
+ EXPECT_FALSE(isgtu_p(op2,op1));
+
+ EXPECT_TRUE (ishis_p(op1,op2));
+ EXPECT_TRUE (ishis_p(op2,op1));
+ break;
+ default:
+ FAIL() << "unexpected UCMP result: " << cmp;
+ }
+ }
+}
+
+//----------------------------------------------------------------------
+// that's all folks... but feel free to add things!
+//----------------------------------------------------------------------