libdpf/test/tests/range_lut_test.cpp

247 lines
9.5 KiB
C++
Raw Normal View History

#include <gtest/gtest.h>
#include "grotto/range_lut.hpp"
#include <cmath>
#include <cstdint>
namespace
{
long double truth_of(grotto::reduced which, long double x)
{
switch (which)
{
case grotto::reduced::ln: return std::log(x);
case grotto::reduced::lg: return std::log2(x);
case grotto::reduced::log10: return std::log10(x);
case grotto::reduced::exp: return std::exp(x);
case grotto::reduced::exp2: return std::exp2(x);
case grotto::reduced::exp10: return std::exp(x * std::log(10.0L));
case grotto::reduced::sin: return std::sin(x);
case grotto::reduced::cos: return std::cos(x);
case grotto::reduced::tan: return std::tan(x);
case grotto::reduced::cot: return 1.0L / std::tan(x);
case grotto::reduced::sec: return 1.0L / std::cos(x);
case grotto::reduced::csc: return 1.0L / std::sin(x);
case grotto::reduced::sinh: return std::sinh(x);
case grotto::reduced::cosh: return std::cosh(x);
case grotto::reduced::tanh: return std::tanh(x);
case grotto::reduced::coth: return 1.0L / std::tanh(x);
case grotto::reduced::sech: return 1.0L / std::cosh(x);
case grotto::reduced::csch: return 1.0L / std::sinh(x);
case grotto::reduced::sqrt: return std::sqrt(x);
case grotto::reduced::inv: return 1.0L / x;
case grotto::reduced::rsqrt: return 1.0L / std::sqrt(x);
case grotto::reduced::invsq: return 1.0L / (x * x);
}
return 0;
}
const char * name_of(grotto::reduced which)
{
switch (which)
{
case grotto::reduced::ln: return "ln";
case grotto::reduced::lg: return "lg";
case grotto::reduced::log10: return "log10";
case grotto::reduced::exp: return "exp";
case grotto::reduced::exp2: return "exp2";
case grotto::reduced::exp10: return "exp10";
case grotto::reduced::sin: return "sin";
case grotto::reduced::cos: return "cos";
case grotto::reduced::tan: return "tan";
case grotto::reduced::cot: return "cot";
case grotto::reduced::sec: return "sec";
case grotto::reduced::csc: return "csc";
case grotto::reduced::sinh: return "sinh";
case grotto::reduced::cosh: return "cosh";
case grotto::reduced::tanh: return "tanh";
case grotto::reduced::coth: return "coth";
case grotto::reduced::sech: return "sech";
case grotto::reduced::csch: return "csch";
case grotto::reduced::sqrt: return "sqrt";
case grotto::reduced::inv: return "inv";
case grotto::reduced::rsqrt: return "rsqrt";
case grotto::reduced::invsq: return "invsq";
}
return "?";
}
std::int64_t raw_of(long double x, unsigned k)
{
const long double scaled = std::ldexp(x, static_cast<int>(k));
return std::llround(scaled);
}
void expect_ulps(grotto::reduced which, unsigned k, long double x, long double ulps)
{
const std::int64_t raw = raw_of(x, k);
std::int64_t got = 0;
ASSERT_NO_THROW(got = grotto::eval_reduced(which, k, raw))
<< name_of(which) << " k=" << k << " x=" << static_cast<double>(x);
const long double truth = truth_of(which, std::ldexp(static_cast<long double>(raw), -static_cast<int>(k)));
const long double want = truth * std::ldexp(1.0L, static_cast<int>(k));
EXPECT_LE(std::fabsl(static_cast<long double>(got) - want), ulps)
<< name_of(which) << " k=" << k << " x=" << static_cast<double>(x)
<< " got=" << got << " want=" << static_cast<double>(want);
}
bool near_odd_multiple_of_half_pi(long double x)
{
const long double turn = std::fmod(std::fabsl(x), 3.14159265358979323846L);
const long double dist = std::fmod(turn + 1.5707963267948966L, 3.14159265358979323846L);
const long double folded = dist > 1.5707963267948966L ? 3.14159265358979323846L - dist : dist;
return folded < 0.15L;
}
} // namespace
TEST(RangeLut, LogarithmsTrackLibmOnEveryPrecision)
{
const grotto::reduced maps[] = {
grotto::reduced::ln, grotto::reduced::lg, grotto::reduced::log10,
};
const long double samples[] = {
0.125L, 0.3L, 0.5L, 0.75L, 1.0L, 1.5L, 2.0L, 3.0L, 7.5L, 16.0L, 24.0L, 100.0L,
};
for (auto which : maps)
for (unsigned k : grotto::principal_precisions)
for (long double x : samples)
expect_ulps(which, k, x, 6.0L);
}
TEST(RangeLut, ExponentialsTrackLibmOnEveryPrecision)
{
const long double samples[] = {
-2.0L, -1.5L, -0.5L, -0.1L, 0.0L, 0.1L, 0.5L, 1.0L, 1.5L, 2.0L,
};
for (auto which : {grotto::reduced::exp, grotto::reduced::exp2})
for (unsigned k : grotto::principal_precisions)
for (long double x : samples)
expect_ulps(which, k, x, 16.0L);
for (unsigned k : grotto::principal_precisions)
{
for (long double x : {-0.9L, -0.25L, 0.0L, 0.25L, 0.9L})
expect_ulps(grotto::reduced::exp10, k, x, 24.0L);
// 10^q scales the absolute error of exp(f ln 10) by the integer power.
for (long double x : {-1.5L, 1.5L})
expect_ulps(grotto::reduced::exp10, k, x, 80.0L);
}
}
TEST(RangeLut, QuarterTurnTrigTracksLibm)
{
const long double samples[] = {
-3.5L, -2.2L, -1.2L, -0.7L, -0.3L, 0.0L, 0.2L, 0.4L, 0.7L, 1.0L, 1.2L, 2.5L, 3.5L,
};
for (auto which : {grotto::reduced::sin, grotto::reduced::cos})
for (unsigned k : grotto::principal_precisions)
for (long double x : samples)
expect_ulps(which, k, x, 3.0L);
for (auto which : {grotto::reduced::tan, grotto::reduced::sec})
for (unsigned k : grotto::principal_precisions)
for (long double x : samples)
{
if (near_odd_multiple_of_half_pi(x))
continue;
expect_ulps(which, k, x, 8.0L);
}
for (auto which : {grotto::reduced::cot, grotto::reduced::csc})
for (unsigned k : grotto::principal_precisions)
for (long double x : samples)
{
if (x == 0.0L || near_odd_multiple_of_half_pi(x))
continue;
expect_ulps(which, k, x, 8.0L);
}
}
TEST(RangeLut, HyperbolicReductionsTrackLibm)
{
const long double samples[] = {
-2.0L, -1.2L, -0.4L, -0.05L, 0.05L, 0.4L, 0.7L, 1.2L, 2.0L,
};
for (auto which : {grotto::reduced::sinh, grotto::reduced::cosh})
for (unsigned k : grotto::principal_precisions)
for (long double x : samples)
expect_ulps(which, k, x, 12.0L);
for (auto which : {
grotto::reduced::tanh, grotto::reduced::sech, grotto::reduced::coth, grotto::reduced::csch,
})
{
for (unsigned k : grotto::principal_precisions)
for (long double x : samples)
{
if ((which == grotto::reduced::coth || which == grotto::reduced::csch) && std::fabsl(x) < 0.2L)
continue;
expect_ulps(which, k, x, 6.0L);
}
}
}
TEST(RangeLut, DyadicLiftsCoverOddAndEvenExponents)
{
const grotto::reduced maps[] = {
grotto::reduced::sqrt, grotto::reduced::inv, grotto::reduced::rsqrt, grotto::reduced::invsq,
};
const long double samples[] = {
0.125L, 0.3L, 0.5L, 0.75L, 1.0L, 1.5L, 2.0L, 3.0L, 6.0L, 7.5L, 16.0L, 24.0L, 48.0L, 100.0L,
};
for (auto which : maps)
for (unsigned k : grotto::principal_precisions)
for (long double x : samples)
{
const long double budget = which == grotto::reduced::sqrt ? 12.0L : 4.0L;
expect_ulps(which, k, x, budget);
}
}
TEST(RangeLut, TinyHyperbolicArgumentsUseThePrincipalTables)
{
for (unsigned k : grotto::principal_precisions)
{
if (k < 13)
continue;
const long double tiny = std::ldexp(1.0L, -16);
expect_ulps(grotto::reduced::sinh, k, tiny, 2.0L);
expect_ulps(grotto::reduced::cosh, k, tiny, 2.0L);
expect_ulps(grotto::reduced::sinh, k, -tiny, 2.0L);
expect_ulps(grotto::reduced::cosh, k, -tiny, 2.0L);
}
}
TEST(RangeLut, TanhAndCothSaturatePastBeta)
{
for (unsigned k : grotto::principal_precisions)
{
const std::int64_t one = std::int64_t{1} << k;
const std::int64_t raw = raw_of(20.0L, k);
EXPECT_EQ(grotto::eval_reduced(grotto::reduced::tanh, k, raw), one);
EXPECT_EQ(grotto::eval_reduced(grotto::reduced::tanh, k, -raw), -one);
EXPECT_EQ(grotto::eval_reduced(grotto::reduced::coth, k, raw), one);
EXPECT_EQ(grotto::eval_reduced(grotto::reduced::coth, k, -raw), -one);
}
}
TEST(RangeLut, SquareRootOfZeroIsZero)
{
for (unsigned k : grotto::principal_precisions)
EXPECT_EQ(grotto::eval_reduced(grotto::reduced::sqrt, k, 0), 0);
}
TEST(RangeLut, RejectsPolesAndNonPositiveLogarithms)
{
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::ln, 16, 0), std::domain_error);
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::ln, 16, -4), std::domain_error);
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::lg, 16, -1), std::domain_error);
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::sqrt, 16, -4), std::domain_error);
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::inv, 16, 0), std::domain_error);
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::rsqrt, 16, -8), std::domain_error);
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::cot, 16, 0), std::domain_error);
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::csc, 16, 0), std::domain_error);
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::coth, 16, 0), std::domain_error);
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::csch, 16, 0), std::domain_error);
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::ln, 7, 32), std::invalid_argument);
}