171 lines
5.3 KiB
C++
171 lines
5.3 KiB
C++
#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);
|
|
}
|