libdpf/test/tests/constant_lut_test.cpp

721 lines
27 KiB
C++
Raw Normal View History

#include <gtest/gtest.h>
#include "dpf.hpp"
#include "grotto/constant_lut.hpp"
#include "grotto/prefix_parity.hpp"
#include <array>
#include <cmath>
#include <cstdint>
#include <limits>
#include <random>
#include <string>
#include <vector>
namespace
{
using grotto::exact_constant;
using u128 = unsigned __int128;
const exact_constant kAll[] = {
exact_constant::signum,
exact_constant::positive,
exact_constant::negative,
exact_constant::nonneg,
exact_constant::nonpos,
exact_constant::zero,
exact_constant::nonzero,
exact_constant::ilogb,
exact_constant::ceil_ilogb,
exact_constant::ilog10,
exact_constant::clz,
exact_constant::clrsb,
};
const char * name_of(exact_constant which)
{
switch (which)
{
case exact_constant::signum: return "signum";
case exact_constant::positive: return "positive";
case exact_constant::negative: return "negative";
case exact_constant::nonneg: return "nonneg";
case exact_constant::nonpos: return "nonpos";
case exact_constant::zero: return "zero";
case exact_constant::nonzero: return "nonzero";
case exact_constant::ilogb: return "ilogb";
case exact_constant::ceil_ilogb: return "ceil_ilogb";
case exact_constant::ilog10: return "ilog10";
case exact_constant::clz: return "clz";
case exact_constant::clrsb: return "clrsb";
}
return "?";
}
unsigned __int128 magnitude(std::int64_t raw)
{
if (raw >= 0)
return static_cast<unsigned __int128>(raw);
if (raw == std::numeric_limits<std::int64_t>::min())
return u128{1} << 63;
return static_cast<unsigned __int128>(-raw);
}
int shift_log2(unsigned __int128 mag)
{
int lg = 0;
while (mag > 1)
{
mag >>= 1;
++lg;
}
return lg;
}
int shift_clz64(std::uint64_t t)
{
int n = 0;
if (t == 0)
return 64;
while ((t & (std::uint64_t{1} << 63)) == 0)
{
t <<= 1;
++n;
}
return n;
}
bool ge_pow10(unsigned __int128 mag, int k, unsigned fractional_bits)
{
if (k >= 0)
{
unsigned __int128 thresh = 1;
for (int i = 0; i < k; ++i)
{
if (thresh > (~u128{0}) / 10)
return false;
thresh *= 10;
}
if (fractional_bits >= 128
|| thresh > (~u128{0} >> fractional_bits))
return false;
return mag >= (thresh << fractional_bits);
}
unsigned __int128 scaled = mag;
for (int i = 0; i < -k; ++i)
{
if (scaled > (~u128{0}) / 10)
return true;
scaled *= 10;
}
return fractional_bits < 128 && scaled >= (u128{1} << fractional_bits);
}
std::int64_t toward_zero(std::int64_t raw, unsigned fractional_bits)
{
const unsigned __int128 mag = magnitude(raw);
const unsigned __int128 q = mag >> fractional_bits;
if (raw >= 0)
return static_cast<std::int64_t>(q);
if (q > static_cast<unsigned __int128>(std::numeric_limits<std::int64_t>::max()))
return std::numeric_limits<std::int64_t>::min();
return -static_cast<std::int64_t>(q);
}
// Independent of grotto::detail: shift loops and the xor form of clrsb,
// not the builtins the LUT builder calls.
std::int64_t reference(exact_constant which, std::int64_t raw, unsigned fractional_bits)
{
const unsigned __int128 mag = magnitude(raw);
switch (which)
{
case exact_constant::signum:
return raw < 0 ? -1 : (raw > 0 ? 1 : 0);
case exact_constant::positive:
return raw > 0;
case exact_constant::negative:
return raw < 0;
case exact_constant::nonneg:
return raw >= 0;
case exact_constant::nonpos:
return raw <= 0;
case exact_constant::zero:
return raw == 0;
case exact_constant::nonzero:
return raw != 0;
case exact_constant::ilogb:
if (mag == 0)
return -64;
if (fractional_bits >= 64
&& mag <= (u128{1} << (fractional_bits - 64)))
return -64;
return shift_log2(mag) - static_cast<std::int64_t>(fractional_bits);
case exact_constant::ceil_ilogb:
if (mag == 0)
return -64;
if (fractional_bits >= 64
&& mag <= (u128{1} << (fractional_bits - 64)))
return -64;
{
const auto floor_exp = shift_log2(mag) - static_cast<std::int64_t>(fractional_bits);
const bool power = (mag & (mag - 1)) == 0;
return power ? floor_exp : floor_exp + 1;
}
case exact_constant::ilog10:
if (mag == 0 || !ge_pow10(mag, -19, fractional_bits))
return -19;
{
int k = -19;
while (k < 40 && ge_pow10(mag, k + 1, fractional_bits))
++k;
return k;
}
case exact_constant::clz:
if (raw < 0)
return 0;
if (fractional_bits >= 128 || mag < (u128{1} << fractional_bits))
return 64;
return shift_clz64(static_cast<std::uint64_t>(mag >> fractional_bits));
case exact_constant::clrsb:
{
const std::int64_t trunc = toward_zero(raw, fractional_bits);
const std::uint64_t u = static_cast<std::uint64_t>(trunc);
const std::uint64_t y = u ^ static_cast<std::uint64_t>(trunc >> 1);
if (y == 0)
return 63;
return shift_clz64(y) - 1;
}
}
return 0;
}
template <typename Raw>
void expect_partition(const grotto::constant_lut<Raw> & lut)
{
using lim = std::numeric_limits<Raw>;
ASSERT_FALSE(lut.bounds.empty());
ASSERT_EQ(lut.bounds.size(), lut.values.size());
EXPECT_EQ(lut.bounds.front(), lim::min());
for (std::size_t i = 1; i < lut.bounds.size(); ++i)
{
EXPECT_LT(lut.bounds[i - 1], lut.bounds[i]);
EXPECT_NE(lut.values[i - 1], lut.values[i]);
EXPECT_EQ(lut(static_cast<Raw>(lut.bounds[i] - 1)), lut.values[i - 1]);
}
EXPECT_EQ(lut(lim::max()), lut.values.back());
for (std::size_t i = 0; i < lut.bounds.size(); ++i)
EXPECT_EQ(lut(lut.bounds[i]), lut.values[i]);
}
template <typename Raw, std::size_t N>
bool segment_product_matches(const grotto::constant_lut<Raw> & lut, Raw alpha)
{
std::array<Raw, N> ends{};
for (std::size_t i = 0; i < N; ++i)
ends[i] = lut.bounds[i];
auto [k0, k1] = dpf::make_dpf(alpha, dpf::bit::one);
const auto s0 = grotto::segment_parities(k0, ends);
const auto s1 = grotto::segment_parities(k1, ends);
__int128 a0 = 0;
__int128 a1 = 0;
__int128 u0 = 0;
__int128 u1 = 0;
int hot = 0;
int hot_at = -1;
for (std::size_t i = 0; i < N; ++i)
{
const int b0 = s0[i] ? 1 : 0;
const int b1 = s1[i] ? 1 : 0;
a0 += __int128(lut.values[i]) * b0;
a1 += __int128(lut.values[i]) * b1;
u0 += b0;
u1 += b1;
if ((b0 ^ b1) != 0)
{
++hot;
hot_at = static_cast<int>(i);
}
}
if (hot != 1)
return false;
const std::int64_t opened = static_cast<std::int64_t>((u0 - u1) * (a0 - a1));
return opened == lut.values[static_cast<std::size_t>(hot_at)]
&& opened == lut(alpha);
}
template <std::size_t N>
bool dispatch_segments(const grotto::constant_lut<std::int8_t> & lut, std::int8_t alpha)
{
if (lut.bounds.size() == N)
return segment_product_matches<std::int8_t, N>(lut, alpha);
if constexpr (N > 1)
return dispatch_segments<N - 1>(lut, alpha);
return false;
}
} // namespace
TEST(ConstantLut, RejectsFractionalBitsPastTheRawWidth)
{
EXPECT_THROW(grotto::make_exact_constant_lut<std::int8_t>(exact_constant::signum, 9),
std::invalid_argument);
EXPECT_THROW(grotto::make_exact_constant_lut<std::int64_t>(exact_constant::clz, 65),
std::invalid_argument);
}
TEST(ConstantLut, PaperPartCountsOnInt64With16FractionalBits)
{
// Appendix D, 64-bit fixed point, 16 fractional bits.
// `zero`, `nonzero`, and `ilog10` are counted after the two end pieces
// join across the wrap (they hold the same constant). `clrsb` is the
// linear count the generator emits; joining its equal ends would make 94.
struct row
{
exact_constant which;
std::size_t linear;
std::size_t wrapped;
};
const row rows[] = {
{exact_constant::signum, 3, 3},
{exact_constant::positive, 2, 2},
{exact_constant::negative, 2, 2},
{exact_constant::nonneg, 2, 2},
{exact_constant::nonpos, 2, 2},
{exact_constant::zero, 3, 2},
{exact_constant::nonzero, 3, 2},
{exact_constant::ilogb, 128, 128},
{exact_constant::ilog10, 41, 40},
{exact_constant::clz, 49, 49},
{exact_constant::clrsb, 95, 94},
};
for (const row & r : rows)
{
const auto lut = grotto::make_exact_constant_lut<std::int64_t>(r.which, 16);
expect_partition(lut);
EXPECT_EQ(lut.linear_parts(), r.linear) << name_of(r.which);
EXPECT_EQ(lut.wrapped_parts(), r.wrapped) << name_of(r.which);
}
}
TEST(ConstantLut, AnchorsMatchTheDefinitions)
{
const auto sgn = grotto::make_exact_constant_lut<std::int64_t>(exact_constant::signum, 16);
EXPECT_EQ(sgn(0), 0);
EXPECT_EQ(sgn(-1), -1);
EXPECT_EQ(sgn(1), 1);
EXPECT_EQ(sgn(std::numeric_limits<std::int64_t>::min()), -1);
EXPECT_EQ(sgn(std::numeric_limits<std::int64_t>::max()), 1);
const auto pos = grotto::make_exact_constant_lut<std::int64_t>(exact_constant::positive, 0);
const auto neg = grotto::make_exact_constant_lut<std::int64_t>(exact_constant::negative, 0);
const auto nn = grotto::make_exact_constant_lut<std::int64_t>(exact_constant::nonneg, 0);
const auto np = grotto::make_exact_constant_lut<std::int64_t>(exact_constant::nonpos, 0);
const auto z = grotto::make_exact_constant_lut<std::int64_t>(exact_constant::zero, 0);
const auto nz = grotto::make_exact_constant_lut<std::int64_t>(exact_constant::nonzero, 0);
EXPECT_EQ(pos(0), 0);
EXPECT_EQ(pos(1), 1);
EXPECT_EQ(pos(-1), 0);
EXPECT_EQ(neg(0), 0);
EXPECT_EQ(neg(-1), 1);
EXPECT_EQ(nn(0), 1);
EXPECT_EQ(nn(-1), 0);
EXPECT_EQ(np(0), 1);
EXPECT_EQ(np(1), 0);
EXPECT_EQ(z(0), 1);
EXPECT_EQ(z(1), 0);
EXPECT_EQ(z(-1), 0);
EXPECT_EQ(nz(0), 0);
EXPECT_EQ(nz(1), 1);
const auto ilogb = grotto::make_exact_constant_lut<std::int64_t>(exact_constant::ilogb, 16);
EXPECT_EQ(ilogb(0), -64);
EXPECT_EQ(ilogb(1), -16);
EXPECT_EQ(ilogb(-1), -16);
EXPECT_EQ(ilogb(65536), 0);
EXPECT_EQ(ilogb(-65536), 0);
EXPECT_EQ(ilogb(std::numeric_limits<std::int64_t>::min()), 47);
const auto ilog10 = grotto::make_exact_constant_lut<std::int64_t>(exact_constant::ilog10, 16);
EXPECT_EQ(ilog10(0), -19);
EXPECT_EQ(ilog10(1), -5);
EXPECT_EQ(ilog10(-1), -5);
EXPECT_EQ(ilog10(65536), 0);
EXPECT_EQ(ilog10(655360), 1);
const auto clz = grotto::make_exact_constant_lut<std::int64_t>(exact_constant::clz, 16);
EXPECT_EQ(clz(0), 64);
EXPECT_EQ(clz(1), 64);
EXPECT_EQ(clz(65535), 64);
EXPECT_EQ(clz(65536), 63);
EXPECT_EQ(clz(131072), 62);
EXPECT_EQ(clz(-1), 0);
EXPECT_EQ(clz(std::int64_t{1} << 62), 17);
const auto clrsb = grotto::make_exact_constant_lut<std::int64_t>(exact_constant::clrsb, 16);
EXPECT_EQ(clrsb(0), 63);
EXPECT_EQ(clrsb(65536), 62);
EXPECT_EQ(clrsb(-65536), 63);
EXPECT_EQ(clrsb(std::numeric_limits<std::int64_t>::min()), 16);
}
TEST(ConstantLut, EveryInt8AndInt16Point)
{
const unsigned widths_f_8[] = {0, 1, 4, 7, 8};
for (unsigned fractional_bits : widths_f_8)
{
for (exact_constant which : kAll)
{
const auto lut = grotto::make_exact_constant_lut<std::int8_t>(which, fractional_bits);
expect_partition(lut);
for (int raw = -128; raw <= 127; ++raw)
{
const auto r = static_cast<std::int8_t>(raw);
EXPECT_EQ(lut(r), reference(which, r, fractional_bits))
<< name_of(which) << " F=" << fractional_bits << " raw=" << raw;
}
}
}
const unsigned widths_f_16[] = {0, 1, 4, 8, 12, 15, 16};
for (unsigned fractional_bits : widths_f_16)
{
for (exact_constant which : kAll)
{
const auto lut = grotto::make_exact_constant_lut<std::int16_t>(which, fractional_bits);
expect_partition(lut);
for (int raw = -32768; raw <= 32767; ++raw)
{
const auto r = static_cast<std::int16_t>(raw);
EXPECT_EQ(lut(r), reference(which, r, fractional_bits))
<< name_of(which) << " F=" << fractional_bits << " raw=" << raw;
}
}
}
}
TEST(ConstantLut, Int32AndInt64BoundariesAndSamples)
{
const unsigned widths_f_32[] = {0, 1, 8, 16, 31, 32};
const unsigned widths_f_64[] = {0, 1, 4, 8, 12, 16, 20, 32, 48, 63, 64};
std::mt19937 rng(0x108u);
for (exact_constant which : kAll)
{
for (unsigned fractional_bits : widths_f_32)
{
const auto lut = grotto::make_exact_constant_lut<std::int32_t>(which, fractional_bits);
expect_partition(lut);
for (std::int32_t raw : lut.bounds)
EXPECT_EQ(lut(raw), reference(which, raw, fractional_bits));
EXPECT_EQ(lut(0), reference(which, 0, fractional_bits));
EXPECT_EQ(lut(-1), reference(which, -1, fractional_bits));
EXPECT_EQ(lut(1), reference(which, 1, fractional_bits));
EXPECT_EQ(lut(std::numeric_limits<std::int32_t>::min()),
reference(which, std::numeric_limits<std::int32_t>::min(), fractional_bits));
EXPECT_EQ(lut(std::numeric_limits<std::int32_t>::max()),
reference(which, std::numeric_limits<std::int32_t>::max(), fractional_bits));
std::uniform_int_distribution<std::int32_t> dist;
for (int n = 0; n < 256; ++n)
{
const std::int32_t raw = dist(rng);
EXPECT_EQ(lut(raw), reference(which, raw, fractional_bits))
<< name_of(which) << " F=" << fractional_bits;
}
}
for (unsigned fractional_bits : widths_f_64)
{
const auto lut = grotto::make_exact_constant_lut<std::int64_t>(which, fractional_bits);
expect_partition(lut);
for (std::size_t i = 0; i < lut.bounds.size(); ++i)
{
const std::int64_t raw = lut.bounds[i];
EXPECT_EQ(lut(raw), reference(which, raw, fractional_bits))
<< name_of(which) << " F=" << fractional_bits << " bound " << i;
if (i + 1 < lut.bounds.size())
{
const std::int64_t before = static_cast<std::int64_t>(lut.bounds[i + 1] - 1);
EXPECT_EQ(lut(before), reference(which, before, fractional_bits));
}
}
std::uniform_int_distribution<std::int64_t> dist;
for (int n = 0; n < 128; ++n)
{
const std::int64_t raw = dist(rng);
EXPECT_EQ(lut(raw), reference(which, raw, fractional_bits))
<< name_of(which) << " F=" << fractional_bits << " raw=" << raw;
}
}
}
}
TEST(ConstantLut, LibmAgreesOnPowersOfTwo)
{
for (unsigned fractional_bits : {0u, 4u, 8u, 12u, 16u, 20u})
{
const auto ilogb = grotto::make_exact_constant_lut<std::int64_t>(
exact_constant::ilogb, fractional_bits);
const auto ilog10 = grotto::make_exact_constant_lut<std::int64_t>(
exact_constant::ilog10, fractional_bits);
for (int k = 0; k <= 62; ++k)
{
const std::int64_t raw = std::int64_t{1} << k;
const double x = std::ldexp(static_cast<double>(raw), -static_cast<int>(fractional_bits));
if (x == 0.0 || !std::isfinite(x))
continue;
EXPECT_EQ(ilogb(raw), std::ilogb(x)) << "F=" << fractional_bits << " k=" << k;
EXPECT_EQ(ilogb(-raw), std::ilogb(-x)) << "F=" << fractional_bits << " k=" << k;
const int from_log10 = static_cast<int>(std::floor(std::log10(std::fabs(x))));
EXPECT_EQ(ilog10(raw), from_log10) << "F=" << fractional_bits << " k=" << k;
EXPECT_EQ(ilog10(-raw), from_log10) << "F=" << fractional_bits << " k=" << k;
}
}
}
TEST(ConstantLut, PartCountsScaleWithTheDomain)
{
// clz pieces: one negative piece, the [0, 1) piece, then one piece per
// power of two that still fits. For a 64-bit word and F fractional bits
// with F <= 62 that is 63 - F + 2.
for (unsigned fractional_bits : {0u, 4u, 8u, 16u, 20u, 32u})
{
const auto clz = grotto::make_exact_constant_lut<std::int64_t>(
exact_constant::clz, fractional_bits);
EXPECT_EQ(clz.linear_parts(), 63u - fractional_bits + 2u) << fractional_bits;
}
// No positive power of two fits once every value is strictly inside (-1, 1).
EXPECT_EQ(grotto::make_exact_constant_lut<std::int64_t>(exact_constant::clz, 63).linear_parts(), 2u);
EXPECT_EQ(grotto::make_exact_constant_lut<std::int64_t>(exact_constant::clz, 64).linear_parts(), 2u);
// ilogb splits at every raw power of two, on both sides, plus zero: 128
// pieces for every fractional width that still sees the whole int64 word.
for (unsigned fractional_bits : {0u, 4u, 8u, 12u, 16u, 20u, 32u, 48u, 63u})
{
EXPECT_EQ(grotto::make_exact_constant_lut<std::int64_t>(
exact_constant::ilogb, fractional_bits).linear_parts(), 128u)
<< fractional_bits;
}
// Sign tests do not depend on the fractional width.
for (unsigned fractional_bits : {0u, 7u, 15u, 16u})
{
EXPECT_EQ(grotto::make_exact_constant_lut<std::int16_t>(
exact_constant::signum, fractional_bits).linear_parts(), 3u);
EXPECT_EQ(grotto::make_exact_constant_lut<std::int16_t>(
exact_constant::zero, fractional_bits).wrapped_parts(), 2u);
}
for (unsigned fractional_bits : {0u, 16u, 31u, 32u})
{
EXPECT_EQ(grotto::make_exact_constant_lut<std::int32_t>(
exact_constant::positive, fractional_bits).linear_parts(), 2u);
EXPECT_EQ(grotto::make_exact_constant_lut<std::int64_t>(
exact_constant::signum, fractional_bits).linear_parts(), 3u);
}
}
TEST(ConstantLut, OneProgramCoversEveryWidthAndPrecision)
{
// Sign programs are three constants. Projecting them onto any word yields
// the same piece values, and the direct evaluator matches the table.
for (unsigned fractional_bits : {0u, 4u, 8u})
{
const auto narrow = grotto::make_exact_constant_lut<std::int8_t>(
exact_constant::signum, fractional_bits);
const auto wide = grotto::make_exact_constant_lut<std::int64_t>(
exact_constant::signum, fractional_bits);
ASSERT_EQ(narrow.values, wide.values);
EXPECT_EQ(narrow.values, (std::vector<std::int64_t>{-1, 0, 1}));
EXPECT_EQ(grotto::evaluate_exact<exact_constant::signum>(std::int8_t{-5}, fractional_bits),
narrow(-5));
EXPECT_EQ(grotto::evaluate_exact<exact_constant::signum>(std::int64_t{5}, fractional_bits),
wide(5));
}
// ilogb is floor(log2(|raw|)) - F. Changing F only shifts that one exponent.
EXPECT_EQ(grotto::evaluate_exact<exact_constant::ilogb>(std::int64_t{1} << 20, 4u),
grotto::evaluate_exact<exact_constant::ilogb>(std::int64_t{1} << 20, 8u) + 4);
// ilog10's positive unit boundary is 10^0 from the shared pow10 table,
// placed at raw = 2^F.
for (unsigned fractional_bits : {0u, 4u, 8u, 16u})
{
const auto lut = grotto::make_exact_constant_lut<std::int64_t>(
exact_constant::ilog10, fractional_bits);
const std::int64_t unit = std::int64_t{1} << fractional_bits;
EXPECT_EQ(lut(unit), 0) << fractional_bits;
if (unit > 1)
EXPECT_LT(lut(static_cast<std::int64_t>(unit - 1)), 0) << fractional_bits;
EXPECT_EQ(grotto::evaluate_exact<exact_constant::ilog10>(unit, fractional_bits), lut(unit));
}
// clz reads the integer part. Shifting the raw word and the fractional
// width by the same amount does not change the count.
EXPECT_EQ(grotto::evaluate_exact<exact_constant::clz>(std::int64_t{1} << 8, 0u),
grotto::evaluate_exact<exact_constant::clz>(std::int64_t{1} << 12, 4u));
EXPECT_EQ(grotto::evaluate_exact<exact_constant::clrsb>(std::int64_t{1} << 8, 0u),
grotto::evaluate_exact<exact_constant::clrsb>(std::int64_t{1} << 12, 4u));
const unsigned fractional_bits = 4;
for (exact_constant which : kAll)
{
const auto lut = grotto::make_exact_constant_lut<std::int16_t>(which, fractional_bits);
for (int raw = -32768; raw <= 32767; raw += 17)
{
const auto r = static_cast<std::int16_t>(raw);
std::int64_t fast = 0;
switch (which)
{
case exact_constant::signum:
fast = grotto::evaluate_exact<exact_constant::signum>(r, fractional_bits); break;
case exact_constant::positive:
fast = grotto::evaluate_exact<exact_constant::positive>(r, fractional_bits); break;
case exact_constant::negative:
fast = grotto::evaluate_exact<exact_constant::negative>(r, fractional_bits); break;
case exact_constant::nonneg:
fast = grotto::evaluate_exact<exact_constant::nonneg>(r, fractional_bits); break;
case exact_constant::nonpos:
fast = grotto::evaluate_exact<exact_constant::nonpos>(r, fractional_bits); break;
case exact_constant::zero:
fast = grotto::evaluate_exact<exact_constant::zero>(r, fractional_bits); break;
case exact_constant::nonzero:
fast = grotto::evaluate_exact<exact_constant::nonzero>(r, fractional_bits); break;
case exact_constant::ilogb:
fast = grotto::evaluate_exact<exact_constant::ilogb>(r, fractional_bits); break;
case exact_constant::ceil_ilogb:
fast = grotto::evaluate_exact<exact_constant::ceil_ilogb>(r, fractional_bits); break;
case exact_constant::ilog10:
fast = grotto::evaluate_exact<exact_constant::ilog10>(r, fractional_bits); break;
case exact_constant::clz:
fast = grotto::evaluate_exact<exact_constant::clz>(r, fractional_bits); break;
case exact_constant::clrsb:
fast = grotto::evaluate_exact<exact_constant::clrsb>(r, fractional_bits); break;
}
EXPECT_EQ(fast, lut(r)) << name_of(which) << " raw=" << raw;
}
}
}
TEST(ConstantLut, SegmentParitiesRecoverEveryInt16Sign)
{
const exact_constant signs[] = {
exact_constant::signum,
exact_constant::positive,
exact_constant::negative,
exact_constant::nonneg,
exact_constant::nonpos,
exact_constant::zero,
exact_constant::nonzero,
};
for (unsigned fractional_bits : {0u, 8u, 15u})
{
for (exact_constant which : signs)
{
const auto lut = grotto::make_exact_constant_lut<std::int16_t>(which, fractional_bits);
ASSERT_LE(lut.bounds.size(), 3u);
for (int raw = -32768; raw <= 32767; ++raw)
{
const auto alpha = static_cast<std::int16_t>(raw);
const bool ok = lut.bounds.size() == 2
? segment_product_matches<std::int16_t, 2>(lut, alpha)
: segment_product_matches<std::int16_t, 3>(lut, alpha);
EXPECT_TRUE(ok) << name_of(which) << " F=" << fractional_bits
<< " alpha=" << raw;
}
}
}
}
TEST(ConstantLut, SegmentParitiesRecoverEveryInt8Point)
{
const unsigned widths[] = {0u, 1u, 4u, 7u};
for (unsigned fractional_bits : widths)
{
for (exact_constant which : kAll)
{
const auto lut = grotto::make_exact_constant_lut<std::int8_t>(which, fractional_bits);
ASSERT_LE(lut.bounds.size(), 40u) << name_of(which);
for (int raw = -128; raw <= 127; ++raw)
{
const auto alpha = static_cast<std::int8_t>(raw);
EXPECT_TRUE(dispatch_segments<40>(lut, alpha))
<< name_of(which) << " F=" << fractional_bits << " alpha=" << raw
<< " parts=" << lut.bounds.size();
}
}
}
}
std::int64_t ref_floor_div(std::int64_t n, std::int64_t d)
{
if (n >= 0)
return n / d;
const __int128 neg = -static_cast<__int128>(n);
return static_cast<std::int64_t>(-((neg + d - 1) / d));
}
std::int64_t ref_quot(std::int64_t raw, std::int64_t modulus, std::int64_t low, std::int64_t high)
{
const std::int64_t clipped = raw < low ? low : (raw > high ? high : raw);
return ref_floor_div(clipped, modulus);
}
TEST(ConstantLut, CeilLogStepsUpOffPowersOfTwo)
{
const auto lut = grotto::make_exact_constant_lut<std::int64_t>(exact_constant::ceil_ilogb, 0);
expect_partition(lut);
EXPECT_EQ(lut(0), -64);
EXPECT_EQ(lut(1), 0);
EXPECT_EQ(lut(2), 1);
EXPECT_EQ(lut(3), 2);
EXPECT_EQ(lut(4), 2);
EXPECT_EQ(lut(-3), 2);
EXPECT_EQ(lut(-4), 2);
// The same real value, written with 4 more fractional bits, has the same ceil log.
EXPECT_EQ(grotto::evaluate_exact<exact_constant::ceil_ilogb>(std::int64_t{3}, 0u),
grotto::evaluate_exact<exact_constant::ceil_ilogb>(std::int64_t{3} << 4, 4u));
}
TEST(ConstantLut, ClippedQuotientIntervalAndThreshold)
{
// quot(3, -4, 10) on the raw grid, including the clip tails.
const auto quot = grotto::make_clipped_quotient_lut<std::int16_t>(3, -4, 10);
expect_partition(quot);
for (int raw = -32768; raw <= 32767; ++raw)
{
const auto r = static_cast<std::int16_t>(raw);
EXPECT_EQ(quot(r), ref_quot(r, 3, -4, 10)) << raw;
}
EXPECT_EQ(quot(-5), ref_floor_div(-4, 3));
EXPECT_EQ(quot(11), ref_floor_div(10, 3));
EXPECT_EQ(quot(0), 0);
EXPECT_EQ(quot(3), 1);
EXPECT_EQ(quot(-3), -1);
// A mathematical step of 0.5 with 2 fractional bits is raw modulus 2.
const auto half = grotto::make_clipped_quotient_lut<std::int8_t>(2, -6, 6);
for (int raw = -128; raw <= 127; ++raw)
EXPECT_EQ(half(static_cast<std::int8_t>(raw)), ref_quot(raw, 2, -6, 6)) << raw;
const auto window = grotto::make_interval_lut<std::int8_t>(-2, 5);
expect_partition(window);
for (int raw = -128; raw <= 127; ++raw)
{
const auto r = static_cast<std::int8_t>(raw);
EXPECT_EQ(window(r), (r >= -2 && r <= 5) ? 1 : 0) << raw;
}
const auto ge = grotto::make_threshold_lut<std::int8_t>(-3, grotto::threshold_cmp::geq);
const auto lt = grotto::make_threshold_lut<std::int8_t>(-3, grotto::threshold_cmp::lt);
for (int raw = -128; raw <= 127; ++raw)
{
const auto r = static_cast<std::int8_t>(raw);
EXPECT_EQ(ge(r), r >= -3);
EXPECT_EQ(lt(r), r < -3);
}
EXPECT_THROW(grotto::make_clipped_quotient_lut<std::int16_t>(0, -1, 1), std::invalid_argument);
EXPECT_THROW(grotto::make_clipped_quotient_lut<std::int16_t>(1, 4, -4), std::invalid_argument);
EXPECT_THROW(grotto::make_clipped_quotient_lut<std::int32_t>(1, -100000, 100000),
std::invalid_argument);
EXPECT_THROW(grotto::make_interval_lut<std::int8_t>(2, -2), std::invalid_argument);
}