Annotate noexcept and constexpr with HEDLEY, and add interval containment, ChaCha, and the dyadic range tables.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ryan Henry 2026-09-24 20:44:07 -06:00
parent 875f09fec1
commit 0d8a5a8131
97 changed files with 9212 additions and 1159 deletions

View file

@ -10,6 +10,8 @@
#ifndef LIBDPF_INCLUDE_GROTTO_CONSTANT_LUT_HPP__
#define LIBDPF_INCLUDE_GROTTO_CONSTANT_LUT_HPP__
#include "hedley/hedley.h"
#include <algorithm>
#include <cstddef>
#include <cstdint>
@ -57,11 +59,13 @@ struct constant_lut
/// `bounds[i + 1]` or one past `numeric_limits<Raw>::max()` for the last piece.
std::vector<std::int64_t> values;
HEDLEY_NO_THROW
std::size_t linear_parts() const noexcept { return values.size(); }
/// Pieces after joining the first and last when they carry the same value.
/// Those two meet across the signed wrap, which is how the paper counts
/// parts for `zero` and `nonzero` (2, not 3).
HEDLEY_NO_THROW
std::size_t wrapped_parts() const noexcept
{
if (values.size() >= 2 && values.front() == values.back())
@ -69,6 +73,7 @@ struct constant_lut
return values.size();
}
HEDLEY_NO_THROW
std::int64_t operator()(Raw x) const noexcept
{
const auto it = std::upper_bound(bounds.begin(), bounds.end(), x);
@ -86,6 +91,7 @@ namespace detail
using u128 = unsigned __int128;
template <typename Raw>
HEDLEY_NO_THROW
constexpr u128 magnitude(Raw raw) noexcept
{
if (raw >= 0)
@ -95,6 +101,7 @@ constexpr u128 magnitude(Raw raw) noexcept
return static_cast<u128>(-static_cast<__int128>(raw));
}
HEDLEY_NO_THROW
constexpr int floor_log2(u128 mag) noexcept
{
if (mag <= std::uint64_t(-1))
@ -102,6 +109,7 @@ constexpr int floor_log2(u128 mag) noexcept
return 127 - __builtin_clzll(static_cast<std::uint64_t>(mag >> 64));
}
HEDLEY_NO_THROW
constexpr bool shift_fits(u128 value, unsigned shift) noexcept
{
return shift < 128 && value <= (~u128{0} >> shift);
@ -131,6 +139,7 @@ inline constexpr std::uint64_t pow10[] = {
10000000000000000000ull,
};
HEDLEY_NO_THROW
inline bool magnitude_ge_pow10(u128 mag, int k, unsigned fractional_bits) noexcept
{
if (mag == 0 || fractional_bits >= 128)
@ -151,6 +160,7 @@ inline bool magnitude_ge_pow10(u128 mag, int k, unsigned fractional_bits) noexce
}
/// Smallest positive magnitude whose base-10 log is at least `k`.
HEDLEY_NO_THROW
inline u128 first_magnitude_at_least_pow10(int k, unsigned fractional_bits) noexcept
{
if (fractional_bits >= 128)
@ -253,6 +263,7 @@ struct sign_program
static constexpr std::int64_t canonical[] = { Neg, Zero, Pos };
template <typename Raw>
HEDLEY_NO_THROW
static std::int64_t eval(Raw raw, unsigned) noexcept
{
if (raw < 0)
@ -310,6 +321,7 @@ template <>
struct exact_lut<exact_constant::ilogb>
{
template <typename Raw>
HEDLEY_NO_THROW
static std::int64_t eval(Raw raw, unsigned fractional_bits) noexcept
{
const detail::u128 mag = detail::magnitude(raw);
@ -341,6 +353,7 @@ template <>
struct exact_lut<exact_constant::ceil_ilogb>
{
template <typename Raw>
HEDLEY_NO_THROW
static std::int64_t eval(Raw raw, unsigned fractional_bits) noexcept
{
const detail::u128 mag = detail::magnitude(raw);
@ -379,6 +392,7 @@ template <>
struct exact_lut<exact_constant::ilog10>
{
template <typename Raw>
HEDLEY_NO_THROW
static std::int64_t eval(Raw raw, unsigned fractional_bits) noexcept
{
const detail::u128 mag = detail::magnitude(raw);
@ -416,6 +430,7 @@ template <>
struct exact_lut<exact_constant::clz>
{
template <typename Raw>
HEDLEY_NO_THROW
static std::int64_t eval(Raw raw, unsigned fractional_bits) noexcept
{
if (raw < 0)
@ -452,6 +467,7 @@ template <>
struct exact_lut<exact_constant::clrsb>
{
template <typename Raw>
HEDLEY_NO_THROW
static std::int64_t eval(Raw raw, unsigned fractional_bits) noexcept
{
const detail::u128 mag = detail::magnitude(raw);
@ -559,6 +575,7 @@ enum class threshold_cmp
};
template <typename Raw>
HEDLEY_NO_THROW
std::int64_t evaluate_threshold(Raw raw, Raw bound, threshold_cmp kind) noexcept
{
switch (kind)

View file

@ -0,0 +1,433 @@
/// @file grotto/dyadic_lut.hpp
/// @brief Exact dyadic step functions from the Grotto gadget list.
/// @details Integer results and boolean 1s are raw fixed-point values:
/// an integer n is stored as `n << fractional_bits`. `ilogb(0)` and
/// `ilog10(0)` return `ilog_of_zero`.
///
/// `make_msb_lut(i)` is bit `i` counting from the most significant
/// bit. It is constant on `2^{i+1}` intervals, so `i` must be less
/// than `msb_bit_limit`.
/// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license.
#ifndef LIBDPF_INCLUDE_GROTTO_DYADIC_LUT_HPP__
#define LIBDPF_INCLUDE_GROTTO_DYADIC_LUT_HPP__
#include "hedley/hedley.h"
#include "grotto/easy_lut.hpp"
#include <cstdint>
#include <limits>
#include <stdexcept>
#include <type_traits>
#include <vector>
namespace grotto
{
/// Sentinel raw value for `ilogb(0)` and `ilog10(0)`.
inline constexpr std::int64_t ilog_of_zero =
std::numeric_limits<std::int64_t>::min();
/// `make_msb_lut(i)` allows `i` in `[0, msb_bit_limit)`.
/// Bit 0 is two intervals; bit 7 is 256.
inline constexpr unsigned msb_bit_limit = 8;
namespace detail
{
using u128 = unsigned __int128;
template <typename Raw>
HEDLEY_CONST
HEDLEY_NO_THROW
constexpr int raw_width() noexcept
{
return std::numeric_limits<Raw>::digits + 1;
}
inline std::int64_t encode_units(std::int64_t units, unsigned fractional_bits)
{
if (fractional_bits >= 63)
throw std::invalid_argument("dyadic lut: fractional width does not fit");
const __int128 scaled = static_cast<__int128>(units) << fractional_bits;
if (scaled > std::numeric_limits<std::int64_t>::max()
|| scaled < std::numeric_limits<std::int64_t>::min())
throw std::overflow_error("dyadic lut: encoded value does not fit int64");
return static_cast<std::int64_t>(scaled);
}
inline easy_poly unit_poly(std::int64_t units, unsigned fractional_bits)
{
return easy_poly{encode_units(units, fractional_bits), 0, 0, 1};
}
inline easy_poly indicator_poly(bool on, unsigned fractional_bits)
{
return unit_poly(on ? 1 : 0, fractional_bits);
}
template <typename Raw>
HEDLEY_CONST
HEDLEY_NO_THROW
constexpr std::uint64_t raw_bits(std::int64_t raw) noexcept
{
constexpr int width = raw_width<Raw>();
const auto masked = static_cast<std::uint64_t>(raw);
if constexpr (width >= 64)
return masked;
else
return masked & ((std::uint64_t{1} << width) - 1);
}
inline int countl_zero_width(std::uint64_t bits, int width)
{
if (width <= 0 || width > 64)
throw std::invalid_argument("dyadic lut: width must be 1..64");
if (width < 64)
bits &= (std::uint64_t{1} << width) - 1;
if (bits == 0)
return width;
return __builtin_clzll(bits) - (64 - width);
}
inline int countl_one_width(std::uint64_t bits, int width)
{
const std::uint64_t flipped = width >= 64 ? ~bits : (~bits & ((std::uint64_t{1} << width) - 1));
return countl_zero_width(flipped, width);
}
template <typename Raw>
int clz_of(std::int64_t raw)
{
return countl_zero_width(raw_bits<Raw>(raw), raw_width<Raw>());
}
template <typename Raw>
int clrsb_of(std::int64_t raw)
{
constexpr int width = raw_width<Raw>();
const std::uint64_t bits = raw_bits<Raw>(raw);
const bool neg = ((bits >> (width - 1)) & 1u) != 0;
const int matched = neg ? countl_one_width(bits, width)
: countl_zero_width(bits, width);
return matched - 1;
}
HEDLEY_CONST
HEDLEY_NO_THROW
constexpr int floor_log2_u128(u128 mag) noexcept
{
if (mag == 0)
return -1;
int n = 0;
while (mag > 1)
{
mag >>= 1;
++n;
}
return n;
}
template <typename Raw>
HEDLEY_CONST
HEDLEY_NO_THROW
constexpr u128 magnitude(std::int64_t raw) noexcept
{
using lim = std::numeric_limits<Raw>;
if (raw == static_cast<std::int64_t>(lim::min()))
return u128{1} << (raw_width<Raw>() - 1);
const auto abs = raw < 0 ? -raw : raw;
return static_cast<u128>(abs);
}
template <typename Raw>
HEDLEY_CONST
HEDLEY_NO_THROW
constexpr int ilogb_units(std::int64_t raw, unsigned fractional_bits) noexcept
{
if (raw == 0)
return 0;
return floor_log2_u128(magnitude<Raw>(raw)) - static_cast<int>(fractional_bits);
}
inline u128 pow10_u128(int exponent)
{
if (exponent < 0 || exponent > 38)
throw std::invalid_argument("dyadic lut: power of ten is out of range");
u128 p = 1;
for (int i = 0; i < exponent; ++i)
p *= 10;
return p;
}
/// `mag / 2^k >= 10^e`.
inline bool magnitude_ge_pow10(u128 mag, unsigned fractional_bits, int exponent)
{
if (mag == 0)
return false;
if (exponent >= 0)
{
const u128 decade = pow10_u128(exponent);
if (fractional_bits > 0 && decade > (~u128{0} >> fractional_bits))
return false;
return mag >= (decade << fractional_bits);
}
const u128 decade = pow10_u128(-exponent);
const u128 scale = u128{1} << fractional_bits;
u128 threshold = scale / decade;
if (scale % decade != 0)
++threshold;
return mag >= threshold;
}
template <typename Raw>
int ilog10_units(std::int64_t raw, unsigned fractional_bits)
{
if (raw == 0)
return 0;
const u128 mag = magnitude<Raw>(raw);
int lo = -static_cast<int>(fractional_bits) - 2;
int hi = raw_width<Raw>();
while (lo < hi)
{
const int mid = lo + (hi - lo + 1) / 2;
if (magnitude_ge_pow10(mag, fractional_bits, mid))
lo = mid;
else
hi = mid - 1;
}
return lo;
}
template <typename Raw, typename At>
HEDLEY_WARN_UNUSED_RESULT
easy_lut<Raw> steps_from_cuts(std::vector<std::int64_t> cuts, At && at)
{
return assemble_easy<Raw>(std::move(cuts), [&](std::int64_t raw) {
return at(raw);
});
}
template <typename Raw>
HEDLEY_WARN_UNUSED_RESULT
easy_lut<Raw> predicate_lut(unsigned fractional_bits, bool at_or_below_zero,
bool at_zero, bool above_zero)
{
std::vector<std::int64_t> cuts{0, 1};
return steps_from_cuts<Raw>(std::move(cuts), [=](std::int64_t raw) {
const bool on = raw < 0 ? at_or_below_zero : (raw == 0 ? at_zero : above_zero);
return indicator_poly(on, fractional_bits);
});
}
} // namespace detail
template <typename Raw>
HEDLEY_WARN_UNUSED_RESULT
easy_lut<Raw> make_positive_lut(unsigned fractional_bits = 0)
{
return detail::predicate_lut<Raw>(fractional_bits, false, false, true);
}
template <typename Raw>
HEDLEY_WARN_UNUSED_RESULT
easy_lut<Raw> make_negative_lut(unsigned fractional_bits = 0)
{
return detail::predicate_lut<Raw>(fractional_bits, true, false, false);
}
template <typename Raw>
HEDLEY_WARN_UNUSED_RESULT
easy_lut<Raw> make_nonnegative_lut(unsigned fractional_bits = 0)
{
return detail::predicate_lut<Raw>(fractional_bits, false, true, true);
}
template <typename Raw>
HEDLEY_WARN_UNUSED_RESULT
easy_lut<Raw> make_nonpositive_lut(unsigned fractional_bits = 0)
{
return detail::predicate_lut<Raw>(fractional_bits, true, true, false);
}
template <typename Raw>
HEDLEY_WARN_UNUSED_RESULT
easy_lut<Raw> make_zero_lut(unsigned fractional_bits = 0)
{
return detail::predicate_lut<Raw>(fractional_bits, false, true, false);
}
template <typename Raw>
HEDLEY_WARN_UNUSED_RESULT
easy_lut<Raw> make_nonzero_lut(unsigned fractional_bits = 0)
{
return detail::predicate_lut<Raw>(fractional_bits, true, false, true);
}
template <typename Raw>
HEDLEY_WARN_UNUSED_RESULT
easy_lut<Raw> make_signum_lut(unsigned fractional_bits = 0)
{
const std::int64_t one = detail::encode_units(1, fractional_bits);
return detail::steps_from_cuts<Raw>({0, 1}, [=](std::int64_t raw) {
if (raw < 0)
return detail::easy_poly{-one, 0, 0, 1};
if (raw == 0)
return detail::kZero;
return detail::easy_poly{one, 0, 0, 1};
});
}
template <typename Raw>
HEDLEY_WARN_UNUSED_RESULT
easy_lut<Raw> make_clz_lut(unsigned fractional_bits = 0)
{
constexpr int width = detail::raw_width<Raw>();
std::vector<std::int64_t> cuts{0, 1};
for (int b = 1; b <= width - 2; ++b)
cuts.push_back(std::int64_t{1} << b);
return detail::steps_from_cuts<Raw>(std::move(cuts), [=](std::int64_t raw) {
return detail::unit_poly(detail::clz_of<Raw>(raw), fractional_bits);
});
}
template <typename Raw>
HEDLEY_WARN_UNUSED_RESULT
easy_lut<Raw> make_clrsb_lut(unsigned fractional_bits = 0)
{
constexpr int width = detail::raw_width<Raw>();
std::vector<std::int64_t> cuts{0, -1, -2};
for (int exp = 0; exp <= width - 2; ++exp)
cuts.push_back(std::int64_t{1} << exp);
for (int exp = 2; exp <= width - 1; ++exp)
{
if (exp >= 63)
cuts.push_back(static_cast<std::int64_t>(std::numeric_limits<Raw>::min()));
else
cuts.push_back(-(std::int64_t{1} << exp));
}
return detail::steps_from_cuts<Raw>(std::move(cuts), [=](std::int64_t raw) {
return detail::unit_poly(detail::clrsb_of<Raw>(raw), fractional_bits);
});
}
template <typename Raw>
HEDLEY_WARN_UNUSED_RESULT
easy_lut<Raw> make_ilogb_lut(unsigned fractional_bits = 0)
{
constexpr int width = detail::raw_width<Raw>();
std::vector<std::int64_t> cuts{0, 1};
for (int b = 1; b <= width - 2; ++b)
cuts.push_back(std::int64_t{1} << b);
for (int exp = 1; exp <= width - 1; ++exp)
{
if (exp >= width - 1)
cuts.push_back(static_cast<std::int64_t>(std::numeric_limits<Raw>::min()) + 1);
else
cuts.push_back(-(std::int64_t{1} << exp) + 1);
}
return detail::steps_from_cuts<Raw>(std::move(cuts), [=](std::int64_t raw) {
if (raw == 0)
return detail::easy_poly{ilog_of_zero, 0, 0, 1};
return detail::unit_poly(detail::ilogb_units<Raw>(raw, fractional_bits),
fractional_bits);
});
}
template <typename Raw>
HEDLEY_WARN_UNUSED_RESULT
easy_lut<Raw> make_ilog10_lut(unsigned fractional_bits = 0)
{
using lim = std::numeric_limits<Raw>;
const std::int64_t maxv = static_cast<std::int64_t>(lim::max());
const int lo = detail::ilog10_units<Raw>(1, fractional_bits);
const int hi_pos = detail::ilog10_units<Raw>(maxv, fractional_bits);
const int hi_neg = detail::ilog10_units<Raw>(static_cast<std::int64_t>(lim::min()),
fractional_bits);
const int hi = hi_pos > hi_neg ? hi_pos : hi_neg;
std::vector<std::int64_t> cuts{0, 1};
std::int64_t prev = 0;
for (int e = lo; e <= hi; ++e)
{
std::int64_t left = 1;
std::int64_t right = maxv;
while (left < right)
{
const std::int64_t mid = left + (right - left) / 2;
if (detail::ilog10_units<Raw>(mid, fractional_bits) >= e)
right = mid;
else
left = mid + 1;
}
if (left == prev)
continue;
prev = left;
cuts.push_back(left);
if (left < maxv)
{
std::int64_t end = left;
std::int64_t scan_left = left;
std::int64_t scan_right = maxv;
while (scan_left < scan_right)
{
const std::int64_t mid = scan_left + (scan_right - scan_left + 1) / 2;
if (detail::ilog10_units<Raw>(mid, fractional_bits) == e)
scan_left = mid;
else
scan_right = mid - 1;
}
end = scan_left;
const std::int64_t neg = -end;
if (neg > static_cast<std::int64_t>(lim::min()))
cuts.push_back(neg);
}
}
return detail::steps_from_cuts<Raw>(std::move(cuts), [=](std::int64_t raw) {
if (raw == 0)
return detail::easy_poly{ilog_of_zero, 0, 0, 1};
return detail::unit_poly(detail::ilog10_units<Raw>(raw, fractional_bits),
fractional_bits);
});
}
/// Bit `index` counting down from the most significant bit of `Raw`.
/// Index 0 is the sign bit. Larger indexes are refused: the bit is constant
/// on `2^{index+1}` intervals.
template <typename Raw>
HEDLEY_WARN_UNUSED_RESULT
easy_lut<Raw> make_msb_lut(unsigned index, unsigned fractional_bits = 0)
{
constexpr int width = detail::raw_width<Raw>();
if (index >= msb_bit_limit || static_cast<int>(index) >= width)
throw std::invalid_argument(
"msb lut: only the most significant bits are piecewise-cheap");
const int shift = width - 1 - static_cast<int>(index);
if (shift >= 63)
{
return detail::steps_from_cuts<Raw>({0}, [=](std::int64_t raw) {
const bool on = ((detail::raw_bits<Raw>(raw) >> shift) & 1u) != 0;
return detail::indicator_poly(on, fractional_bits);
});
}
const std::int64_t step = std::int64_t{1} << shift;
std::vector<std::int64_t> cuts;
const auto minv = static_cast<std::int64_t>(std::numeric_limits<Raw>::min());
for (std::int64_t boundary = minv; ; )
{
cuts.push_back(boundary);
if (boundary > static_cast<std::int64_t>(std::numeric_limits<Raw>::max()) - step)
break;
boundary += step;
}
return detail::steps_from_cuts<Raw>(std::move(cuts), [=](std::int64_t raw) {
const bool on = ((detail::raw_bits<Raw>(raw) >> shift) & 1u) != 0;
return detail::indicator_poly(on, fractional_bits);
});
}
} // namespace grotto
#endif // LIBDPF_INCLUDE_GROTTO_DYADIC_LUT_HPP__

View file

@ -9,6 +9,8 @@
#ifndef LIBDPF_INCLUDE_GROTTO_EASY_LUT_HPP__
#define LIBDPF_INCLUDE_GROTTO_EASY_LUT_HPP__
#include "hedley/hedley.h"
#include <algorithm>
#include <cstdint>
#include <limits>
@ -35,6 +37,7 @@ struct easy_lut
std::vector<std::int64_t> c2;
std::vector<std::int64_t> den;
HEDLEY_NO_THROW
std::size_t parts() const noexcept { return c0.size(); }
std::int64_t operator()(Raw x) const
@ -82,6 +85,7 @@ struct easy_poly
std::int64_t den = 1;
};
HEDLEY_NO_THROW
inline bool operator==(easy_poly a, easy_poly b) noexcept
{
return a.c0 == b.c0 && a.c1 == b.c1 && a.c2 == b.c2 && a.den == b.den;

View file

@ -40,6 +40,7 @@ namespace detail
/// @brief Integer value of an already-rounded finite double, as a 256-bit word.
/// Values that do not fit saturate to all-ones.
HEDLEY_NO_THROW
inline uint256_t uint256_from_rounded_double(double rounded) noexcept
{
if (!(rounded > 0.0) || !std::isfinite(rounded))
@ -99,6 +100,7 @@ struct is_static_castable<To, From,
/// Low `bits` of `wide`, saturated to all-ones when `wide` does not fit.
template <typename Raw, std::size_t Bits>
HEDLEY_NO_THROW
Raw saturate_low_bits(uint256_t wide) noexcept
{
static_assert(Bits > 0 && Bits <= 256);
@ -151,6 +153,7 @@ Raw saturate_low_bits(uint256_t wide) noexcept
}
template <typename IntegralType>
HEDLEY_NO_THROW
inline IntegralType rounded_double_to_integral(double rounded) noexcept
{
if constexpr (std::is_integral_v<IntegralType>
@ -206,6 +209,7 @@ template <typename IntegralType,
typename T>
HEDLEY_ALWAYS_INLINE
HEDLEY_CONST
HEDLEY_NO_THROW
constexpr IntegralType scale_integer_to_fixed_raw(T integer_value) noexcept
{
using unsigned_type = dpf::utils::make_unsigned_t<IntegralType>;
@ -228,6 +232,7 @@ inline constexpr bool is_signed_rep_v =
template <typename IntegralType>
HEDLEY_ALWAYS_INLINE
HEDLEY_CONST
HEDLEY_NO_THROW
constexpr IntegralType raw_neg(IntegralType x) noexcept
{
using unsigned_type = dpf::utils::make_unsigned_t<IntegralType>;
@ -238,6 +243,7 @@ constexpr IntegralType raw_neg(IntegralType x) noexcept
template <typename IntegralType>
HEDLEY_ALWAYS_INLINE
HEDLEY_CONST
HEDLEY_NO_THROW
constexpr IntegralType raw_abs(IntegralType x) noexcept
{
if constexpr (is_signed_rep_v<IntegralType>)
@ -252,6 +258,7 @@ constexpr IntegralType raw_abs(IntegralType x) noexcept
template <typename IntegralType>
HEDLEY_ALWAYS_INLINE
HEDLEY_CONST
HEDLEY_NO_THROW
constexpr IntegralType raw_fmod(IntegralType a, IntegralType b) noexcept
{
if (b == IntegralType{})
@ -265,6 +272,7 @@ constexpr IntegralType raw_fmod(IntegralType a, IntegralType b) noexcept
template <unsigned FractionalBits,
typename IntegralType>
HEDLEY_NO_THROW
auto constexpr make_fixed_from_integral_type(IntegralType value) noexcept;
/// @tparam FractionalBits Number of fractional bits used in the fixed-point
@ -378,6 +386,7 @@ public:
~fixedpoint() = default;
/// @brief Cast to `double`
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
explicit constexpr operator double() const noexcept
@ -728,6 +737,7 @@ public:
template <unsigned FractionalBits,
typename IntegralType,
typename Mask>
HEDLEY_NO_THROW
constexpr bool operator&(const Mask & mask,
const fixedpoint<FractionalBits, IntegralType> & x) noexcept
{
@ -739,6 +749,7 @@ template <class CharT,
class Traits,
unsigned FractionalBits,
typename IntegralType>
HEDLEY_NO_THROW
std::basic_ostream<CharT, Traits> &
operator<<(std::basic_ostream<CharT, Traits> & os,
const fixedpoint<FractionalBits, IntegralType> & f) noexcept
@ -762,6 +773,7 @@ operator>>(std::basic_istream<CharT, Traits> & is,
template <unsigned FractionalBits,
typename IntegralType>
HEDLEY_NO_THROW
auto constexpr make_fixed_from_integral_type(IntegralType value) noexcept
{
return fixedpoint<FractionalBits, IntegralType>::from_raw(value);
@ -811,6 +823,7 @@ static auto make_fixed_safe(double d)
template <unsigned ToFractionalBits,
unsigned FromFractionalBits,
typename IntegralType>
HEDLEY_NO_THROW
constexpr auto precision_cast(const fixedpoint<FromFractionalBits, IntegralType> & f) noexcept
{
auto value = f.integral_representation();
@ -826,6 +839,7 @@ constexpr auto precision_cast(const fixedpoint<FromFractionalBits, IntegralType>
template <unsigned FractionalBits,
typename IntegralType>
HEDLEY_NO_THROW
static constexpr auto precision_of(fixedpoint<FractionalBits, IntegralType>) noexcept
{
return FractionalBits;
@ -1466,6 +1480,7 @@ struct countl_zero_symmetric_difference<grotto::fixedpoint<FractionalBits, Integ
using T = grotto::fixedpoint<FractionalBits, IntegralType>;
static constexpr auto clz = dpf::utils::countl_zero_symmetric_difference<typename T::integral_type>{};
HEDLEY_NO_THROW
HEDLEY_PURE
HEDLEY_ALWAYS_INLINE
constexpr std::size_t operator()(const T & lhs, const T & rhs) const noexcept
@ -1499,6 +1514,7 @@ struct mod_pow_2<grotto::fixedpoint<FractionalBits, IntegralType>>
using fixed_type = grotto::fixedpoint<FractionalBits, IntegralType>;
static constexpr auto mod = mod_pow_2<IntegralType>{};
HEDLEY_NO_THROW
std::size_t operator()(fixed_type val, std::size_t n) const noexcept
{
return mod(val.integral_representation(), n);
@ -1512,6 +1528,7 @@ struct make_from_integral_value<grotto::fixedpoint<FractionalBits, IntegralType>
using fixed_type = grotto::fixedpoint<FractionalBits, IntegralType>;
using integral_type = typename to_integral_type<fixed_type>::integral_type;
HEDLEY_NO_THROW
constexpr fixed_type operator()(integral_type val) const noexcept
{
return grotto::make_fixed_from_integral_type<FractionalBits, IntegralType>(
@ -1617,6 +1634,7 @@ class numeric_limits<grotto::fixedpoint<FractionalBits, IntegralType>>
static constexpr int bitwidth =
static_cast<int>(dpf::utils::bitlength_of_v<I>);
HEDLEY_NO_THROW
static constexpr I raw_lowest() noexcept
{
if constexpr (signed_rep)
@ -1627,6 +1645,7 @@ class numeric_limits<grotto::fixedpoint<FractionalBits, IntegralType>>
return I{};
}
HEDLEY_NO_THROW
static constexpr I raw_max() noexcept
{
if constexpr (signed_rep)
@ -1664,8 +1683,11 @@ class numeric_limits<grotto::fixedpoint<FractionalBits, IntegralType>>
static constexpr bool traps = false;
static constexpr bool tinyness_before = false;
HEDLEY_NO_THROW
static constexpr T lowest() noexcept { return T::from_raw(raw_lowest()); }
HEDLEY_NO_THROW
static constexpr T max() noexcept { return T::from_raw(raw_max()); }
HEDLEY_NO_THROW
static constexpr T min() noexcept
{
if constexpr (FractionalBits == 0)
@ -1674,17 +1696,23 @@ class numeric_limits<grotto::fixedpoint<FractionalBits, IntegralType>>
}
return T::from_raw(I{1});
}
HEDLEY_NO_THROW
static constexpr T epsilon() noexcept
{
return FractionalBits ? T::from_raw(I{1}) : T::from_raw(I{});
}
HEDLEY_NO_THROW
static constexpr T round_error() noexcept
{
return FractionalBits ? T(0.5) : T(0);
}
HEDLEY_NO_THROW
static constexpr T infinity() noexcept { return max(); }
HEDLEY_NO_THROW
static constexpr T quiet_NaN() noexcept { return T::from_raw(I{}); }
HEDLEY_NO_THROW
static constexpr T signalling_NaN() noexcept { return T::from_raw(I{}); }
HEDLEY_NO_THROW
static constexpr T denorm_min() noexcept { return min(); }
};

View file

@ -6,6 +6,8 @@
#ifndef LIBDPF_INCLUDE_GROTTO_FIXEDPOINT_MUL_HPP__
#define LIBDPF_INCLUDE_GROTTO_FIXEDPOINT_MUL_HPP__
#include "hedley/hedley.h"
#ifndef LIBDPF_INCLUDE_DPF_FIXEDPOINT_HPP__
#include "grotto/fixedpoint.hpp"
#endif
@ -106,6 +108,7 @@ namespace detail
inline constexpr std::size_t fixed_mul_buf_limbs = 12;
HEDLEY_NO_THROW
constexpr void mask_to_bits(std::uint64_t * limbs, std::size_t nlimbs, unsigned bits) noexcept
{
if (bits >= nlimbs * 64u)
@ -129,11 +132,13 @@ constexpr void mask_to_bits(std::uint64_t * limbs, std::size_t nlimbs, unsigned
}
}
HEDLEY_NO_THROW
constexpr bool test_bit(const std::uint64_t * limbs, unsigned bit) noexcept
{
return ((limbs[bit / 64u] >> (bit % 64u)) & 1u) != 0u;
}
HEDLEY_NO_THROW
constexpr void fill_ones(std::uint64_t * limbs, unsigned from, unsigned to) noexcept
{
for (unsigned bit = from; bit < to; )
@ -149,6 +154,7 @@ constexpr void fill_ones(std::uint64_t * limbs, unsigned from, unsigned to) noex
}
}
HEDLEY_NO_THROW
constexpr void sign_extend_range(std::uint64_t * limbs, unsigned from_bits, unsigned to_bits) noexcept
{
if (to_bits <= from_bits || from_bits == 0u)
@ -162,6 +168,7 @@ constexpr void sign_extend_range(std::uint64_t * limbs, unsigned from_bits, unsi
}
template <typename T>
HEDLEY_NO_THROW
constexpr void store_raw_limbs(const T & value, std::uint64_t out[4]) noexcept
{
out[0] = out[1] = out[2] = out[3] = 0;
@ -192,6 +199,7 @@ constexpr void store_raw_limbs(const T & value, std::uint64_t out[4]) noexcept
/// Low `dest_bits` of `value`, sign-extended when `value` is a narrower signed integer.
template <typename T>
HEDLEY_NO_THROW
constexpr void reduce_operand(const T & value, unsigned src_bits, bool is_signed,
unsigned dest_bits, std::uint64_t * dest, unsigned nlimbs) noexcept
{
@ -215,6 +223,7 @@ constexpr void reduce_operand(const T & value, unsigned src_bits, bool is_signed
}
/// Product modulo `2^(64*nlimbs)`, using exactly `nlimbs` limbs of each operand.
HEDLEY_NO_THROW
constexpr void mul_low_limbs(std::uint64_t * out, const std::uint64_t * lhs,
const std::uint64_t * rhs, unsigned nlimbs) noexcept
{
@ -235,6 +244,7 @@ constexpr void mul_low_limbs(std::uint64_t * out, const std::uint64_t * lhs,
}
}
HEDLEY_NO_THROW
constexpr void shift_left_limbs(std::uint64_t * limbs, std::size_t nlimbs, unsigned shift) noexcept
{
if (shift == 0u)
@ -261,6 +271,7 @@ constexpr void shift_left_limbs(std::uint64_t * limbs, std::size_t nlimbs, unsig
}
}
HEDLEY_NO_THROW
constexpr void shift_right_limbs(std::uint64_t * limbs, std::size_t nlimbs, unsigned shift) noexcept
{
if (shift == 0u)
@ -288,6 +299,7 @@ constexpr void shift_right_limbs(std::uint64_t * limbs, std::size_t nlimbs, unsi
}
template <typename T>
HEDLEY_NO_THROW
constexpr T limbs_to_integral(const std::uint64_t * limbs) noexcept
{
if constexpr (std::is_same_v<T, uint256_t>)
@ -331,6 +343,7 @@ template <unsigned IntegerBits,
typename LhsIntegral,
unsigned RhsFractionalBits,
typename RhsIntegral>
HEDLEY_NO_THROW
constexpr auto fixed_mul(
fixedpoint<LhsFractionalBits, LhsIntegral> lhs,
fixedpoint<RhsFractionalBits, RhsIntegral> rhs) noexcept

View file

@ -9,6 +9,8 @@
#ifndef LIBDPF_INCLUDE_GROTTO_HEXFLOAT_HPP__
#define LIBDPF_INCLUDE_GROTTO_HEXFLOAT_HPP__
#include "hedley/hedley.h"
#include <algorithm>
#include <cctype>
#include <cmath>
@ -109,6 +111,7 @@ constexpr bool is_hexfloat_fixedpoint = false;
template <unsigned FractionalBits, typename Integral>
constexpr bool is_hexfloat_fixedpoint<fixedpoint<FractionalBits, Integral>> = true;
HEDLEY_NO_THROW
constexpr void mask_low_bits(std::uint64_t * limbs, unsigned width) noexcept
{
if (width >= 256u)
@ -132,11 +135,13 @@ constexpr void mask_low_bits(std::uint64_t * limbs, unsigned width) noexcept
}
}
HEDLEY_NO_THROW
constexpr bool bit_is_set(const std::uint64_t * limbs, unsigned bit) noexcept
{
return ((limbs[bit / 64u] >> (bit % 64u)) & 1u) != 0u;
}
HEDLEY_NO_THROW
constexpr void negate_low_bits(std::uint64_t * limbs, unsigned width) noexcept
{
for (unsigned i = 0; i < 4u; ++i)
@ -154,6 +159,7 @@ constexpr void negate_low_bits(std::uint64_t * limbs, unsigned width) noexcept
}
template <typename T>
HEDLEY_NO_THROW
constexpr void store_integer_bits(const T & value, std::uint64_t out[4]) noexcept
{
out[0] = out[1] = out[2] = out[3] = 0;
@ -183,6 +189,7 @@ constexpr void store_integer_bits(const T & value, std::uint64_t out[4]) noexcep
}
template <typename T>
HEDLEY_NO_THROW
constexpr T load_integer_bits(const std::uint64_t * limbs) noexcept
{
if constexpr (std::is_same_v<T, uint256_t>)
@ -205,6 +212,7 @@ constexpr T load_integer_bits(const std::uint64_t * limbs) noexcept
}
}
HEDLEY_NO_THROW
inline int highest_bit(const std::uint64_t * limbs) noexcept
{
for (int i = 3; i >= 0; --i)
@ -268,6 +276,7 @@ std::string format_hexfloat(const T & value, int fractional_bits)
return std::string(negative ? "-" : "+") + "0x1." + fraction + "p" + expbuf;
}
HEDLEY_NO_THROW
inline int hex_value(char c) noexcept
{
if (c >= '0' && c <= '9') return c - '0';

194
include/grotto/nmod.hpp Normal file
View file

@ -0,0 +1,194 @@
/// @file grotto/nmod.hpp
/// @brief Reduction modulo an arbitrary public modulus.
/// @details `nmod` multiplies by a rounded reciprocal `1/M` and splits that
/// product with a floor. The quotient is `floor(x/M)`. The residue
/// is `{x/M}` truncated onto `residue_bits` fractional bits, so it
/// lies in `[0, 2^residue_bits)`. A negative product borrows, which
/// keeps the residue non-negative.
///
/// The reciprocal magnitude is 128 bits, so `1/M` can be carried
/// wider than the input. A power-of-two modulus is the same split
/// with an exact shift (`nmod_pow2`).
/// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license.
#ifndef LIBDPF_INCLUDE_GROTTO_NMOD_HPP__
#define LIBDPF_INCLUDE_GROTTO_NMOD_HPP__
#include <cstdint>
#include <stdexcept>
#include "hedley/hedley.h"
namespace grotto
{
namespace nmod_detail
{
using u128 = unsigned __int128;
}
struct nmod_result
{
/// `floor({x/M} * 2^residue_bits)`, in `[0, 2^residue_bits)`.
std::int64_t residue = 0;
/// `floor(x/M)`.
std::int64_t quotient = 0;
};
/// `x_raw / 2^x_bits` modulo `M`, with `1/M ≈ recip_raw / 2^recip_bits`.
/// `recip_raw` is a positive magnitude of at most 128 bits.
/// @throws std::invalid_argument if the reciprocal is zero or a width is illegal.
/// @throws std::overflow_error if the quotient does not fit in `int64_t`.
HEDLEY_WARN_UNUSED_RESULT
inline nmod_result nmod(std::int64_t x_raw, unsigned x_bits,
unsigned __int128 recip_raw, unsigned recip_bits, unsigned residue_bits)
{
if (recip_raw == 0)
throw std::invalid_argument("nmod: reciprocal must be positive");
if (residue_bits > 63)
throw std::invalid_argument("nmod: residue must fit in int64");
if (x_bits > 100000u || recip_bits > 100000u)
throw std::invalid_argument("nmod: fractional width is too large");
const bool neg = x_raw < 0;
const auto x_mag = static_cast<std::uint64_t>(
neg ? -static_cast<__int128>(x_raw) : x_raw);
const auto recip_lo = static_cast<std::uint64_t>(recip_raw);
const auto recip_hi = static_cast<std::uint64_t>(recip_raw >> 64);
unsigned __int128 low = static_cast<unsigned __int128>(x_mag) * recip_lo;
unsigned __int128 high = static_cast<unsigned __int128>(x_mag) * recip_hi;
std::uint64_t limb[4] = {};
limb[0] = static_cast<std::uint64_t>(low);
const unsigned __int128 mid = (low >> 64) + static_cast<std::uint64_t>(high);
limb[1] = static_cast<std::uint64_t>(mid);
const unsigned __int128 top = (high >> 64) + (mid >> 64);
limb[2] = static_cast<std::uint64_t>(top);
limb[3] = static_cast<std::uint64_t>(top >> 64);
const auto bit_set_at_or_above = [&](unsigned bit) {
if (bit >= 256)
return false;
const unsigned index = bit / 64u;
const unsigned offset = bit % 64u;
if ((limb[index] >> offset) != 0)
return true;
for (unsigned i = index + 1; i < 4; ++i)
{
if (limb[i] != 0)
return true;
}
return false;
};
const auto extract = [&](unsigned low_bit, unsigned count) -> std::uint64_t {
if (count == 0 || low_bit >= 256)
return 0;
const unsigned index = low_bit / 64u;
const unsigned offset = low_bit % 64u;
unsigned __int128 chunk = limb[index];
if (index + 1 < 4)
chunk |= static_cast<unsigned __int128>(limb[index + 1]) << 64;
chunk >>= offset;
if (count == 64)
return static_cast<std::uint64_t>(chunk);
return static_cast<std::uint64_t>(chunk) & ((std::uint64_t{1} << count) - 1);
};
const auto low_bits_set = [&](unsigned width) {
if (width == 0)
return false;
if (width >= 256)
return limb[0] != 0 || limb[1] != 0 || limb[2] != 0 || limb[3] != 0;
const unsigned index = width / 64u;
const unsigned offset = width % 64u;
for (unsigned i = 0; i < index; ++i)
{
if (limb[i] != 0)
return true;
}
if (offset == 0)
return false;
const std::uint64_t mask = (std::uint64_t{1} << offset) - 1;
return (limb[index] & mask) != 0;
};
const unsigned scale = x_bits + recip_bits;
const bool remainder = low_bits_set(scale);
std::uint64_t quotient_mag = 0;
if (scale < 256)
{
if (bit_set_at_or_above(scale + 64))
throw std::overflow_error("nmod: quotient does not fit int64");
quotient_mag = extract(scale, 64);
}
nmod_result out;
if (!neg)
{
if (quotient_mag > static_cast<std::uint64_t>(INT64_MAX))
throw std::overflow_error("nmod: quotient does not fit int64");
out.quotient = static_cast<std::int64_t>(quotient_mag);
}
else if (!remainder)
{
if (quotient_mag > (static_cast<std::uint64_t>(INT64_MAX) + 1))
throw std::overflow_error("nmod: quotient does not fit int64");
out.quotient = quotient_mag == (std::uint64_t{1} << 63)
? INT64_MIN
: -static_cast<std::int64_t>(quotient_mag);
}
else
{
if (quotient_mag > static_cast<std::uint64_t>(INT64_MAX))
throw std::overflow_error("nmod: quotient does not fit int64");
out.quotient = -static_cast<std::int64_t>(quotient_mag) - 1;
}
if (residue_bits == 0 || (!neg && !remainder) || (neg && !remainder))
return out;
// A 128-bit reciprocal times an int64 magnitude stays under 2^192.
// With a residue of at most 63 bits, a scale at or above 256 puts every
// product bit strictly below the residue window.
if (scale >= 256)
{
if (neg)
out.residue = static_cast<std::int64_t>((std::uint64_t{1} << residue_bits) - 1);
return out;
}
std::uint64_t field = 0;
if (scale >= residue_bits)
field = extract(scale - residue_bits, residue_bits);
else
field = extract(0, scale) << (residue_bits - scale);
if (neg)
{
const std::uint64_t unit = std::uint64_t{1} << residue_bits;
const unsigned discarded = scale >= residue_bits ? scale - residue_bits : 0;
const bool borrow = discarded > 0 && low_bits_set(discarded);
field = borrow ? unit - field - 1 : unit - field;
}
out.residue = static_cast<std::int64_t>(field);
return out;
}
/// Modulus `2^{-exp}` by an exact shift. Positive `exp` multiplies by
/// `2^exp` (`exp`'s `2^{-13}` split). Zero splits at the integer (`2^x`).
/// Negative `exp` is a modulus above one.
/// @throws std::overflow_error if `exp` does not fit the reciprocal width.
HEDLEY_WARN_UNUSED_RESULT
inline nmod_result nmod_pow2(std::int64_t x_raw, unsigned x_bits, int exp,
unsigned residue_bits)
{
if (exp >= 128 || exp < -100000)
throw std::overflow_error("nmod: power-of-two reciprocal does not fit");
if (exp >= 0)
return nmod(x_raw, x_bits, nmod_detail::u128{1} << static_cast<unsigned>(exp), 0, residue_bits);
return nmod(x_raw, x_bits, 1, static_cast<unsigned>(-exp), residue_bits);
}
} // namespace grotto
#endif // LIBDPF_INCLUDE_GROTTO_NMOD_HPP__

View file

@ -22,6 +22,8 @@
#ifndef LIBDPF_INCLUDE_GROTTO_OFFSET_HORNER_HPP__
#define LIBDPF_INCLUDE_GROTTO_OFFSET_HORNER_HPP__
#include "hedley/hedley.h"
#include <algorithm>
#include <array>
#include <cstddef>
@ -43,6 +45,7 @@ namespace grotto
inline constexpr std::size_t offset_horner_max_degree = 3;
template <typename T>
HEDLEY_NO_THROW
T offset_horner_group_add(T a, T b) noexcept
{
using u = std::make_unsigned_t<T>;
@ -50,6 +53,7 @@ T offset_horner_group_add(T a, T b) noexcept
}
template <typename T>
HEDLEY_NO_THROW
T offset_horner_group_sub(T a, T b) noexcept
{
using u = std::make_unsigned_t<T>;
@ -65,6 +69,7 @@ struct offset_horner_x_plus_r
};
template <typename T>
HEDLEY_NO_THROW
offset_horner_x_plus_r<T> offset_horner_at_x_plus_r(T x, T r) noexcept
{
return offset_horner_x_plus_r<T>{
@ -86,6 +91,7 @@ inline constexpr uint64_t binom[4][4] = {
};
template <typename T>
HEDLEY_NO_THROW
uint64_t lift(T v) noexcept
{
if constexpr (std::is_signed_v<T>)
@ -95,6 +101,7 @@ uint64_t lift(T v) noexcept
}
template <std::size_t Degree>
HEDLEY_NO_THROW
uint64_t horner_at(const std::array<uint64_t, Degree + 1> & coeff, uint64_t point) noexcept
{
uint64_t acc = coeff[Degree];
@ -104,6 +111,7 @@ uint64_t horner_at(const std::array<uint64_t, Degree + 1> & coeff, uint64_t poin
}
template <std::size_t Degree>
HEDLEY_NO_THROW
void fill_payloads(uint64_t base, uint64_t (&payload)[Degree + 1]) noexcept
{
uint64_t pow = 1;
@ -191,6 +199,7 @@ std::vector<uint64_t> segments_of(const Key & key, const std::vector<InputT> & k
}
template <typename T>
HEDLEY_NO_THROW
int64_t math_lift(T value) noexcept
{
if constexpr (std::is_signed_v<T>)
@ -200,6 +209,7 @@ int64_t math_lift(T value) noexcept
}
template <typename T>
HEDLEY_NO_THROW
T domain_min() noexcept
{
if constexpr (std::is_signed_v<T>)
@ -211,6 +221,7 @@ T domain_min() noexcept
/// Public center-space cut where `center + eta` crosses the domain end.
/// Empty when that cut is outside the domain, including `eta == 0`.
template <typename T>
HEDLEY_NO_THROW
std::optional<T> carry_threshold(T eta) noexcept
{
constexpr unsigned bits = dpf::utils::bitlength_of_v<T>;
@ -237,6 +248,7 @@ std::optional<T> carry_threshold(T eta) noexcept
/// `center + kappa` is the wrapped representative, as a mathematical integer.
template <typename T>
HEDLEY_NO_THROW
int64_t kappa_for(T left, T eta) noexcept
{
constexpr unsigned bits = dpf::utils::bitlength_of_v<T>;
@ -528,7 +540,7 @@ namespace offset_horner_detail
template <std::size_t Degree, typename InputT, typename Rng>
geneval_offset_horner_result<Degree, InputT> geneval_at(
InputT center0, InputT center1, InputT center, InputT eta,
bool arith, InputT center0, InputT center1, InputT center, InputT eta,
const std::vector<InputT> & knots,
const std::vector<std::array<uint64_t, Degree + 1>> & coeff,
Rng rng)
@ -553,8 +565,11 @@ geneval_offset_horner_result<Degree, InputT> geneval_at(
std::array<std::vector<uint64_t>, Degree + 1> seg1;
for (std::size_t m = 0; m <= Degree; ++m)
{
const auto opened = dpf::geneval_cmp(center0, center1,
shifted.begin(), shifted.end(), rng, payload[m]);
const auto opened = arith
? dpf::geneval_cmp(dpf::arith_input, center0, center1,
shifted.begin(), shifted.end(), rng, payload[m])
: dpf::geneval_cmp(center0, center1,
shifted.begin(), shifted.end(), rng, payload[m]);
const uint64_t blind = dpf::uniform_sample<uint64_t>();
wrap[m][0] = blind;
wrap[m][1] = payload[m] - blind;
@ -574,6 +589,17 @@ geneval_offset_horner_result<Degree, InputT> geneval_at(
return out;
}
template <std::size_t Degree, typename InputT, typename Rng>
geneval_offset_horner_result<Degree, InputT> geneval_at(
InputT center0, InputT center1, InputT center, InputT eta,
const std::vector<InputT> & knots,
const std::vector<std::array<uint64_t, Degree + 1>> & coeff,
Rng rng)
{
return geneval_at<Degree>(false, center0, center1, center, eta, knots, coeff,
std::move(rng));
}
} // namespace offset_horner_detail
/// Geneval-style offset Horner. The center is XOR-shared as in `geneval_point`.
@ -609,10 +635,25 @@ geneval_offset_horner_result<Degree, InputT> geneval_offset_horner(
center0, center1, eta, knots, coeff, std::move(rng));
}
/// Additive shares of the center: `center0 + center1` is the comparison point.
template <std::size_t Degree = offset_horner_max_degree,
typename InputT,
typename Rng>
geneval_offset_horner_result<Degree, InputT> geneval_offset_horner(
dpf::arith_input_t, InputT center0, InputT center1, InputT eta,
const std::vector<InputT> & knots,
const std::vector<std::array<uint64_t, Degree + 1>> & coeff,
Rng rng)
{
const InputT center = offset_horner_group_add(center0, center1);
return offset_horner_detail::geneval_at<Degree>(
true, center0, center1, center, eta, knots, coeff, std::move(rng));
}
/// Additive shares of the input `x` and the mask `r`. Reconstructs
/// `eta = x - r` and `center = 2r`, XOR-shares that center as `(center, 0)`,
/// and returns both parties' Horner shares of the cubic at `x + r`
/// (the group element `x + r`).
/// `eta = x - r` and passes additive shares of `center = 2r` (`2·r0`, `2·r1`)
/// to arithmetic `geneval_cmp`. Returns both parties' Horner shares of the
/// cubic at `x + r` (the group element `x + r`).
template <std::size_t Degree = offset_horner_max_degree,
typename InputT,
typename Rng>
@ -625,10 +666,11 @@ geneval_offset_horner_result<Degree, InputT> geneval_offset_horner(
const InputT x = offset_horner_group_add(x0, x1);
const InputT r = offset_horner_group_add(r0, r1);
const InputT eta = offset_horner_group_sub(x, r);
const InputT center = offset_horner_group_add(r, r);
InputT zero{};
const InputT center0 = offset_horner_group_add(r0, r0);
const InputT center1 = offset_horner_group_add(r1, r1);
const InputT center = offset_horner_group_add(center0, center1);
return offset_horner_detail::geneval_at<Degree>(
center, zero, center, eta, knots, coeff, std::move(rng));
true, center0, center1, center, eta, knots, coeff, std::move(rng));
}
template <std::size_t Degree = offset_horner_max_degree, typename InputT>

View file

@ -115,6 +115,7 @@ struct offset_iterator_base
std::add_const_t<reference>>;
using size_type = std::size_t;
HEDLEY_NO_THROW
constexpr offset_iterator_base(wrapped_iterator iter, size_type offset) noexcept
: it{iter}, offset_{offset} { }

View file

@ -1,10 +1,12 @@
/// @file grotto/piecewise.hpp
/// @brief Horner evaluation of a cubic and a bound-selected piece.
/// @details `eval_horner` evaluates one polynomial. `piecewise_eval` selects
/// the piece whose upper bound is the first entry of `bounds`
/// strictly greater than `x`.
/// @author Ryan Henry <ryan.henry@ucalgary.ca>
/// @brief
/// @details
/// @copyright Copyright (c) 2019-2023 Ryan Henry and others
/// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
/// see [LICENSE.md](@ref GPLv2) for details.
/// see [LICENSE.md](@ref license) for details.
#ifndef LIBDPF_INCLUDE_GROTTO_PIECEWISE_HPP__
#define LIBDPF_INCLUDE_GROTTO_PIECEWISE_HPP__
@ -13,6 +15,8 @@
#include <iterator>
#include <algorithm>
#include "hedley/hedley.h"
namespace grotto
{
@ -25,16 +29,26 @@ template <typename T> using poly_quadratic = std::array<T, 3>;
template <typename T> using poly_cubic = std::array<T, 4>;
template <typename T>
constexpr auto eval_horner(const poly_constant<T> & f, T x) { return f[0]; }
HEDLEY_PURE
HEDLEY_NO_THROW
constexpr auto eval_horner(const poly_constant<T> & f, T x) noexcept { return f[0]; }
template <typename T>
constexpr auto eval_horner(const poly_linear<T> & f, T x) { return f[1] * x + f[0]; }
HEDLEY_PURE
HEDLEY_NO_THROW
constexpr auto eval_horner(const poly_linear<T> & f, T x) noexcept { return f[1] * x + f[0]; }
template <typename T>
constexpr auto eval_horner(const poly_quadratic<T> & f, T x) { return (f[2] * x + f[1]) * x + f[0]; }
HEDLEY_PURE
HEDLEY_NO_THROW
constexpr auto eval_horner(const poly_quadratic<T> & f, T x) noexcept { return (f[2] * x + f[1]) * x + f[0]; }
template <typename T>
constexpr auto eval_horner(const poly_cubic<T> & f, T x) { return ((f[3] * x + f[2]) * x + f[1]) * x + f[0]; }
HEDLEY_PURE
HEDLEY_NO_THROW
constexpr auto eval_horner(const poly_cubic<T> & f, T x) noexcept { return ((f[3] * x + f[2]) * x + f[1]) * x + f[0]; }
template <typename T, std::size_t D, std::size_t N1, std::size_t N2>
auto piecewise_eval(const std::array<std::array<T, D>, N1> & polys, const std::array<T, N2> & bounds, T x)
HEDLEY_PURE
HEDLEY_NO_THROW
auto piecewise_eval(const std::array<std::array<T, D>, N1> & polys, const std::array<T, N2> & bounds, T x) noexcept
{
auto it = std::upper_bound(std::cbegin(bounds), std::cend(bounds), x,
[](const T & lhs, const T & rhs){ return lhs < rhs; });

View file

@ -17,6 +17,7 @@
#include "dpf/twiddle.hpp"
#include "dpf/leaf_node.hpp"
#include "dpf/dcf.hpp"
#include "dpf/blocked_dcf.hpp"
#include "grotto/offset_iterable.hpp"
#include "dpf/path_memoizer.hpp"
#include "dpf/utils.hpp"
@ -29,6 +30,7 @@ namespace grotto
template <typename NodeT,
typename InputT>
HEDLEY_NO_THROW
auto parity_of_substring_prefix(const NodeT & node, InputT x) noexcept
{
static constexpr auto bits_per_limb = dpf::utils::bitlength_of_v<decltype(node[0])>;
@ -211,6 +213,7 @@ struct key_has_cmp<T, std::void_t<decltype(std::declval<const T &>().has_cmp())>
: std::true_type {};
template <typename KeyT>
HEDLEY_NO_THROW
uint64_t cmp_addend_raw(const KeyT & key) noexcept
{
if constexpr (dpf::is_party_key_v<KeyT>)
@ -263,7 +266,12 @@ static auto signed_prefix_parities(const DpfKey & dpf,
constexpr std::size_t depth = key_type::depth;
const auto & ch = dpf.cmp();
const std::size_t nbits = static_cast<std::size_t>(ch.nbits);
if (nbits > depth)
if constexpr (key_type::cmp_block > 0)
{
if (key_type::cmp_h > depth)
throw std::invalid_argument("signed_prefix_parities: comparison is deeper than the key");
}
else if (nbits > depth)
throw std::invalid_argument("signed_prefix_parities: comparison is deeper than the key");
const uint64_t mask = ch.mask;
@ -284,6 +292,11 @@ static auto signed_prefix_parities(const DpfKey & dpf,
prefixes[which] = addend & mask;
continue;
}
if constexpr (key_type::cmp_block > 0)
{
prefixes[which] = dpf::detail::blocked::eval_share(dpf, tx, path);
continue;
}
const std::size_t resume = dpf::detail::path_resume_for_level(
path, dpf, tx, nbits);
@ -363,7 +376,12 @@ static void signed_prefix_parities_into(const DpfKey & dpf,
constexpr std::size_t depth = key_type::depth;
const auto & ch = dpf.cmp();
const std::size_t nbits = static_cast<std::size_t>(ch.nbits);
if (nbits > depth)
if constexpr (key_type::cmp_block > 0)
{
if (key_type::cmp_h > depth)
throw std::invalid_argument("signed_prefix_parities: comparison is deeper than the key");
}
else if (nbits > depth)
throw std::invalid_argument("signed_prefix_parities: comparison is deeper than the key");
const uint64_t mask = ch.mask;
@ -382,6 +400,11 @@ static void signed_prefix_parities_into(const DpfKey & dpf,
out[which] = addend & mask;
continue;
}
if constexpr (key_type::cmp_block > 0)
{
out[which] = dpf::detail::blocked::eval_share(dpf, tx, path);
continue;
}
const std::size_t resume = dpf::detail::path_resume_for_level(
path, dpf, tx, nbits);

View file

@ -17,6 +17,8 @@
#ifndef LIBDPF_INCLUDE_GROTTO_PRINCIPAL_LUT_HPP__
#define LIBDPF_INCLUDE_GROTTO_PRINCIPAL_LUT_HPP__
#include "hedley/hedley.h"
#include <cstdint>
#include <stdexcept>
@ -44,6 +46,7 @@ enum class principal : unsigned
inline constexpr unsigned principal_precisions[] = {8u, 12u, 16u, 20u, 24u, 28u, 32u};
HEDLEY_NO_THROW
inline constexpr bool principal_precision(unsigned fractional_bits) noexcept
{
for (unsigned k : principal_precisions)
@ -150,6 +153,7 @@ inline w256 w_mul_u64(w256 value, std::uint64_t factor)
return out;
}
HEDLEY_NO_THROW
inline bool w_negative(w256 value) noexcept
{
return value.hi < 0;
@ -311,7 +315,10 @@ inline w256 w_mul_i64(w256 value, std::int64_t factor)
{
if (factor >= 0)
return w_mul_u64(value, static_cast<std::uint64_t>(factor));
return w_neg(w_mul_u64(value, static_cast<std::uint64_t>(-factor)));
// `-factor` is undefined at INT64_MIN. The magnitude is the unsigned wrap.
const auto mag = static_cast<std::uint64_t>(0)
- static_cast<std::uint64_t>(factor);
return w_neg(w_mul_u64(value, mag));
}
inline std::int64_t horner(const cubic_bits & piece, unsigned q, std::int64_t raw, unsigned fractional_bits)

View file

@ -0,0 +1,676 @@
/// @file grotto/range_lut.hpp
/// @brief Full-domain maps built from the principal-domain cubics.
/// @details Each reduced map is one of the elementary range reductions, and
/// the polynomial it evaluates is the matching principal table:
/// `ln` / `lg` / `log10` share the mantissa logarithm;
/// `exp` / `exp2` / `exp10` share the `2^{-13}` exponential;
/// `sin` / `cos` share the quarter-turn sine;
/// `tan` / `cot` share `tanf` and `tang`;
/// `sec` / `csc` share `sec` and `gsec`;
/// `sinh` / `cosh` / `tanh` / `sech` share the hyperbolic addition;
/// `coth` and `csch` use their principal small-argument tables;
/// `sqrt` / `inv` / `rsqrt` / `invsq` are dyadic lifts of `[1/2, 1]`.
/// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license.
#ifndef LIBDPF_INCLUDE_GROTTO_RANGE_LUT_HPP__
#define LIBDPF_INCLUDE_GROTTO_RANGE_LUT_HPP__
#include <cstdint>
#include <stdexcept>
#include "hedley/hedley.h"
#include "grotto/principal_lut.hpp"
namespace grotto
{
enum class reduced : unsigned
{
ln = 0,
lg,
log10,
exp,
exp2,
exp10,
sin,
cos,
tan,
cot,
sec,
csc,
sinh,
cosh,
tanh,
coth,
sech,
csch,
sqrt,
inv,
rsqrt,
invsq,
};
HEDLEY_WARN_UNUSED_RESULT
inline std::int64_t eval_reduced(reduced which, unsigned fractional_bits, std::int64_t raw);
namespace range_detail
{
using u128 = unsigned __int128;
constexpr u128 words(std::uint64_t hi, std::uint64_t lo)
{
return (u128{hi} << 64) | lo;
}
/// `value * 2^64`, rounded half away from zero. Values above `2^64` keep the
/// high limb so the constant is not truncated.
inline constexpr u128 ln2_64 = words(0, 12786308645202655660ULL);
inline constexpr u128 inv_ln2_64 = words(1, 8166282121979093367ULL);
inline constexpr u128 log10_2_64 = words(0, 5553023288523357132ULL);
inline constexpr u128 ln10_64 = words(2, 5581709770980765788ULL);
inline constexpr u128 inv_ln10_64 = words(0, 8011319160293570763ULL);
inline constexpr u128 sqrt2_64 = words(1, 7640891576956012809ULL);
inline constexpr u128 rsqrt2_64 = words(0, 13043817825332782212ULL);
inline constexpr u128 two_over_pi_64 = words(0, 11743562013128004906ULL);
inline constexpr u128 four_over_pi_64 = words(1, 5040379952546458196ULL);
inline constexpr u128 pi_over_4_64 = words(0, 14488038916154245685ULL);
/// `exp(2^{i-13}) * 2^64`.
inline constexpr u128 exp_chunk_64[13] = {
words(1, 2251937258231296ULL),
words(1, 4504149427926357ULL),
words(1, 9009398635954180ULL),
words(1, 18023197466514910ULL),
words(1, 36064004308734226ULL),
words(1, 72198514957318099ULL),
words(1, 144679606912572172ULL),
words(1, 290493950045950331ULL),
words(1, 585562514163419534ULL),
words(1, 1189712777830127574ULL),
words(1, 2456155437534072733ULL),
words(1, 5239344172067481206ULL),
words(1, 11966795255776918679ULL),
};
inline std::int64_t round_mag(u128 mag, unsigned shift, bool neg)
{
if (shift >= 128)
return 0;
if (shift > 0)
{
mag += u128{1} << (shift - 1);
mag >>= shift;
}
if (mag > static_cast<u128>(INT64_MAX))
throw std::overflow_error("range lut: value does not fit int64");
const auto out = static_cast<std::int64_t>(mag);
return neg ? -out : out;
}
inline std::int64_t round_i128(__int128 value, unsigned shift)
{
const bool neg = value < 0;
const auto mag = static_cast<u128>(neg ? -value : value);
return round_mag(mag, shift, neg);
}
inline std::int64_t scale_unit(u128 mag64, unsigned fractional_bits)
{
return round_mag(mag64, 64u - fractional_bits, false);
}
inline std::int64_t mul_raw(std::int64_t lhs, std::int64_t rhs, unsigned fractional_bits)
{
return round_i128(static_cast<__int128>(lhs) * rhs, fractional_bits);
}
inline std::int64_t div_raw(std::int64_t num, std::int64_t den, unsigned fractional_bits)
{
if (den == 0)
throw std::domain_error("range lut: division by zero");
const bool neg = (num < 0) != (den < 0);
auto n = static_cast<u128>(num < 0 ? -static_cast<__int128>(num) : num);
auto d = static_cast<u128>(den < 0 ? -static_cast<__int128>(den) : den);
n <<= fractional_bits;
const u128 quot = (n + d / 2) / d;
return round_mag(quot, 0, neg);
}
inline std::int64_t shift_pow2(std::int64_t value, int places)
{
if (places == 0 || value == 0)
return value;
if (places > 0)
{
if (places >= 62)
throw std::overflow_error("range lut: exponent overflow");
const __int128 wide = static_cast<__int128>(value) << places;
if (wide > INT64_MAX || wide < INT64_MIN)
throw std::overflow_error("range lut: exponent overflow");
return static_cast<std::int64_t>(wide);
}
return round_i128(value, static_cast<unsigned>(-places));
}
HEDLEY_CONST
HEDLEY_NO_THROW
constexpr std::int64_t one_raw(unsigned fractional_bits) noexcept
{
return std::int64_t{1} << fractional_bits;
}
HEDLEY_CONST
HEDLEY_NO_THROW
constexpr u128 magnitude_of(std::int64_t raw) noexcept
{
if (raw >= 0)
return static_cast<u128>(raw);
return static_cast<u128>(-static_cast<__int128>(raw));
}
inline std::int64_t abs_raw(std::int64_t raw)
{
const u128 mag = magnitude_of(raw);
if (mag > static_cast<u128>(INT64_MAX))
throw std::overflow_error("range lut: magnitude does not fit int64");
return static_cast<std::int64_t>(mag);
}
struct dyadic
{
std::int64_t mantissa_raw;
int power;
};
inline dyadic split_positive(std::int64_t raw, unsigned fractional_bits)
{
if (raw <= 0)
throw std::domain_error("range lut: reduction requires a positive input");
const auto mag = static_cast<unsigned long long>(raw);
const int floor_log = 63 - __builtin_clzll(mag);
const int shift = static_cast<int>(fractional_bits) - floor_log - 1;
std::int64_t mantissa = shift >= 0
? raw << shift
: round_i128(raw, static_cast<unsigned>(-shift));
int power = floor_log + 1 - static_cast<int>(fractional_bits);
const std::int64_t one = one_raw(fractional_bits);
const std::int64_t half = one >> 1;
if (mantissa >= one)
{
mantissa >>= 1;
++power;
}
if (mantissa < half)
mantissa = half;
return dyadic{mantissa, power};
}
inline std::int64_t ln2_raw(unsigned fractional_bits)
{
return scale_unit(ln2_64, fractional_bits);
}
inline std::int64_t eval_ln_positive(unsigned fractional_bits, std::int64_t raw)
{
const dyadic part = split_positive(raw, fractional_bits);
const std::int64_t ln_m = eval_principal(principal::ln, fractional_bits, part.mantissa_raw);
return ln_m + static_cast<std::int64_t>(part.power) * ln2_raw(fractional_bits);
}
inline std::int64_t eval_exp_at_scale(unsigned fractional_bits, std::int64_t raw)
{
if (fractional_bits < 13)
{
const int lift = static_cast<int>(16u - fractional_bits);
const __int128 lifted_arg = static_cast<__int128>(raw) << lift;
if (lifted_arg > INT64_MAX || lifted_arg < INT64_MIN)
throw std::overflow_error("range lut: exponent overflow");
const std::int64_t lifted = eval_exp_at_scale(16, static_cast<std::int64_t>(lifted_arg));
return round_i128(lifted, static_cast<unsigned>(lift));
}
const std::int64_t ln2 = ln2_raw(fractional_bits);
if (ln2 <= 0)
throw std::logic_error("range lut: ln 2 constant");
std::int64_t n_bin = raw / ln2;
std::int64_t remainder = raw - n_bin * ln2;
if (remainder < 0)
{
remainder += ln2;
--n_bin;
}
while (remainder >= ln2)
{
remainder -= ln2;
++n_bin;
}
const std::int64_t step = std::int64_t{1} << (fractional_bits - 13);
const std::int64_t chunks = remainder / step;
const std::int64_t tiny = remainder - chunks * step;
std::int64_t table_raw = tiny << 13;
const std::int64_t one = one_raw(fractional_bits);
if (table_raw > one)
table_raw = one;
std::int64_t exp_s = eval_principal(principal::exp, fractional_bits, table_raw);
for (unsigned bit = 0; bit < 13; ++bit)
{
if ((static_cast<unsigned long long>(chunks) & (1ull << bit)) == 0)
continue;
// `chunk` is `exp(2^{i-13}) * 2^64`, so the product's high limb is the raw product.
const u128 prod = static_cast<u128>(exp_s) * exp_chunk_64[bit];
exp_s = round_mag(prod, 64, false);
}
return shift_pow2(exp_s, static_cast<int>(n_bin));
}
inline std::int64_t fractional_raw(std::int64_t raw, unsigned fractional_bits, std::int64_t & whole)
{
const std::int64_t one = one_raw(fractional_bits);
std::int64_t q = raw / one;
std::int64_t f = raw - q * one;
if (f < 0)
{
f += one;
--q;
}
whole = q;
return f;
}
inline std::int64_t pow10_raw(int exponent, unsigned fractional_bits)
{
const std::int64_t one = one_raw(fractional_bits);
if (exponent == 0)
return one;
if (exponent < 0)
return div_raw(one, pow10_raw(-exponent, fractional_bits), fractional_bits);
u128 acc = static_cast<u128>(one);
for (int i = 0; i < exponent; ++i)
{
if (acc > static_cast<u128>(INT64_MAX) / 10)
throw std::overflow_error("range lut: exponent overflow");
acc *= 10;
}
return static_cast<std::int64_t>(acc);
}
struct angle
{
unsigned index;
std::int64_t frac_raw;
};
/// `{ |x| * multiplier }` at this precision, with the integer part reduced
/// only as far as the low bits the quadrant logic reads.
inline angle reduce_positive(unsigned fractional_bits, std::int64_t raw, u128 multiplier_64)
{
const u128 scaled = magnitude_of(raw) * multiplier_64;
const u128 rounded = (scaled + (u128{1} << 63)) >> 64;
const u128 one = u128{1} << fractional_bits;
return angle{
static_cast<unsigned>(rounded >> fractional_bits),
static_cast<std::int64_t>(rounded & (one - 1)),
};
}
inline std::int64_t principal_sin_fraction(unsigned fractional_bits, std::int64_t fraction_raw, bool complement)
{
const std::int64_t one = one_raw(fractional_bits);
std::int64_t argument = complement ? one - fraction_raw : fraction_raw;
if (argument < 0)
argument = 0;
if (argument > one)
argument = one;
return eval_principal(principal::sin, fractional_bits, argument);
}
inline std::int64_t sin_from_angle(unsigned fractional_bits, const angle & turned, int sign)
{
const unsigned which = turned.index & 3u;
const bool complement = which == 1 || which == 3;
const int quadrant_sign = (which == 2 || which == 3) ? -1 : 1;
const std::int64_t magnitude = principal_sin_fraction(
fractional_bits, turned.frac_raw, complement);
return magnitude * quadrant_sign * sign;
}
inline std::int64_t cos_from_angle(unsigned fractional_bits, const angle & turned)
{
angle shifted = turned;
shifted.index += 1;
return sin_from_angle(fractional_bits, shifted, 1);
}
inline std::int64_t pi_over_4_raw(unsigned fractional_bits)
{
return scale_unit(pi_over_4_64, fractional_bits);
}
inline std::int64_t tan_positive(unsigned fractional_bits, std::int64_t magnitude, int quarter_shift)
{
const angle turned = reduce_positive(fractional_bits, magnitude, four_over_pi_64);
const unsigned q = (turned.index + static_cast<unsigned>(quarter_shift)) & 3u;
const std::int64_t one = one_raw(fractional_bits);
std::int64_t t = (q == 0 || q == 2) ? turned.frac_raw : one - turned.frac_raw;
if (t < 0)
t = 0;
if (t > one)
t = one;
const std::int64_t z = mul_raw(t, pi_over_4_raw(fractional_bits), fractional_bits);
if (q == 0 || q == 3)
{
const std::int64_t tanf = eval_principal(principal::tanf, fractional_bits, t);
const std::int64_t y = mul_raw(z, tanf, fractional_bits);
return q == 3 ? -y : y;
}
if (z == 0)
throw std::domain_error("range lut: tan pole");
const std::int64_t tang = eval_principal(principal::tang, fractional_bits, t);
const std::int64_t y = div_raw(one, z, fractional_bits) + tang;
return q == 2 ? -y : y;
}
inline std::int64_t sec_positive(unsigned fractional_bits, std::int64_t magnitude, int octant_shift)
{
const angle turned = reduce_positive(fractional_bits, magnitude, four_over_pi_64);
const unsigned q8 = (turned.index + static_cast<unsigned>(octant_shift)) & 7u;
const unsigned q = q8 & 3u;
const int sigma = (q8 & 4u) == 0 ? 1 : -1;
const std::int64_t one = one_raw(fractional_bits);
std::int64_t t = (q == 0 || q == 2) ? turned.frac_raw : one - turned.frac_raw;
if (t < 0)
t = 0;
if (t > one)
t = one;
if (q == 0 || q == 3)
{
const std::int64_t sec = eval_principal(principal::sec, fractional_bits, t);
const int sign = (q == 3 ? -1 : 1) * sigma;
return sec * sign;
}
const std::int64_t z = mul_raw(t, pi_over_4_raw(fractional_bits), fractional_bits);
if (z == 0)
throw std::domain_error("range lut: sec pole");
const std::int64_t gsec = eval_principal(principal::gsec, fractional_bits, t);
std::int64_t y = div_raw(one, z, fractional_bits) + gsec;
if (q == 2)
y = -y;
return y * sigma;
}
inline void quotient_2_13(unsigned fractional_bits, std::int64_t magnitude,
std::int64_t & quotient, std::int64_t & remainder)
{
if (fractional_bits >= 13)
{
const unsigned shift = fractional_bits - 13;
quotient = magnitude >> shift;
const std::int64_t mask = shift >= 63 ? INT64_MAX : (std::int64_t{1} << shift) - 1;
remainder = shift == 0 ? 0 : magnitude & mask;
return;
}
const int lift = static_cast<int>(13u - fractional_bits);
const __int128 wide = static_cast<__int128>(magnitude) << lift;
if (wide > INT64_MAX)
throw std::overflow_error("range lut: exponent overflow");
quotient = static_cast<std::int64_t>(wide);
remainder = 0;
}
inline std::int64_t exp_of_quotient(unsigned fractional_bits, std::int64_t quotient, std::int64_t magnitude)
{
if (quotient == 0)
return one_raw(fractional_bits);
__int128 argument;
if (fractional_bits >= 13)
argument = static_cast<__int128>(quotient) << (fractional_bits - 13);
else
argument = magnitude;
if (argument > INT64_MAX)
throw std::overflow_error("range lut: exponent overflow");
return eval_exp_at_scale(fractional_bits, static_cast<std::int64_t>(argument));
}
struct hyp
{
std::int64_t sinh_raw;
std::int64_t cosh_raw;
};
inline hyp sinh_cosh(unsigned fractional_bits, std::int64_t raw)
{
const bool neg = raw < 0;
const auto mag_wide = magnitude_of(raw);
if (mag_wide > static_cast<u128>(INT64_MAX))
throw std::overflow_error("range lut: exponent overflow");
const std::int64_t mag = static_cast<std::int64_t>(mag_wide);
std::int64_t quotient = 0;
std::int64_t remainder = 0;
quotient_2_13(fractional_bits, mag, quotient, remainder);
const std::int64_t one = one_raw(fractional_bits);
std::int64_t table = 0;
if (fractional_bits >= 13 && remainder != 0)
{
const __int128 lifted = static_cast<__int128>(remainder) << 13;
table = lifted > one ? one : static_cast<std::int64_t>(lifted);
}
const std::int64_t sr = eval_principal(principal::sinh, fractional_bits, table);
const std::int64_t cr = eval_principal(principal::cosh, fractional_bits, table);
std::int64_t sh = sr;
std::int64_t ch = cr;
if (quotient != 0)
{
const std::int64_t grown = exp_of_quotient(fractional_bits, quotient, mag);
std::int64_t inv = 0;
if (grown != 0)
inv = div_raw(one, grown, fractional_bits);
const std::int64_t sq = round_i128(static_cast<__int128>(grown) - inv, 1);
const std::int64_t cq = round_i128(static_cast<__int128>(grown) + inv, 1);
const __int128 sinh_sum = static_cast<__int128>(sq) * cr + static_cast<__int128>(cq) * sr;
const __int128 cosh_sum = static_cast<__int128>(cq) * cr + static_cast<__int128>(sq) * sr;
sh = round_i128(sinh_sum, fractional_bits);
ch = round_i128(cosh_sum, fractional_bits);
}
if (neg)
sh = -sh;
return hyp{sh, ch};
}
/// `ln(2^{k+1} ± 1) / 2`, the saturation threshold used by `tanh` and `coth`.
inline std::int64_t beta_raw(unsigned fractional_bits, bool plus)
{
u128 ln = u128{fractional_bits + 1} * ln2_64;
const u128 eps = u128{1} << (63u - fractional_bits);
if (plus)
ln += eps;
else
ln -= eps;
return round_mag(ln, 65u - fractional_bits, false);
}
inline int half_pow_of(int power)
{
return (power & 1) != 0 ? (power - 1) / 2 : power / 2;
}
} // namespace range_detail
HEDLEY_WARN_UNUSED_RESULT
inline std::int64_t eval_reduced(reduced which, unsigned fractional_bits, std::int64_t raw)
{
using namespace range_detail;
if (!principal_precision(fractional_bits))
throw std::invalid_argument("range lut: precision must be 8, 12, ..., 32");
const std::int64_t one = one_raw(fractional_bits);
switch (which)
{
case reduced::ln:
return eval_ln_positive(fractional_bits, raw);
case reduced::lg:
{
const dyadic part = split_positive(raw, fractional_bits);
const std::int64_t ln_m = eval_principal(principal::ln, fractional_bits, part.mantissa_raw);
const std::int64_t lg_m = mul_raw(
ln_m, scale_unit(inv_ln2_64, fractional_bits), fractional_bits);
return lg_m + (static_cast<std::int64_t>(part.power) << fractional_bits);
}
case reduced::log10:
{
const dyadic part = split_positive(raw, fractional_bits);
const std::int64_t ln_m = eval_principal(principal::ln, fractional_bits, part.mantissa_raw);
const std::int64_t mantissa = mul_raw(
ln_m, scale_unit(inv_ln10_64, fractional_bits), fractional_bits);
const std::int64_t lift = static_cast<std::int64_t>(part.power)
* scale_unit(log10_2_64, fractional_bits);
return mantissa + lift;
}
case reduced::exp:
return eval_exp_at_scale(fractional_bits, raw);
case reduced::exp2:
{
std::int64_t whole = 0;
const std::int64_t frac = fractional_raw(raw, fractional_bits, whole);
const std::int64_t natural = mul_raw(frac, ln2_raw(fractional_bits), fractional_bits);
return shift_pow2(eval_exp_at_scale(fractional_bits, natural), static_cast<int>(whole));
}
case reduced::exp10:
{
std::int64_t whole = 0;
const std::int64_t frac = fractional_raw(raw, fractional_bits, whole);
const std::int64_t natural = mul_raw(
frac, scale_unit(ln10_64, fractional_bits), fractional_bits);
if (whole > 18 || whole < -18)
throw std::overflow_error("range lut: exponent overflow");
return mul_raw(
eval_exp_at_scale(fractional_bits, natural),
pow10_raw(static_cast<int>(whole), fractional_bits),
fractional_bits);
}
case reduced::sin:
return sin_from_angle(
fractional_bits,
reduce_positive(fractional_bits, raw, two_over_pi_64),
raw < 0 ? -1 : 1);
case reduced::cos:
return cos_from_angle(
fractional_bits, reduce_positive(fractional_bits, raw, two_over_pi_64));
case reduced::tan:
{
const std::int64_t y = tan_positive(fractional_bits, abs_raw(raw), 0);
return raw < 0 ? -y : y;
}
case reduced::cot:
{
if (raw == 0)
throw std::domain_error("range lut: cot pole");
const std::int64_t y = -tan_positive(fractional_bits, abs_raw(raw), 2);
return raw < 0 ? -y : y;
}
case reduced::sec:
return sec_positive(fractional_bits, abs_raw(raw), 0);
case reduced::csc:
{
if (raw == 0)
throw std::domain_error("range lut: csc pole");
const std::int64_t y = sec_positive(fractional_bits, abs_raw(raw), -2);
return raw < 0 ? -y : y;
}
case reduced::sinh:
return sinh_cosh(fractional_bits, raw).sinh_raw;
case reduced::cosh:
return sinh_cosh(fractional_bits, raw).cosh_raw;
case reduced::tanh:
{
if (raw == 0)
return 0;
const std::int64_t limit = beta_raw(fractional_bits, false);
const std::int64_t mag = abs_raw(raw);
if (mag >= limit)
return raw < 0 ? -one : one;
const hyp pair = sinh_cosh(fractional_bits, raw);
return div_raw(pair.sinh_raw, pair.cosh_raw, fractional_bits);
}
case reduced::coth:
{
if (raw == 0)
throw std::domain_error("range lut: coth pole");
const std::int64_t limit = beta_raw(fractional_bits, true);
const std::int64_t mag = abs_raw(raw);
std::int64_t y;
if (mag >= limit)
y = one;
else
{
const std::int64_t t = div_raw(mag, limit, fractional_bits);
const std::int64_t argument = t > one ? one : t;
const std::int64_t removed = eval_principal(principal::coth, fractional_bits, argument);
y = removed + div_raw(one, mag, fractional_bits);
}
return raw < 0 ? -y : y;
}
case reduced::sech:
{
const std::int64_t ch = sinh_cosh(fractional_bits, raw).cosh_raw;
return div_raw(one, ch, fractional_bits);
}
case reduced::csch:
{
if (raw == 0)
throw std::domain_error("range lut: csch pole");
const bool neg = raw < 0;
const std::int64_t mag = abs_raw(raw);
std::int64_t y;
if (mag <= one)
{
const std::int64_t removed = eval_principal(principal::csch, fractional_bits, mag);
y = removed + div_raw(one, mag, fractional_bits);
}
else
{
y = div_raw(one, sinh_cosh(fractional_bits, mag).sinh_raw, fractional_bits);
}
return neg ? -y : y;
}
case reduced::sqrt:
{
if (raw == 0)
return 0;
const dyadic part = split_positive(raw, fractional_bits);
std::int64_t root = eval_principal(principal::sqrt, fractional_bits, part.mantissa_raw);
if ((part.power & 1) != 0)
root = mul_raw(root, scale_unit(sqrt2_64, fractional_bits), fractional_bits);
return shift_pow2(root, half_pow_of(part.power));
}
case reduced::inv:
{
const dyadic part = split_positive(raw, fractional_bits);
const std::int64_t reciprocal = eval_principal(
principal::inv, fractional_bits, part.mantissa_raw);
return shift_pow2(reciprocal, -part.power);
}
case reduced::rsqrt:
{
const dyadic part = split_positive(raw, fractional_bits);
std::int64_t root = eval_principal(principal::rsqrt, fractional_bits, part.mantissa_raw);
if ((part.power & 1) != 0)
root = mul_raw(root, scale_unit(rsqrt2_64, fractional_bits), fractional_bits);
return shift_pow2(root, -half_pow_of(part.power));
}
case reduced::invsq:
{
const dyadic part = split_positive(raw, fractional_bits);
const std::int64_t square = eval_principal(
principal::invsq, fractional_bits, part.mantissa_raw);
return shift_pow2(square, -2 * part.power);
}
}
throw std::invalid_argument("range lut: unknown map");
}
} // namespace grotto
#endif // LIBDPF_INCLUDE_GROTTO_RANGE_LUT_HPP__