libdpf/test/tests/principal_lut_test.cpp

253 lines
8.4 KiB
C++
Raw Normal View History

#include <gtest/gtest.h>
#include "grotto/principal_lut.hpp"
#include <cmath>
#include <cstdint>
#include <vector>
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<int>(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<long double>(raw), -static_cast<int>(k));
const long double truth = reference(which, k, x) * ldexpl(1, static_cast<int>(k));
EXPECT_LE(fabsl(static_cast<long double>(y) - truth), 1.5L) << static_cast<int>(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<grotto::principal>(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<grotto::principal>(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<std::int64_t>(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<long double>(raw), -static_cast<int>(k));
const long double truth = recip_reference(which, x) * ldexpl(1.0L, static_cast<int>(k));
EXPECT_LE(fabsl(static_cast<long double>(y) - truth), 1.5L)
<< static_cast<int>(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<std::int64_t>(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));
}