#include #include #include "grotto/principal_lut.hpp" #include #include #include namespace { struct sample { int which; unsigned k; std::int64_t raw; std::int64_t y; }; const sample kSamples[] = { #include "principal_samples.inc" }; long double series_coth_minus_inv(long double u) { const long double u2 = u * u; return u * (1.0L / 3.0L - u2 / 45.0L + 2.0L * u2 * u2 / 945.0L); } long double reference(grotto::principal which, unsigned k, long double x) { constexpr long double pi = 3.141592653589793238462643383279502884L; switch (which) { case grotto::principal::ln: return logl(x); case grotto::principal::exp: return expl(ldexpl(x, -13)); case grotto::principal::sin: return sinl(pi * x / 2); case grotto::principal::tanf: if (x == 0) return 1; { const long double z = pi * x / 4; return tanl(z) / z; } case grotto::principal::tang: if (x < ldexpl(1, -12)) { const long double z = pi * x / 4; const long double z2 = z * z; return -z / 3 - z2 * z / 45; } { const long double z = pi * x / 4; return 1 / tanl(z) - 1 / z; } case grotto::principal::sinh: return sinhl(ldexpl(x, -13)); case grotto::principal::cosh: return coshl(ldexpl(x, -13)); case grotto::principal::sqrt: return sqrtl(x); case grotto::principal::coth: if (x == 0) return 0; { const long double beta = 0.5L * logl(ldexpl(1, static_cast(k) + 1) + 1); const long double u = beta * x; if (u < 0.05L) return series_coth_minus_inv(u); return 1 / tanhl(u) - 1 / u; } case grotto::principal::sec: return 1 / cosl(pi * x / 4); case grotto::principal::gsec: if (x < ldexpl(1, -12)) { const long double z = pi * x / 4; const long double z2 = z * z; return z / 6 + 7 * z2 * z / 360; } { const long double z = pi * x / 4; return 1 / sinl(z) - 1 / z; } case grotto::principal::csch: if (x < ldexpl(1, -12)) { const long double x2 = x * x; return -x / 6 + 7 * x2 * x / 360; } return 1 / sinhl(x) - 1 / x; } return 0; } std::int64_t domain_left(grotto::principal which, unsigned k) { if (which == grotto::principal::ln || which == grotto::principal::sqrt) return std::int64_t{1} << (k - 1); return 0; } std::int64_t domain_right(unsigned k) { return std::int64_t{1} << k; } void expect_close(grotto::principal which, unsigned k, std::int64_t raw) { const auto y = grotto::eval_principal(which, k, raw); const long double x = ldexpl(static_cast(raw), -static_cast(k)); const long double truth = reference(which, k, x) * ldexpl(1, static_cast(k)); EXPECT_LE(fabsl(static_cast(y) - truth), 1.5L) << static_cast(which) << " k=" << k << " raw=" << raw; } } // namespace TEST(PrincipalLut, CoarsenedPieceCountsStayPut) { EXPECT_EQ(grotto::principal_parts(grotto::principal::ln, 8), grotto::principal_parts(grotto::principal::ln, 32)); EXPECT_EQ(grotto::principal_parts(grotto::principal::sqrt, 8), 393); EXPECT_EQ(grotto::principal_parts(grotto::principal::exp, 32), 2u); EXPECT_EQ(grotto::principal_parts(grotto::principal::sinh, 16), 1u); EXPECT_LT(grotto::principal_parts(grotto::principal::coth, 8), grotto::principal_parts(grotto::principal::coth, 32)); } TEST(PrincipalLut, EmbeddedHornerMatches) { for (const sample & point : kSamples) { const auto which = static_cast(point.which); EXPECT_EQ(grotto::eval_principal(which, point.k, point.raw), point.y) << point.which << " k=" << point.k << " raw=" << point.raw; } } TEST(PrincipalLut, WithinOneUlpOnTheDomain) { for (unsigned which_i = 0; which_i < 12; ++which_i) { const auto which = static_cast(which_i); for (unsigned k : {8u, 12u}) { const auto left = domain_left(which, k); const auto right = domain_right(k); for (std::int64_t raw = left; raw <= right; ++raw) expect_close(which, k, raw); } for (unsigned k : {16u, 20u, 24u, 28u, 32u}) { const auto left = domain_left(which, k); const auto right = domain_right(k); const std::int64_t step = std::max(1, (right - left) / 256); expect_close(which, k, left); expect_close(which, k, right); for (std::int64_t raw = left; raw < right; raw += step) expect_close(which, k, raw); } } } TEST(PrincipalLut, RejectsBadPrecisionAndDomain) { EXPECT_THROW(grotto::eval_principal(grotto::principal::ln, 7, 64), std::invalid_argument); EXPECT_THROW(grotto::eval_principal(grotto::principal::ln, 8, 0), std::out_of_range); EXPECT_THROW(grotto::eval_principal(grotto::principal::exp, 8, -1), std::out_of_range); EXPECT_THROW(grotto::eval_principal(grotto::principal::sin, 8, 257), std::out_of_range); } long double recip_reference(grotto::principal which, long double x) { switch (which) { case grotto::principal::inv: return 1.0L / x; case grotto::principal::rsqrt: return 1.0L / sqrtl(x); case grotto::principal::invsq: return 1.0L / (x * x); default: return 0; } } void expect_recip(grotto::principal which, unsigned k, std::int64_t raw) { const auto y = grotto::eval_principal(which, k, raw); const long double x = ldexpl(static_cast(raw), -static_cast(k)); const long double truth = recip_reference(which, x) * ldexpl(1.0L, static_cast(k)); EXPECT_LE(fabsl(static_cast(y) - truth), 1.5L) << static_cast(which) << " k=" << k << " raw=" << raw << " y=" << y; } TEST(PrincipalLut, ReciprocalPieceCounts) { const unsigned inv[] = {2u, 3u, 5u, 9u, 18u, 35u, 69u}; const unsigned rsqrt[] = {1u, 2u, 3u, 6u, 12u, 24u, 48u}; const unsigned invsq[] = {2u, 4u, 8u, 15u, 29u, 57u, 113u}; unsigned slot = 0; for (unsigned k : grotto::principal_precisions) { EXPECT_EQ(grotto::principal_parts(grotto::principal::inv, k), inv[slot]); EXPECT_EQ(grotto::principal_parts(grotto::principal::rsqrt, k), rsqrt[slot]); EXPECT_EQ(grotto::principal_parts(grotto::principal::invsq, k), invsq[slot]); ++slot; } } TEST(PrincipalLut, ReciprocalWithinOneAndAHalfUlp) { const grotto::principal maps[] = { grotto::principal::inv, grotto::principal::rsqrt, grotto::principal::invsq, }; for (const auto which : maps) { for (unsigned k : {8u, 12u, 16u}) { const auto left = std::int64_t{1} << (k - 1); const auto right = std::int64_t{1} << k; for (std::int64_t raw = left; raw <= right; ++raw) expect_recip(which, k, raw); } for (unsigned k : {20u, 24u, 28u, 32u}) { const auto left = std::int64_t{1} << (k - 1); const auto right = std::int64_t{1} << k; const std::int64_t step = std::max(1, (right - left) / 4096); expect_recip(which, k, left); expect_recip(which, k, right); for (std::int64_t raw = left; raw < right; raw += step) expect_recip(which, k, raw); } } } TEST(PrincipalLut, ReciprocalRejectsOutsidePrincipalInterval) { EXPECT_THROW(grotto::eval_principal(grotto::principal::inv, 7, 128), std::invalid_argument); EXPECT_THROW(grotto::eval_principal(grotto::principal::inv, 8, 127), std::out_of_range); EXPECT_THROW(grotto::eval_principal(grotto::principal::inv, 8, 257), std::out_of_range); EXPECT_THROW(grotto::eval_principal(grotto::principal::rsqrt, 12, 2047), std::out_of_range); EXPECT_THROW(grotto::eval_principal(grotto::principal::invsq, 16, (std::int64_t{1} << 16) + 1), std::out_of_range); EXPECT_THROW(grotto::eval_principal(grotto::principal::invsq, 8, -1), std::out_of_range); EXPECT_NO_THROW(grotto::eval_principal(grotto::principal::inv, 8, 128)); EXPECT_NO_THROW(grotto::eval_principal(grotto::principal::rsqrt, 8, 256)); EXPECT_NO_THROW(grotto::eval_principal(grotto::principal::invsq, 12, 4096)); }