Ship the TLS mesh, composer, Beaver/Yao/leaf MPC, prep/online paths, apps, and docs so the tree is pushable before elevating share_expr, security_mode, and prep resume. Co-authored-by: Cursor <cursoragent@cursor.com>
376 lines
14 KiB
C++
376 lines
14 KiB
C++
#include <gtest/gtest.h>
|
|
#include <tuple>
|
|
|
|
#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);
|
|
case grotto::reduced::expm1: return expm1l(x);
|
|
case grotto::reduced::log1p: return log1pl(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";
|
|
case grotto::reduced::expm1: return "expm1";
|
|
case grotto::reduced::log1p: return "log1p";
|
|
}
|
|
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 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 (unsigned k : grotto::principal_precisions)
|
|
{
|
|
for (long double x : samples)
|
|
{
|
|
expect_ulps(grotto::reduced::ln, k, x, 1.0L);
|
|
expect_ulps(grotto::reduced::log10, k, x, 1.0L);
|
|
expect_ulps(grotto::reduced::lg, k, x, 2.0L);
|
|
}
|
|
for (int exp : {-20, 20})
|
|
{
|
|
const long double x = std::ldexp(1.0L, exp);
|
|
if (raw_of(x, k) == 0)
|
|
continue;
|
|
expect_ulps(grotto::reduced::ln, k, x, 1.0L);
|
|
expect_ulps(grotto::reduced::log10, k, x, 1.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 (unsigned k : grotto::principal_precisions)
|
|
for (long double x : samples)
|
|
{
|
|
expect_ulps(grotto::reduced::exp, k, x, 1.0L);
|
|
expect_ulps(grotto::reduced::exp2, k, x, 2.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);
|
|
}
|
|
|
|
TEST(RangeLut, TanAndSecPolesThrow)
|
|
{
|
|
const unsigned k = 16;
|
|
auto pole_of = [&](grotto::reduced which) {
|
|
const std::int64_t lo = raw_of(-8.0L, k);
|
|
const std::int64_t hi = raw_of(8.0L, k);
|
|
for (std::int64_t raw = lo; raw <= hi; ++raw)
|
|
{
|
|
try
|
|
{
|
|
grotto::eval_reduced(which, k, raw);
|
|
}
|
|
catch (const std::domain_error &)
|
|
{
|
|
return raw;
|
|
}
|
|
}
|
|
return std::int64_t{0};
|
|
};
|
|
for (auto which : {grotto::reduced::tan, grotto::reduced::sec})
|
|
{
|
|
const std::int64_t raw = pole_of(which);
|
|
ASSERT_NE(raw, 0) << name_of(which);
|
|
EXPECT_THROW(grotto::eval_reduced(which, k, raw), std::domain_error)
|
|
<< name_of(which) << " raw=" << raw;
|
|
if (raw > raw_of(-8.0L, k))
|
|
{
|
|
EXPECT_NO_THROW(grotto::eval_reduced(which, k, raw - 1))
|
|
<< name_of(which);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(RangeLut, Exp10RejectsAnIntegerPowerPast18)
|
|
{
|
|
const unsigned k = 16;
|
|
int first_overflow = -1;
|
|
for (int n = 0; n <= 20; ++n)
|
|
{
|
|
try
|
|
{
|
|
grotto::eval_reduced(grotto::reduced::exp10, k, raw_of(static_cast<long double>(n), k));
|
|
}
|
|
catch (const std::overflow_error &)
|
|
{
|
|
first_overflow = n;
|
|
break;
|
|
}
|
|
}
|
|
ASSERT_GT(first_overflow, 0);
|
|
ASSERT_LE(first_overflow, 19);
|
|
EXPECT_NO_THROW(grotto::eval_reduced(grotto::reduced::exp10, k,
|
|
raw_of(static_cast<long double>(first_overflow - 1), k)));
|
|
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::exp10, k, raw_of(19.0L, k)), std::overflow_error);
|
|
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::exp10, k, raw_of(-19.0L, k)), std::overflow_error);
|
|
}
|
|
|
|
TEST(RangeLut, Expm1AndLog1pTrackLibm)
|
|
{
|
|
const long double expm1_samples[] = {
|
|
-4.0L, -2.0L, -1.0L, -0.7L, -0.5L, -0.25L, -0.125L, -0.015625L,
|
|
0.0L, 0.015625L, 0.125L, 0.25L, 0.5L, 0.7L, 1.0L, 1.5L, 2.0L,
|
|
};
|
|
const long double log1p_samples[] = {
|
|
-0.75L, -0.5L, -0.25L, -0.125L, -0.015625L, 0.0L, 0.015625L,
|
|
0.125L, 0.25L, 0.5L, 1.0L, 1.5L, 3.0L, 7.5L, 16.0L, 100.0L,
|
|
};
|
|
for (unsigned k : grotto::principal_precisions)
|
|
{
|
|
expect_ulps(grotto::reduced::expm1, k, std::ldexp(1.0L, -static_cast<int>(k)), 2.0L);
|
|
expect_ulps(grotto::reduced::expm1, k, -std::ldexp(1.0L, -static_cast<int>(k)), 2.0L);
|
|
expect_ulps(grotto::reduced::log1p, k, std::ldexp(1.0L, -static_cast<int>(k)), 2.0L);
|
|
expect_ulps(grotto::reduced::log1p, k, -std::ldexp(1.0L, -static_cast<int>(k)), 2.0L);
|
|
for (long double x : expm1_samples)
|
|
expect_ulps(grotto::reduced::expm1, k, x, 1.0L);
|
|
for (long double x : log1p_samples)
|
|
expect_ulps(grotto::reduced::log1p, k, x, 1.0L);
|
|
}
|
|
}
|
|
|
|
TEST(RangeLut, ReciprocalFamilyStaysInsideOneUlpAfterTheLift)
|
|
{
|
|
const long double samples[] = {
|
|
std::ldexp(1.0L, -12), 0.015625L, 0.125L, 0.5L, 0.75L, 1.0L, 1.5L, 3.0L, 8.0L, 100.0L,
|
|
};
|
|
for (auto which : {grotto::reduced::inv, grotto::reduced::rsqrt, grotto::reduced::invsq})
|
|
{
|
|
for (unsigned k : {16u, 32u})
|
|
{
|
|
for (long double x : samples)
|
|
{
|
|
if (k == 16 && x < std::ldexp(1.0L, -10))
|
|
continue;
|
|
expect_ulps(which, k, x, 1.0L);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(RangeLut, Expm1AndLog1pFixpoints)
|
|
{
|
|
for (unsigned k : grotto::principal_precisions)
|
|
{
|
|
EXPECT_EQ(grotto::eval_reduced(grotto::reduced::expm1, k, 0), 0);
|
|
EXPECT_EQ(grotto::eval_reduced(grotto::reduced::log1p, k, 0), 0);
|
|
const std::int64_t neg_one = -raw_of(1.0L, k);
|
|
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::log1p, k, neg_one), std::domain_error);
|
|
EXPECT_THROW(grotto::eval_reduced(grotto::reduced::log1p, k, neg_one - 1), std::domain_error);
|
|
const std::int64_t far = raw_of(-40.0L, k);
|
|
EXPECT_EQ(grotto::eval_reduced(grotto::reduced::expm1, k, far), neg_one);
|
|
}
|
|
}
|