#include #include #include "dpf.hpp" #include "grotto/constant_lut.hpp" #include "grotto/prefix_parity.hpp" #include #include #include #include #include #include #include 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(raw); if (raw == std::numeric_limits::min()) return u128{1} << 63; return static_cast(-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(q); if (q > static_cast(std::numeric_limits::max())) return std::numeric_limits::min(); return -static_cast(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(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(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(mag >> fractional_bits)); case exact_constant::clrsb: { const std::int64_t trunc = toward_zero(raw, fractional_bits); const std::uint64_t u = static_cast(trunc); const std::uint64_t y = u ^ static_cast(trunc >> 1); if (y == 0) return 63; return shift_clz64(y) - 1; } } return 0; } template void expect_partition(const grotto::constant_lut & lut) { using lim = std::numeric_limits; 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(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 bool segment_product_matches(const grotto::constant_lut & lut, Raw alpha) { std::array 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(i); } } if (hot != 1) return false; const std::int64_t opened = static_cast((u0 - u1) * (a0 - a1)); return opened == lut.values[static_cast(hot_at)] && opened == lut(alpha); } template bool dispatch_segments(const grotto::constant_lut & lut, std::int8_t alpha) { if (lut.bounds.size() == N) return segment_product_matches(lut, alpha); if constexpr (N > 1) return dispatch_segments(lut, alpha); return false; } } // namespace TEST(ConstantLut, RejectsFractionalBitsPastTheRawWidth) { EXPECT_THROW(grotto::make_exact_constant_lut(exact_constant::signum, 9), std::invalid_argument); EXPECT_THROW(grotto::make_exact_constant_lut(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(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(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::min()), -1); EXPECT_EQ(sgn(std::numeric_limits::max()), 1); const auto pos = grotto::make_exact_constant_lut(exact_constant::positive, 0); const auto neg = grotto::make_exact_constant_lut(exact_constant::negative, 0); const auto nn = grotto::make_exact_constant_lut(exact_constant::nonneg, 0); const auto np = grotto::make_exact_constant_lut(exact_constant::nonpos, 0); const auto z = grotto::make_exact_constant_lut(exact_constant::zero, 0); const auto nz = grotto::make_exact_constant_lut(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(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::min()), 47); const auto ilog10 = grotto::make_exact_constant_lut(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(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(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::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(which, fractional_bits); expect_partition(lut); for (int raw = -128; raw <= 127; ++raw) { const auto r = static_cast(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(which, fractional_bits); expect_partition(lut); for (int raw = -32768; raw <= 32767; ++raw) { const auto r = static_cast(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(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::min()), reference(which, std::numeric_limits::min(), fractional_bits)); EXPECT_EQ(lut(std::numeric_limits::max()), reference(which, std::numeric_limits::max(), fractional_bits)); std::uniform_int_distribution 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(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(lut.bounds[i + 1] - 1); EXPECT_EQ(lut(before), reference(which, before, fractional_bits)); } } std::uniform_int_distribution 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( exact_constant::ilogb, fractional_bits); const auto ilog10 = grotto::make_exact_constant_lut( 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(raw), -static_cast(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(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( 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(exact_constant::clz, 63).linear_parts(), 2u); EXPECT_EQ(grotto::make_exact_constant_lut(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( 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( exact_constant::signum, fractional_bits).linear_parts(), 3u); EXPECT_EQ(grotto::make_exact_constant_lut( exact_constant::zero, fractional_bits).wrapped_parts(), 2u); } for (unsigned fractional_bits : {0u, 16u, 31u, 32u}) { EXPECT_EQ(grotto::make_exact_constant_lut( exact_constant::positive, fractional_bits).linear_parts(), 2u); EXPECT_EQ(grotto::make_exact_constant_lut( 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( exact_constant::signum, fractional_bits); const auto wide = grotto::make_exact_constant_lut( exact_constant::signum, fractional_bits); ASSERT_EQ(narrow.values, wide.values); EXPECT_EQ(narrow.values, (std::vector{-1, 0, 1})); EXPECT_EQ(grotto::evaluate_exact(std::int8_t{-5}, fractional_bits), narrow(-5)); EXPECT_EQ(grotto::evaluate_exact(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(std::int64_t{1} << 20, 4u), grotto::evaluate_exact(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( 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(unit - 1)), 0) << fractional_bits; EXPECT_EQ(grotto::evaluate_exact(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(std::int64_t{1} << 8, 0u), grotto::evaluate_exact(std::int64_t{1} << 12, 4u)); EXPECT_EQ(grotto::evaluate_exact(std::int64_t{1} << 8, 0u), grotto::evaluate_exact(std::int64_t{1} << 12, 4u)); const unsigned fractional_bits = 4; for (exact_constant which : kAll) { const auto lut = grotto::make_exact_constant_lut(which, fractional_bits); for (int raw = -32768; raw <= 32767; raw += 17) { const auto r = static_cast(raw); std::int64_t fast = 0; switch (which) { case exact_constant::signum: fast = grotto::evaluate_exact(r, fractional_bits); break; case exact_constant::positive: fast = grotto::evaluate_exact(r, fractional_bits); break; case exact_constant::negative: fast = grotto::evaluate_exact(r, fractional_bits); break; case exact_constant::nonneg: fast = grotto::evaluate_exact(r, fractional_bits); break; case exact_constant::nonpos: fast = grotto::evaluate_exact(r, fractional_bits); break; case exact_constant::zero: fast = grotto::evaluate_exact(r, fractional_bits); break; case exact_constant::nonzero: fast = grotto::evaluate_exact(r, fractional_bits); break; case exact_constant::ilogb: fast = grotto::evaluate_exact(r, fractional_bits); break; case exact_constant::ceil_ilogb: fast = grotto::evaluate_exact(r, fractional_bits); break; case exact_constant::ilog10: fast = grotto::evaluate_exact(r, fractional_bits); break; case exact_constant::clz: fast = grotto::evaluate_exact(r, fractional_bits); break; case exact_constant::clrsb: fast = grotto::evaluate_exact(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(which, fractional_bits); ASSERT_LE(lut.bounds.size(), 3u); for (int raw = -32768; raw <= 32767; ++raw) { const auto alpha = static_cast(raw); const bool ok = lut.bounds.size() == 2 ? segment_product_matches(lut, alpha) : segment_product_matches(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(which, fractional_bits); ASSERT_LE(lut.bounds.size(), 40u) << name_of(which); for (int raw = -128; raw <= 127; ++raw) { const auto alpha = static_cast(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(-((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(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(std::int64_t{3}, 0u), grotto::evaluate_exact(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(3, -4, 10); expect_partition(quot); for (int raw = -32768; raw <= 32767; ++raw) { const auto r = static_cast(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(2, -6, 6); for (int raw = -128; raw <= 127; ++raw) EXPECT_EQ(half(static_cast(raw)), ref_quot(raw, 2, -6, 6)) << raw; const auto window = grotto::make_interval_lut(-2, 5); expect_partition(window); for (int raw = -128; raw <= 127; ++raw) { const auto r = static_cast(raw); EXPECT_EQ(window(r), (r >= -2 && r <= 5) ? 1 : 0) << raw; } const auto ge = grotto::make_threshold_lut(-3, grotto::threshold_cmp::geq); const auto lt = grotto::make_threshold_lut(-3, grotto::threshold_cmp::lt); for (int raw = -128; raw <= 127; ++raw) { const auto r = static_cast(raw); EXPECT_EQ(ge(r), r >= -3); EXPECT_EQ(lt(r), r < -3); } EXPECT_THROW(grotto::make_clipped_quotient_lut(0, -1, 1), std::invalid_argument); EXPECT_THROW(grotto::make_clipped_quotient_lut(1, 4, -4), std::invalid_argument); EXPECT_THROW(grotto::make_clipped_quotient_lut(1, -100000, 100000), std::invalid_argument); EXPECT_THROW(grotto::make_interval_lut(2, -2), std::invalid_argument); }