#include #include "dpf.hpp" #include "grotto/constant_lut.hpp" #include "grotto/prefix_parity.hpp" #include #include #include #include namespace { uint64_t opened(uint64_t a, uint64_t b, uint64_t mask) { return (a + b) & mask; } template int piece_containing(T alpha, const std::array & ends) { for (std::size_t i = 0; i + 1 < N; ++i) { if (alpha >= ends[i] && alpha < ends[i + 1]) return static_cast(i); } return static_cast(N - 1); } } // namespace TEST(SignedPrefix, MatchesComparisonEvalOnEveryUint8Point) { const uint8_t alpha = 0x3c; auto [k0, k1] = dpf::make_dpf(alpha, dpf::gt(uint64_t{1})); const uint64_t mask = k0.cmp().mask; std::array ends{0, 1, 10, alpha, 100, 200, 254, 255}; const auto p0 = grotto::signed_prefix_parities(k0, ends); const auto p1 = grotto::signed_prefix_parities(k1, ends); for (std::size_t i = 0; i < ends.size(); ++i) { const auto e0 = dpf::eval_point(dpf::cmp, k0, ends[i]); const auto e1 = dpf::eval_point(dpf::cmp, k1, ends[i]); EXPECT_EQ(p0[i], e0.raw()) << int(ends[i]); EXPECT_EQ(p1[i], e1.raw()) << int(ends[i]); EXPECT_EQ(opened(p0[i], p1[i], mask), ends[i] > alpha ? 1u : 0u); } } TEST(SignedPrefix, SegmentsAreOneHot) { const uint8_t alpha = 40; auto [k0, k1] = dpf::make_dpf(alpha, dpf::gt(uint64_t{1})); const uint64_t mask = k0.cmp().mask; const std::array ends{0, 1, 10, 40, 200}; const auto s0 = grotto::signed_segment_parities(k0, ends); const auto s1 = grotto::signed_segment_parities(k1, ends); const int hot = piece_containing(alpha, ends); uint64_t sum = 0; for (std::size_t i = 0; i < ends.size(); ++i) { const uint64_t bit = opened(s0[i], s1[i], mask); sum = (sum + bit) & mask; EXPECT_EQ(bit, i == static_cast(hot) ? 1u : 0u) << i; } EXPECT_EQ(sum, 1u); for (uint8_t a : {uint8_t{0}, uint8_t{1}, uint8_t{9}, uint8_t{200}, uint8_t{255}}) { auto [a0, a1] = dpf::make_dpf(a, dpf::gt(uint64_t{1})); const auto t0 = grotto::signed_segment_parities(a0, ends); const auto t1 = grotto::signed_segment_parities(a1, ends); const int where = piece_containing(a, ends); for (std::size_t i = 0; i < ends.size(); ++i) EXPECT_EQ(opened(t0[i], t1[i], mask), i == static_cast(where) ? 1u : 0u) << "alpha=" << int(a) << " piece=" << i; } } TEST(SignedPrefix, WholeDomainIsThePublicOne) { auto [k0, k1] = dpf::make_dpf(uint8_t{7}, dpf::gt(uint64_t{1})); const std::array ends{0}; const auto s0 = grotto::signed_segment_parities(k0, ends); const auto s1 = grotto::signed_segment_parities(k1, ends); EXPECT_EQ(opened(s0[0], s1[0], k0.cmp().mask), 1u); } TEST(SignedPrefix, SignumLutIsSignCorrect) { for (int alpha = -128; alpha <= 127; ++alpha) { const auto a = static_cast(alpha); auto [k0, k1] = dpf::make_dpf(a, dpf::gt(uint64_t{1})); const auto lut = grotto::make_exact_constant_lut( grotto::exact_constant::signum, 0); ASSERT_EQ(lut.bounds.size(), 3u); std::array ends{}; for (std::size_t i = 0; i < 3; ++i) ends[i] = lut.bounds[i]; const auto s0 = grotto::signed_segment_parities(k0, ends); const auto s1 = grotto::signed_segment_parities(k1, ends); uint64_t acc0 = 0; uint64_t acc1 = 0; for (std::size_t i = 0; i < 3; ++i) { const auto coef = static_cast(lut.values[i]); acc0 += s0[i] * coef; acc1 += s1[i] * coef; } const auto got = static_cast(acc0 + acc1); const int64_t want = alpha < 0 ? -1 : (alpha > 0 ? 1 : 0); EXPECT_EQ(got, want) << alpha; // The advice-bit lift is ±want. The DCF shares must not come out negated. if (want != 0) EXPECT_NE(got, -want) << alpha; } } TEST(SignedPrefix, SharedPrefixAgreesWithAFreshWalk) { const uint16_t alpha = 0x0100; auto [k0, k1] = dpf::make_dpf(alpha, dpf::gt(uint64_t{1})); // Consecutive knots share a long prefix; the cached sum must match a // one-knot walk. const std::array ends{0, 1, 0x0100, 0x0101}; const auto many = grotto::signed_prefix_parities(k0, ends); for (std::size_t i = 0; i < ends.size(); ++i) { const std::array one{ends[i]}; const auto alone = grotto::signed_prefix_parities(k0, one); EXPECT_EQ(many[i], alone[0]) << i; } (void)k1; } TEST(SignedPrefix, RejectsAKeyWithoutAComparison) { auto [k0, k1] = dpf::make_dpf(uint8_t{1}, dpf::bit::one); const std::array ends{0, 1}; EXPECT_THROW(grotto::signed_prefix_parities(k0, ends), std::invalid_argument); EXPECT_THROW(grotto::signed_segment_parities(k1, ends), std::invalid_argument); } template void expect_ilogb_segments(const grotto::constant_lut & lut, int8_t alpha) { if (lut.bounds.size() != N) return; 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::gt(uint64_t{1})); const uint64_t mask = k0.cmp().mask; const auto s0 = grotto::signed_segment_parities(k0, ends); const auto s1 = grotto::signed_segment_parities(k1, ends); uint64_t acc = 0; for (std::size_t i = 0; i < N; ++i) { const uint64_t bit = opened(s0[i], s1[i], mask); acc += bit * static_cast(lut.values[i]); } EXPECT_EQ(acc, static_cast(lut(alpha))) << int(alpha); } template void dispatch_ilogb(const grotto::constant_lut & lut, int8_t alpha, bool & matched) { if (lut.bounds.size() == N) { expect_ilogb_segments(lut, alpha); matched = true; return; } if constexpr (N > 1) dispatch_ilogb(lut, alpha, matched); } TEST(SignedPrefix, SegmentsRecoverIlogbInt8) { for (unsigned frac : {0u, 4u}) { const auto lut = grotto::make_exact_constant_lut( grotto::exact_constant::ilogb, frac); ASSERT_GE(lut.bounds.size(), 3u); ASSERT_LE(lut.bounds.size(), 40u); for (int v = -128; v <= 127; ++v) { bool matched = false; dispatch_ilogb<40>(lut, static_cast(v), matched); ASSERT_TRUE(matched) << lut.bounds.size(); } } }