#include #include "grotto/range_lut.hpp" #include #include 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(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(x); const long double truth = truth_of(which, std::ldexp(static_cast(raw), -static_cast(k))); const long double want = truth * std::ldexp(1.0L, static_cast(k)); EXPECT_LE(std::fabsl(static_cast(got) - want), ulps) << name_of(which) << " k=" << k << " x=" << static_cast(x) << " got=" << got << " want=" << static_cast(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); }