libdpf/test/tests/range_lut_test.cpp
Ryan Henry 0d22946a0e Checkpoint the party/runtime stack before share-program and malicious-mode work.
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>
2026-09-28 05:59:19 -06:00

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);
}
}