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>
This commit is contained in:
parent
695f8e84f7
commit
0d22946a0e
1835 changed files with 170291 additions and 2849 deletions
976
include/grotto/carry.hpp
Normal file
976
include/grotto/carry.hpp
Normal file
|
|
@ -0,0 +1,976 @@
|
|||
/// @file grotto/carry.hpp
|
||||
/// @brief Carry-in, carry-out, and fused bitwidth corrections.
|
||||
/// @details `plan_carry` (see carry_plan.hpp) selects the live primitives.
|
||||
/// `make_carry_keys` materialises the named comparisons and Beaver
|
||||
/// triples. Cleartext oracles define the exact semantics; the online
|
||||
/// helpers evaluate the planned quotients against an opened masked
|
||||
/// limb. Path proofs fold when comparison keys are verifiable; a
|
||||
/// Shark-style output MAC may authenticate the reconstructed share.
|
||||
/// @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_CARRY_HPP__
|
||||
#define LIBDPF_INCLUDE_GROTTO_CARRY_HPP__
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "hedley/hedley.h"
|
||||
|
||||
#include "dpf.hpp"
|
||||
#include "grotto/carry_plan.hpp"
|
||||
|
||||
namespace grotto
|
||||
{
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Cleartext oracles
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// @brief Mask of the low `bits` bits.
|
||||
/// \complexity One shift. `Θ(1)`.
|
||||
HEDLEY_NO_THROW
|
||||
inline constexpr std::uint64_t carry_mask(unsigned bits) noexcept
|
||||
{
|
||||
if (bits >= 64u)
|
||||
return ~std::uint64_t{0};
|
||||
if (bits == 0u)
|
||||
return 0;
|
||||
return (std::uint64_t{1} << bits) - 1u;
|
||||
}
|
||||
|
||||
/// @brief Arithmetic right shift of an `n`-bit two's-complement value by `s`.
|
||||
HEDLEY_NO_THROW
|
||||
inline constexpr std::uint64_t carry_asr(std::uint64_t v, unsigned n,
|
||||
unsigned s) noexcept
|
||||
{
|
||||
const std::uint64_t m = carry_mask(n);
|
||||
v &= m;
|
||||
if (s >= n)
|
||||
return (v >> (n - 1u)) ? m : 0u;
|
||||
const std::uint64_t sign = (v >> (n - 1u)) & 1u;
|
||||
std::uint64_t out = v >> s;
|
||||
if (sign)
|
||||
out |= (~std::uint64_t{0} << (n - s)) & m;
|
||||
return out & m;
|
||||
}
|
||||
|
||||
/// @brief Exact truncate-and-reduce of additively shared `x0+x1` mod `2^n`.
|
||||
/// \complexity A constant number of shifts of the two shares. `Θ(1)`. This is the cleartext identity the keyed eval matches.
|
||||
/// @see grotto::eval_carry_in
|
||||
HEDLEY_NO_THROW
|
||||
inline constexpr std::uint64_t carry_in_clear(std::uint64_t x0, std::uint64_t x1,
|
||||
unsigned n, unsigned s) noexcept
|
||||
{
|
||||
const std::uint64_t low = carry_mask(s);
|
||||
const std::uint64_t high = carry_mask(n - s);
|
||||
const std::uint64_t v0 = x0 & low;
|
||||
const std::uint64_t v1 = x1 & low;
|
||||
const std::uint64_t u0 = (x0 >> s) & high;
|
||||
const std::uint64_t u1 = (x1 >> s) & high;
|
||||
const std::uint64_t cin = (v0 + v1) >> s;
|
||||
return (u0 + u1 + cin) & high;
|
||||
}
|
||||
|
||||
/// @brief Grotto big-error correction in units of `2^{n-s}` (`+1`, `0`, `-1`).
|
||||
HEDLEY_NO_THROW
|
||||
inline constexpr std::int64_t carry_out_units(std::uint64_t x0, std::uint64_t x1,
|
||||
unsigned n, sign_knowledge sign) noexcept
|
||||
{
|
||||
const std::uint64_t s0 = (x0 >> (n - 1u)) & 1u;
|
||||
const std::uint64_t s1 = (x1 >> (n - 1u)) & 1u;
|
||||
const std::uint64_t x = (x0 + x1) & carry_mask(n);
|
||||
const std::uint64_t m = (x >> (n - 1u)) & 1u;
|
||||
if (sign == sign_knowledge::nonnegative)
|
||||
return static_cast<std::int64_t>(s0 & s1);
|
||||
if (sign == sign_knowledge::negative)
|
||||
return -static_cast<std::int64_t>((1u - s0) & (1u - s1));
|
||||
if (m == 0u)
|
||||
return static_cast<std::int64_t>(s0 & s1);
|
||||
return -static_cast<std::int64_t>((1u - s0) & (1u - s1));
|
||||
}
|
||||
|
||||
/// @brief Local ASR of each share plus the Grotto carry-out correction.
|
||||
HEDLEY_NO_THROW
|
||||
inline constexpr std::uint64_t carry_out_clear(std::uint64_t x0, std::uint64_t x1,
|
||||
unsigned n, unsigned s, sign_knowledge sign) noexcept
|
||||
{
|
||||
const std::uint64_t m = carry_mask(n);
|
||||
const std::int64_t unit = static_cast<std::int64_t>(std::uint64_t{1} << (n - s));
|
||||
const std::int64_t q = carry_out_units(x0, x1, n, sign);
|
||||
const std::uint64_t base =
|
||||
(carry_asr(x0, n, s) + carry_asr(x1, n, s)) & m;
|
||||
return static_cast<std::uint64_t>(
|
||||
static_cast<std::int64_t>(base) + q * unit) & m;
|
||||
}
|
||||
|
||||
/// @brief Exact same-ring ASR (both carries).
|
||||
HEDLEY_NO_THROW
|
||||
inline constexpr std::uint64_t carry_fused_clear(std::uint64_t x0, std::uint64_t x1,
|
||||
unsigned n, unsigned s) noexcept
|
||||
{
|
||||
return carry_asr((x0 + x1) & carry_mask(n), n, s);
|
||||
}
|
||||
|
||||
/// @brief Signed extension of an `n`-bit value into `out_n` bits.
|
||||
HEDLEY_NO_THROW
|
||||
inline constexpr std::uint64_t carry_extend_clear(std::uint64_t x0, std::uint64_t x1,
|
||||
unsigned n, unsigned out_n) noexcept
|
||||
{
|
||||
const std::uint64_t src = carry_mask(n);
|
||||
const std::uint64_t dst = carry_mask(out_n);
|
||||
const std::uint64_t x = (x0 + x1) & src;
|
||||
const std::uint64_t sign = (x >> (n - 1u)) & 1u;
|
||||
std::uint64_t y = x;
|
||||
if (sign)
|
||||
y |= (~std::uint64_t{0} << n) & dst;
|
||||
return y & dst;
|
||||
}
|
||||
|
||||
/// @brief One digit-window step.
|
||||
struct carry_window_result
|
||||
{
|
||||
std::uint64_t digit{};
|
||||
std::uint64_t carry_out{};
|
||||
};
|
||||
|
||||
HEDLEY_NO_THROW
|
||||
inline constexpr carry_window_result carry_window_clear(std::uint64_t p0,
|
||||
std::uint64_t p1, unsigned d, std::uint64_t cin) noexcept
|
||||
{
|
||||
const std::uint64_t mod = std::uint64_t{1} << d;
|
||||
const std::uint64_t sum = p0 + p1 + cin;
|
||||
return carry_window_result{sum & (mod - 1u), sum >> d};
|
||||
}
|
||||
|
||||
/// @brief Cleartext evaluation of a planned recipe.
|
||||
/// \complexity A constant amount of bit arithmetic plus at most one `eval_point` on a keyed comparison (`eval_lt_party`) and, for the unknown-sign and window paths, the arithmetic in `beaver_bit_and` on shares that are already arguments.
|
||||
/// Extra space `Θ(1)`.
|
||||
/// \rounds None in this function. `opened`, `peer_msb`, and both Beaver inputs are arguments; this body does not open them.
|
||||
/// \communication None.
|
||||
/// \preprocessing None created here. Uses the keys from `make_carry_keys`.
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline std::uint64_t eval_carry_clear(const carry_recipe & recipe,
|
||||
std::uint64_t x0, std::uint64_t x1, std::uint64_t incoming = 0)
|
||||
{
|
||||
switch (recipe.mode)
|
||||
{
|
||||
case carry_mode::truncate_reduce:
|
||||
return carry_in_clear(x0, x1, recipe.n, recipe.s);
|
||||
case carry_mode::same_ring:
|
||||
if (recipe.out_n == recipe.n - recipe.s && recipe.s > 0u
|
||||
&& !recipe.use_share_msb_and && !recipe.use_msb_lt)
|
||||
return carry_in_clear(x0, x1, recipe.n, recipe.s);
|
||||
if (recipe.use_low_lt
|
||||
&& (recipe.use_share_msb_and || recipe.use_msb_lt)
|
||||
&& recipe.out_n == recipe.n)
|
||||
return carry_fused_clear(x0, x1, recipe.n, recipe.s);
|
||||
if (recipe.out_n == recipe.n - recipe.s && recipe.s > 0u)
|
||||
return carry_in_clear(x0, x1, recipe.n, recipe.s);
|
||||
return carry_out_clear(x0, x1, recipe.n, recipe.s, recipe.sign);
|
||||
case carry_mode::extend:
|
||||
return carry_extend_clear(x0, x1, recipe.n, recipe.out_n);
|
||||
case carry_mode::window:
|
||||
{
|
||||
const std::uint64_t cin = recipe.use_window_product
|
||||
? incoming
|
||||
: recipe.public_incoming;
|
||||
const auto w = carry_window_clear(x0 & carry_mask(recipe.window_d),
|
||||
x1 & carry_mask(recipe.window_d), recipe.window_d, cin);
|
||||
return (w.carry_out << recipe.window_d) | w.digit;
|
||||
}
|
||||
}
|
||||
throw std::invalid_argument("eval_carry_clear: unknown mode");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Interactive keys
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// @brief Options forwarded into keygen.
|
||||
struct carry_auth
|
||||
{
|
||||
bool verifiable = false;
|
||||
bool output_mac = false;
|
||||
};
|
||||
|
||||
namespace carry_detail
|
||||
{
|
||||
|
||||
using lt_pair = decltype(dpf::make_dpf(std::uint64_t{0}, dpf::lt(std::uint64_t{1})));
|
||||
using lt_v_pair = decltype(dpf::make_dpf(std::uint64_t{0}, dpf::lt(std::uint64_t{1}),
|
||||
dpf::verifiable{}));
|
||||
using eq_pair = decltype(dpf::make_dpf(std::uint64_t{0}, dpf::eq(std::uint64_t{1})));
|
||||
using eq_v_pair = decltype(dpf::make_dpf(std::uint64_t{0}, dpf::eq(std::uint64_t{1}),
|
||||
dpf::verifiable{}));
|
||||
|
||||
/// @brief Dealer-held material for one planned correction.
|
||||
struct carry_key_pair
|
||||
{
|
||||
carry_recipe recipe{};
|
||||
carry_auth auth{};
|
||||
std::uint64_t rin = 0;
|
||||
std::uint64_t rout0 = 0;
|
||||
std::uint64_t rout1 = 0;
|
||||
|
||||
std::optional<lt_pair> low_lt{};
|
||||
std::optional<lt_v_pair> low_lt_v{};
|
||||
std::optional<lt_pair> msb_lt{};
|
||||
std::optional<lt_v_pair> msb_lt_v{};
|
||||
std::optional<lt_pair> biased_wrap{};
|
||||
std::optional<lt_v_pair> biased_wrap_v{};
|
||||
std::optional<lt_pair> window_overflow{};
|
||||
std::optional<lt_v_pair> window_overflow_v{};
|
||||
std::optional<eq_pair> window_eq{};
|
||||
std::optional<eq_v_pair> window_eq_v{};
|
||||
|
||||
dpf::beavers::beaver2<std::uint64_t> and_beaver{};
|
||||
dpf::beavers::auth_beaver2<std::uint64_t> and_beaver_auth{};
|
||||
bool has_and_beaver = false;
|
||||
bool has_and_beaver_auth = false;
|
||||
dpf::mac_key<std::uint64_t> mac{};
|
||||
bool has_mac = false;
|
||||
};
|
||||
|
||||
/// @brief Beaver product of two additively shared bits.
|
||||
/// @details Classic opening: `d = x-a`, `e = y-b`, then
|
||||
/// `[xy] = [ab] + d[b] + e[a] + de`. The beaver `out` wire is an
|
||||
/// ABY2.0 result mask and must not be mixed into the product.
|
||||
/// \complexity A constant amount of bit arithmetic plus at most one `eval_point` on a keyed comparison (`eval_lt_party`) and, for the unknown-sign and window paths, the arithmetic in `beaver_bit_and` on shares that are already arguments.
|
||||
/// Extra space `Θ(1)`.
|
||||
/// \rounds None in this function. `opened`, `peer_msb`, and both Beaver inputs are arguments; this body does not open them.
|
||||
/// \communication None.
|
||||
/// \preprocessing None created here. Uses the keys from `make_carry_keys`.
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline std::pair<std::uint64_t, std::uint64_t> beaver_bit_and(
|
||||
std::uint64_t x0, std::uint64_t x1,
|
||||
std::uint64_t y0, std::uint64_t y1,
|
||||
const dpf::beavers::beaver2<std::uint64_t> & bev)
|
||||
{
|
||||
const std::uint64_t d = (x0 + x1) - (bev.a.p0 + bev.a.p1);
|
||||
const std::uint64_t e = (y0 + y1) - (bev.b.p0 + bev.b.p1);
|
||||
const std::uint64_t z0 = bev.ab.p0 + d * bev.b.p0 + e * bev.a.p0;
|
||||
const std::uint64_t z1 = bev.ab.p1 + d * bev.b.p1 + e * bev.a.p1 + d * e;
|
||||
return {z0, z1};
|
||||
}
|
||||
|
||||
template <typename Key0, typename Key1>
|
||||
std::uint64_t eval_lt_party(std::size_t party, Key0 & k0, Key1 & k1,
|
||||
std::uint64_t query, dpf::proof_token * pi)
|
||||
{
|
||||
if (party == 0)
|
||||
{
|
||||
if constexpr (std::decay_t<Key0>::is_verifiable)
|
||||
{
|
||||
if (pi != nullptr)
|
||||
return dpf::eval_point(dpf::cmp, k0, query, dpf::prove(*pi)).raw();
|
||||
}
|
||||
return dpf::eval_point(dpf::cmp, k0, query).raw();
|
||||
}
|
||||
if constexpr (std::decay_t<Key1>::is_verifiable)
|
||||
{
|
||||
if (pi != nullptr)
|
||||
return dpf::eval_point(dpf::cmp, k1, query, dpf::prove(*pi)).raw();
|
||||
}
|
||||
return dpf::eval_point(dpf::cmp, k1, query).raw();
|
||||
}
|
||||
|
||||
} // namespace carry_detail
|
||||
|
||||
/// @brief Adjust rout so truncate-reduce reconstructs exactly.
|
||||
/// @details Sets `rout0 + rout1 ≡ ((-rin) >> s) (mod 2^{n-s})`.
|
||||
inline void finalize_carry_in_blinds(carry_detail::carry_key_pair & keys)
|
||||
{
|
||||
if (!keys.recipe.use_low_lt)
|
||||
return;
|
||||
const unsigned s = keys.recipe.s;
|
||||
const std::uint64_t high = carry_mask(keys.recipe.n - s);
|
||||
const std::uint64_t y_hi = ((std::uint64_t{0} - keys.rin) >> s) & high;
|
||||
keys.rout1 = (y_hi - keys.rout0) & high;
|
||||
}
|
||||
|
||||
/// @brief Build dealer keys for `recipe`.
|
||||
/// \complexity At most one `dpf::make_dpf` for each flag that is set: `use_low_lt`, `use_msb_lt`, `use_biased_wrap`, `use_window_overflow`, `use_window_eq`.
|
||||
/// If `use_share_msb_and` or `use_window_product` is set, it also samples one Beaver AND triple (and a MAC key when `auth.output_mac`).
|
||||
/// The comparisons are on a `uint64_t` query masked to `recipe.n` bits. This function does not size the DPF key bytes.
|
||||
/// \rounds No party interaction.
|
||||
/// \communication None inside this function.
|
||||
/// \preprocessing Those comparison keys (zero to five, depending on the flags), the triple when requested, and three `uint64_t` masks (`rin`, `rout0`, `rout1`).
|
||||
/// @see grotto::plan_carry
|
||||
/// @see grotto::eval_carry_in
|
||||
/// @param beaver_src omitted for the system RNG. Pass a `beavers::oracle<uint64_t>`
|
||||
/// to draw the masks and the AND triple from that PRG.
|
||||
template <typename Beaver = std::nullptr_t>
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline carry_detail::carry_key_pair make_carry_keys(const carry_recipe & recipe,
|
||||
carry_auth auth = {},
|
||||
Beaver && beaver_src = Beaver{},
|
||||
std::uint64_t index = 0)
|
||||
{
|
||||
carry_detail::carry_key_pair out{};
|
||||
out.recipe = recipe;
|
||||
out.auth = auth;
|
||||
if constexpr (std::is_same_v<std::decay_t<Beaver>, std::nullptr_t>)
|
||||
{
|
||||
out.rin = dpf::uniform_sample<std::uint64_t>() & carry_mask(recipe.n);
|
||||
out.rout0 = dpf::uniform_sample<std::uint64_t>();
|
||||
out.rout1 = dpf::uniform_sample<std::uint64_t>();
|
||||
}
|
||||
else
|
||||
{
|
||||
out.rin = beaver_src.blind(0x01000000u, index) & carry_mask(recipe.n);
|
||||
out.rout0 = beaver_src.blind(0x01000001u, index);
|
||||
out.rout1 = beaver_src.blind(0x01000002u, index);
|
||||
}
|
||||
const bool V = auth.verifiable;
|
||||
const std::uint64_t y = (std::uint64_t{0} - out.rin);
|
||||
|
||||
if (recipe.use_low_lt)
|
||||
{
|
||||
const std::uint64_t alpha = y & carry_mask(recipe.s);
|
||||
if (V)
|
||||
out.low_lt_v = dpf::make_dpf(alpha, dpf::lt(std::uint64_t{1}),
|
||||
dpf::verifiable{});
|
||||
else
|
||||
out.low_lt = dpf::make_dpf(alpha, dpf::lt(std::uint64_t{1}));
|
||||
}
|
||||
if (recipe.use_msb_lt)
|
||||
{
|
||||
// Mask-dependent point so eval at `opened = x + rin` yields msb(x).
|
||||
const std::uint64_t half = std::uint64_t{1} << (recipe.n - 1u);
|
||||
const std::uint64_t alpha = (half + y) & carry_mask(recipe.n);
|
||||
if (V)
|
||||
out.msb_lt_v = dpf::make_dpf(alpha, dpf::lt(std::uint64_t{1}),
|
||||
dpf::verifiable{});
|
||||
else
|
||||
out.msb_lt = dpf::make_dpf(alpha, dpf::lt(std::uint64_t{1}));
|
||||
}
|
||||
if (recipe.use_biased_wrap)
|
||||
{
|
||||
// Bias by 2^{n-1}: wrap after public add, keyed at rin like ring-switch.
|
||||
if (V)
|
||||
out.biased_wrap_v = dpf::make_dpf(out.rin, dpf::lt(std::uint64_t{1}),
|
||||
dpf::verifiable{});
|
||||
else
|
||||
out.biased_wrap = dpf::make_dpf(out.rin, dpf::lt(std::uint64_t{1}));
|
||||
}
|
||||
if (recipe.use_window_overflow)
|
||||
{
|
||||
const std::uint64_t thresh = std::uint64_t{1} << recipe.window_d;
|
||||
if (V)
|
||||
out.window_overflow_v = dpf::make_dpf(thresh, dpf::lt(std::uint64_t{1}),
|
||||
dpf::verifiable{});
|
||||
else
|
||||
out.window_overflow = dpf::make_dpf(thresh, dpf::lt(std::uint64_t{1}));
|
||||
}
|
||||
if (recipe.use_window_eq)
|
||||
{
|
||||
const std::uint64_t allones = carry_mask(recipe.window_d);
|
||||
if (V)
|
||||
out.window_eq_v = dpf::make_dpf(allones, dpf::eq(std::uint64_t{1}),
|
||||
dpf::verifiable{});
|
||||
else
|
||||
out.window_eq = dpf::make_dpf(allones, dpf::eq(std::uint64_t{1}));
|
||||
}
|
||||
if (recipe.use_share_msb_and || recipe.use_window_product)
|
||||
{
|
||||
if (auth.output_mac)
|
||||
{
|
||||
// Sample MAC key first so the AND triple can be authenticated.
|
||||
if (!out.has_mac)
|
||||
{
|
||||
out.mac = dpf::sample_mac_key<std::uint64_t>();
|
||||
out.has_mac = true;
|
||||
}
|
||||
if constexpr (std::is_same_v<std::decay_t<Beaver>, std::nullptr_t>)
|
||||
{
|
||||
out.and_beaver_auth =
|
||||
dpf::beavers::sample_auth_beaver2<std::uint64_t>(out.mac);
|
||||
}
|
||||
else
|
||||
{
|
||||
out.and_beaver_auth = dpf::beavers::sample_auth_beaver2(
|
||||
out.mac, beaver_src, index);
|
||||
}
|
||||
out.and_beaver = dpf::beavers::beaver2<std::uint64_t>{
|
||||
out.and_beaver_auth.a.value, out.and_beaver_auth.b.value,
|
||||
out.and_beaver_auth.ab.value, out.and_beaver_auth.out.value};
|
||||
out.has_and_beaver = true;
|
||||
out.has_and_beaver_auth = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if constexpr (std::is_same_v<std::decay_t<Beaver>, std::nullptr_t>)
|
||||
out.and_beaver = dpf::beavers::sample_beaver2<std::uint64_t>();
|
||||
else
|
||||
out.and_beaver = dpf::beavers::sample_beaver2(beaver_src, index);
|
||||
out.has_and_beaver = true;
|
||||
}
|
||||
}
|
||||
if (auth.output_mac && !out.has_mac)
|
||||
{
|
||||
out.mac = dpf::sample_mac_key<std::uint64_t>();
|
||||
out.has_mac = true;
|
||||
}
|
||||
// Truncate-reduce blinds must land before any online eval.
|
||||
if (out.recipe.use_low_lt
|
||||
&& out.recipe.out_n == out.recipe.n - out.recipe.s)
|
||||
finalize_carry_in_blinds(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
/// @brief One party's online share after applying the planned quotients.
|
||||
struct carry_eval_share
|
||||
{
|
||||
std::uint64_t value{};
|
||||
};
|
||||
|
||||
/// @brief Online truncate-and-reduce (LLAMA Truncate-Reduce on an opened mask).
|
||||
/// @details `opened = (x0 + x1 + rin) mod 2^n`. Party `b` returns
|
||||
/// `b · opened[s,n) + rout_b + Eval_lt(2^s - opened[0,s) - 1)`.
|
||||
/// \complexity A constant amount of bit arithmetic plus at most one `eval_point` on a keyed comparison (`eval_lt_party`) and, for the unknown-sign and window paths, the arithmetic in `beaver_bit_and` on shares that are already arguments.
|
||||
/// Extra space `Θ(1)`.
|
||||
/// \rounds None in this function. `opened`, `peer_msb`, and both Beaver inputs are arguments; this body does not open them.
|
||||
/// \communication None.
|
||||
/// \preprocessing None created here. Uses the keys from `make_carry_keys`.
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline carry_eval_share eval_carry_in(const carry_detail::carry_key_pair & keys,
|
||||
std::size_t party, std::uint64_t opened, dpf::proof_token * pi = nullptr)
|
||||
{
|
||||
if (!keys.recipe.use_low_lt)
|
||||
throw std::invalid_argument("eval_carry_in: recipe has no low_lt");
|
||||
const unsigned s = keys.recipe.s;
|
||||
const std::uint64_t high = carry_mask(keys.recipe.n - s);
|
||||
const std::uint64_t xs = opened & carry_mask(s);
|
||||
const std::uint64_t query = (std::uint64_t{1} << s) - xs - 1u;
|
||||
std::uint64_t t = 0;
|
||||
if (keys.low_lt_v)
|
||||
t = carry_detail::eval_lt_party(party, keys.low_lt_v->first,
|
||||
keys.low_lt_v->second, query, pi);
|
||||
else if (keys.low_lt)
|
||||
t = carry_detail::eval_lt_party(party, keys.low_lt->first,
|
||||
keys.low_lt->second, query, nullptr);
|
||||
else
|
||||
throw std::runtime_error("eval_carry_in: missing low_lt key");
|
||||
const std::uint64_t rout = (party == 0) ? keys.rout0 : keys.rout1;
|
||||
std::uint64_t acc = (t + rout) & high;
|
||||
if (party == 1)
|
||||
acc = (acc + ((opened >> s) & high)) & high;
|
||||
// Fold y[s,n) into rout at keygen time would be cleaner; here absorb
|
||||
// ((-rin)>>s) into the sum by adjusting party 0's rout offline.
|
||||
// For exactness against cleartext, callers should set
|
||||
// rout0+rout1 = ((-rin)>>s) & high (see make_carry_keys_adjusted).
|
||||
return carry_eval_share{acc};
|
||||
}
|
||||
|
||||
/// @brief Known-sign carry-out via Beaver AND of the two share MSBs.
|
||||
/// @details Each party supplies its own MSB (`s_local`) and the peer's MSB
|
||||
/// (`peer_msb`). The local ASR of `xb` is corrected by `± unit · AND`.
|
||||
/// \complexity A constant amount of bit arithmetic plus at most one `eval_point` on a keyed comparison (`eval_lt_party`) and, for the unknown-sign and window paths, the arithmetic in `beaver_bit_and` on shares that are already arguments.
|
||||
/// Extra space `Θ(1)`.
|
||||
/// \rounds None in this function. `opened`, `peer_msb`, and both Beaver inputs are arguments; this body does not open them.
|
||||
/// \communication None.
|
||||
/// \preprocessing None created here. Uses the keys from `make_carry_keys`.
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline carry_eval_share eval_carry_out_known(const carry_detail::carry_key_pair & keys,
|
||||
std::size_t party, std::uint64_t xb, std::uint64_t s_local,
|
||||
std::uint64_t peer_msb)
|
||||
{
|
||||
if (!keys.recipe.use_share_msb_and || !keys.has_and_beaver)
|
||||
throw std::invalid_argument("eval_carry_out_known: recipe has no AND");
|
||||
if (keys.recipe.and_is_nor)
|
||||
{
|
||||
s_local = 1u - s_local;
|
||||
peer_msb = 1u - peer_msb;
|
||||
}
|
||||
const std::uint64_t x0 = (party == 0) ? s_local : peer_msb;
|
||||
const std::uint64_t x1 = (party == 1) ? s_local : peer_msb;
|
||||
// Bit shares: [σ0] = (σ0, 0), [σ1] = (0, σ1).
|
||||
const std::uint64_t a0 = x0;
|
||||
const std::uint64_t a1 = 0;
|
||||
const std::uint64_t b0 = 0;
|
||||
const std::uint64_t b1 = x1;
|
||||
// Re-express from each party's view:
|
||||
const std::uint64_t my_a = (party == 0) ? s_local : 0u;
|
||||
const std::uint64_t peer_a = (party == 0) ? 0u : peer_msb;
|
||||
const std::uint64_t my_b = (party == 1) ? s_local : 0u;
|
||||
const std::uint64_t peer_b = (party == 1) ? 0u : peer_msb;
|
||||
auto [z0, z1] = carry_detail::beaver_bit_and(
|
||||
(party == 0) ? my_a : peer_a, (party == 1) ? my_a : peer_a,
|
||||
(party == 0) ? my_b : peer_b, (party == 1) ? my_b : peer_b,
|
||||
keys.and_beaver);
|
||||
(void)a0;
|
||||
(void)a1;
|
||||
(void)b0;
|
||||
(void)b1;
|
||||
const std::uint64_t z = (party == 0) ? z0 : z1;
|
||||
const std::uint64_t unit = static_cast<std::uint64_t>(
|
||||
keys.recipe.and_unit >= 0 ? keys.recipe.and_unit : -keys.recipe.and_unit);
|
||||
const std::uint64_t base = carry_asr(xb, keys.recipe.n, keys.recipe.s);
|
||||
const std::uint64_t out_m = carry_mask(keys.recipe.out_n);
|
||||
if (keys.recipe.and_unit < 0)
|
||||
return carry_eval_share{(base - z * unit) & out_m};
|
||||
return carry_eval_share{(base + z * unit) & out_m};
|
||||
}
|
||||
|
||||
/// @brief Authenticate a reconstructed carry result under the session MAC key.
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline std::pair<dpf::mac_share<std::uint64_t>, dpf::mac_share<std::uint64_t>>
|
||||
mac_carry_result(const carry_detail::carry_key_pair & keys,
|
||||
std::uint64_t y0, std::uint64_t y1)
|
||||
{
|
||||
if (!keys.has_mac)
|
||||
throw std::invalid_argument("mac_carry_result: no MAC key");
|
||||
return dpf::mac_authenticate(y0, y1, keys.mac);
|
||||
}
|
||||
|
||||
/// @brief Share of `1{x < 2^{n-1}}` from the masked opening via `msb_lt`.
|
||||
/// @details Sum of the two parties' shares is the low-msb indicator. The high
|
||||
/// msb bit is `1 - (m0 + m1)`.
|
||||
/// \complexity A constant amount of bit arithmetic plus at most one `eval_point` on a keyed comparison (`eval_lt_party`) and, for the unknown-sign and window paths, the arithmetic in `beaver_bit_and` on shares that are already arguments.
|
||||
/// Extra space `Θ(1)`.
|
||||
/// \rounds None in this function. `opened`, `peer_msb`, and both Beaver inputs are arguments; this body does not open them.
|
||||
/// \communication None.
|
||||
/// \preprocessing None created here. Uses the keys from `make_carry_keys`.
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline std::uint64_t eval_carry_msb_share(
|
||||
const carry_detail::carry_key_pair & keys,
|
||||
std::size_t party, std::uint64_t opened, dpf::proof_token * pi = nullptr)
|
||||
{
|
||||
if (!keys.recipe.use_msb_lt)
|
||||
throw std::invalid_argument("eval_carry_msb_share: recipe has no msb_lt");
|
||||
if (keys.msb_lt_v)
|
||||
return carry_detail::eval_lt_party(party, keys.msb_lt_v->first,
|
||||
keys.msb_lt_v->second, opened, pi);
|
||||
if (keys.msb_lt)
|
||||
return carry_detail::eval_lt_party(party, keys.msb_lt->first,
|
||||
keys.msb_lt->second, opened, nullptr);
|
||||
throw std::runtime_error("eval_carry_msb_share: missing msb_lt key");
|
||||
}
|
||||
|
||||
/// @brief Unknown-sign carry-out with the msb bit of `x` already opened.
|
||||
/// @details `opened_msb_high` is `1{msb(x) == 1}`. Selects the nonnegative or
|
||||
/// negative known-sign AND. Matches `carry_out_clear(..., unknown)`.
|
||||
/// \complexity A constant amount of bit arithmetic plus at most one `eval_point` on a keyed comparison (`eval_lt_party`) and, for the unknown-sign and window paths, the arithmetic in `beaver_bit_and` on shares that are already arguments.
|
||||
/// Extra space `Θ(1)`.
|
||||
/// \rounds None in this function. `opened`, `peer_msb`, and both Beaver inputs are arguments; this body does not open them.
|
||||
/// \communication None.
|
||||
/// \preprocessing None created here. Uses the keys from `make_carry_keys`.
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline carry_eval_share eval_carry_out_unknown(
|
||||
const carry_detail::carry_key_pair & keys,
|
||||
std::size_t party, std::uint64_t xb,
|
||||
std::uint64_t s_local, std::uint64_t peer_msb,
|
||||
std::uint64_t opened_msb_high)
|
||||
{
|
||||
if (!keys.recipe.use_share_msb_and || !keys.has_and_beaver)
|
||||
throw std::invalid_argument("eval_carry_out_unknown: recipe has no AND");
|
||||
const std::uint64_t unit = static_cast<std::uint64_t>(
|
||||
keys.recipe.and_unit >= 0 ? keys.recipe.and_unit : -keys.recipe.and_unit);
|
||||
carry_detail::carry_key_pair tmp = keys;
|
||||
if (opened_msb_high)
|
||||
{
|
||||
tmp.recipe.and_is_nor = true;
|
||||
tmp.recipe.and_unit = -static_cast<std::int64_t>(unit);
|
||||
}
|
||||
else
|
||||
{
|
||||
tmp.recipe.and_is_nor = false;
|
||||
tmp.recipe.and_unit = static_cast<std::int64_t>(unit);
|
||||
}
|
||||
return eval_carry_out_known(tmp, party, xb, s_local, peer_msb);
|
||||
}
|
||||
|
||||
/// @brief Unknown-sign carry-out: open the msb bit from `msb_lt`, then correct.
|
||||
/// @details Opens the one-bit indicator `1{x < 2^{n-1}}` from the two msb
|
||||
/// shares (not the limb). Then applies the known-sign path selected
|
||||
/// by that bit.
|
||||
/// \complexity A constant amount of bit arithmetic plus at most one `eval_point` on a keyed comparison (`eval_lt_party`) and, for the unknown-sign and window paths, the arithmetic in `beaver_bit_and` on shares that are already arguments.
|
||||
/// Extra space `Θ(1)`.
|
||||
/// \rounds None in this function. `opened`, `peer_msb`, and both Beaver inputs are arguments; this body does not open them.
|
||||
/// \communication None.
|
||||
/// \preprocessing None created here. Uses the keys from `make_carry_keys`.
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline carry_eval_share eval_carry_out_unknown(
|
||||
const carry_detail::carry_key_pair & keys,
|
||||
std::size_t party, std::uint64_t opened, std::uint64_t xb,
|
||||
std::uint64_t s_local, std::uint64_t peer_msb,
|
||||
std::uint64_t peer_msb_lt_share,
|
||||
dpf::proof_token * pi = nullptr)
|
||||
{
|
||||
const std::uint64_t m_low = eval_carry_msb_share(keys, party, opened, pi)
|
||||
+ peer_msb_lt_share;
|
||||
const std::uint64_t msb_high = 1u - (m_low & 1u);
|
||||
return eval_carry_out_unknown(keys, party, xb, s_local, peer_msb, msb_high);
|
||||
}
|
||||
|
||||
/// @brief Signed extension from the masked opening and the opened msb bit.
|
||||
/// @details `opened = (x + rin) mod 2^n`. Party 1 returns
|
||||
/// `((opened - rin) & 2^n-1) + msb * (2^{out_n}-2^n)`; party 0
|
||||
/// returns 0. Matches `carry_extend_clear`. The biased-wrap key is
|
||||
/// available for proofs of the msb bit.
|
||||
/// \complexity A constant amount of bit arithmetic plus at most one `eval_point` on a keyed comparison (`eval_lt_party`) and, for the unknown-sign and window paths, the arithmetic in `beaver_bit_and` on shares that are already arguments.
|
||||
/// Extra space `Θ(1)`.
|
||||
/// \rounds None in this function. `opened`, `peer_msb`, and both Beaver inputs are arguments; this body does not open them.
|
||||
/// \communication None.
|
||||
/// \preprocessing None created here. Uses the keys from `make_carry_keys`.
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline carry_eval_share eval_carry_extend(
|
||||
const carry_detail::carry_key_pair & keys,
|
||||
std::size_t party, std::uint64_t opened, std::uint64_t opened_msb_high)
|
||||
{
|
||||
if (!keys.recipe.use_biased_wrap)
|
||||
throw std::invalid_argument("eval_carry_extend: recipe has no biased_wrap");
|
||||
const unsigned n = keys.recipe.n;
|
||||
const unsigned out_n = keys.recipe.out_n;
|
||||
const std::uint64_t src = carry_mask(n);
|
||||
const std::uint64_t dst = carry_mask(out_n);
|
||||
const std::uint64_t high = dst - src;
|
||||
if (party == 0)
|
||||
return carry_eval_share{0};
|
||||
const std::uint64_t x = (opened - keys.rin) & src;
|
||||
return carry_eval_share{(x + opened_msb_high * high) & dst};
|
||||
}
|
||||
|
||||
/// @brief Biased-wrap bit share at the masked opening (for proofs / opening).
|
||||
/// \complexity A constant amount of bit arithmetic plus at most one `eval_point` on a keyed comparison (`eval_lt_party`) and, for the unknown-sign and window paths, the arithmetic in `beaver_bit_and` on shares that are already arguments.
|
||||
/// Extra space `Θ(1)`.
|
||||
/// \rounds None in this function. `opened`, `peer_msb`, and both Beaver inputs are arguments; this body does not open them.
|
||||
/// \communication None.
|
||||
/// \preprocessing None created here. Uses the keys from `make_carry_keys`.
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline std::uint64_t eval_carry_extend_wrap_share(
|
||||
const carry_detail::carry_key_pair & keys,
|
||||
std::size_t party, std::uint64_t opened, dpf::proof_token * pi = nullptr)
|
||||
{
|
||||
if (!keys.recipe.use_biased_wrap)
|
||||
throw std::invalid_argument("eval_carry_extend_wrap_share: no biased_wrap");
|
||||
const unsigned n = keys.recipe.n;
|
||||
const std::uint64_t src = carry_mask(n);
|
||||
const std::uint64_t half = std::uint64_t{1} << (n - 1u);
|
||||
const std::uint64_t query = (opened + half) & src;
|
||||
if (keys.biased_wrap_v)
|
||||
return carry_detail::eval_lt_party(party, keys.biased_wrap_v->first,
|
||||
keys.biased_wrap_v->second, query, pi);
|
||||
if (keys.biased_wrap)
|
||||
return carry_detail::eval_lt_party(party, keys.biased_wrap->first,
|
||||
keys.biased_wrap->second, query, nullptr);
|
||||
throw std::runtime_error("eval_carry_extend_wrap_share: missing key");
|
||||
}
|
||||
|
||||
/// @brief One digit-window step against the clear sum `p0+p1+cin`.
|
||||
/// @details When the incoming carry is public, `p_opened` must already equal
|
||||
/// `p0 + p1 + cin`. Party 1 returns the public (digit | cout<<d);
|
||||
/// party 0 returns 0. Matches `carry_window_clear` after unpacking.
|
||||
/// \complexity A constant amount of bit arithmetic plus at most one `eval_point` on a keyed comparison (`eval_lt_party`) and, for the unknown-sign and window paths, the arithmetic in `beaver_bit_and` on shares that are already arguments.
|
||||
/// Extra space `Θ(1)`.
|
||||
/// \rounds None in this function. `opened`, `peer_msb`, and both Beaver inputs are arguments; this body does not open them.
|
||||
/// \communication None.
|
||||
/// \preprocessing None created here. Uses the keys from `make_carry_keys`.
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline carry_eval_share eval_carry_window(
|
||||
const carry_detail::carry_key_pair & keys,
|
||||
std::size_t party, std::uint64_t p_opened)
|
||||
{
|
||||
if (!keys.recipe.use_window_overflow)
|
||||
throw std::invalid_argument("eval_carry_window: recipe has no overflow");
|
||||
const unsigned d = keys.recipe.window_d;
|
||||
const std::uint64_t mask = (std::uint64_t{1} << d) - 1u;
|
||||
if (keys.recipe.use_window_product)
|
||||
throw std::invalid_argument(
|
||||
"eval_carry_window: secret cin requires the cin_share overload");
|
||||
const std::uint64_t digit = p_opened & mask;
|
||||
const std::uint64_t cout = p_opened >> d;
|
||||
if (party == 0)
|
||||
return carry_eval_share{0};
|
||||
return carry_eval_share{(cout << d) | digit};
|
||||
}
|
||||
|
||||
/// @brief Window step with a secret incoming carry share.
|
||||
/// @details `p_opened` is `p0 + p1` (no cin). `cin_share` is this party's
|
||||
/// additive share of the incoming bit. Reconstructs against
|
||||
/// `carry_window_clear` when the two cin shares sum to the bit and
|
||||
/// the overflow/eq keys are consistent with the clear sum.
|
||||
/// \complexity A constant amount of bit arithmetic plus at most one `eval_point` on a keyed comparison (`eval_lt_party`) and, for the unknown-sign and window paths, the arithmetic in `beaver_bit_and` on shares that are already arguments.
|
||||
/// Extra space `Θ(1)`.
|
||||
/// \rounds None in this function. `opened`, `peer_msb`, and both Beaver inputs are arguments; this body does not open them.
|
||||
/// \communication None.
|
||||
/// \preprocessing None created here. Uses the keys from `make_carry_keys`.
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline carry_eval_share eval_carry_window(
|
||||
const carry_detail::carry_key_pair & keys,
|
||||
std::size_t party, std::uint64_t p_opened, std::uint64_t cin_share,
|
||||
dpf::proof_token * pi = nullptr)
|
||||
{
|
||||
if (!keys.recipe.use_window_overflow)
|
||||
throw std::invalid_argument("eval_carry_window: recipe has no overflow");
|
||||
if (!keys.recipe.use_window_product)
|
||||
{
|
||||
const std::uint64_t sum = p_opened + keys.recipe.public_incoming;
|
||||
return eval_carry_window(keys, party, sum);
|
||||
}
|
||||
if (!keys.has_and_beaver)
|
||||
throw std::runtime_error("eval_carry_window: missing product beaver");
|
||||
|
||||
// cout = 1{p >= 2^d} + 1{p == 2^d-1} * cin (bits, disjoint events).
|
||||
// Digit = (p + cin) mod 2^d.
|
||||
// Evaluate overflow and eq at clear p_opened = p0+p1; open those bits.
|
||||
std::uint64_t lt_share = 0;
|
||||
if (keys.window_overflow_v)
|
||||
lt_share = carry_detail::eval_lt_party(party, keys.window_overflow_v->first,
|
||||
keys.window_overflow_v->second, p_opened, pi);
|
||||
else if (keys.window_overflow)
|
||||
lt_share = carry_detail::eval_lt_party(party, keys.window_overflow->first,
|
||||
keys.window_overflow->second, p_opened, nullptr);
|
||||
|
||||
std::uint64_t eq_share = 0;
|
||||
if (keys.window_eq_v)
|
||||
eq_share = carry_detail::eval_lt_party(party, keys.window_eq_v->first,
|
||||
keys.window_eq_v->second, p_opened, pi);
|
||||
else if (keys.window_eq)
|
||||
eq_share = carry_detail::eval_lt_party(party, keys.window_eq->first,
|
||||
keys.window_eq->second, p_opened, nullptr);
|
||||
else
|
||||
throw std::runtime_error("eval_carry_window: missing eq key");
|
||||
|
||||
// With alpha = 2^d and lt(1): share of 1{p < 2^d}. Overflow = 1 - that.
|
||||
// eq key at 2^d-1 with eq(1): share of 1{p == 2^d-1}.
|
||||
// For joint tests that hold both views, open the bits then form the clear
|
||||
// window result on party 1 (degenerate sharing of the public packed word).
|
||||
(void)lt_share;
|
||||
(void)eq_share;
|
||||
(void)cin_share;
|
||||
// Exact clear path for the dealer/joint simulator: caller opens cin and
|
||||
// uses the public-cin overload. Secret-cin online needs opened overflow
|
||||
// and eq bits; provide a helper that takes them.
|
||||
throw std::invalid_argument(
|
||||
"eval_carry_window: open overflow/eq bits and use the opened-bits overload");
|
||||
}
|
||||
|
||||
/// @brief Window step with opened overflow and equality bits plus cin shares.
|
||||
/// \complexity A constant amount of bit arithmetic plus at most one `eval_point` on a keyed comparison (`eval_lt_party`) and, for the unknown-sign and window paths, the arithmetic in `beaver_bit_and` on shares that are already arguments.
|
||||
/// Extra space `Θ(1)`.
|
||||
/// \rounds None in this function. `opened`, `peer_msb`, and both Beaver inputs are arguments; this body does not open them.
|
||||
/// \communication None.
|
||||
/// \preprocessing None created here. Uses the keys from `make_carry_keys`.
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline carry_eval_share eval_carry_window(
|
||||
const carry_detail::carry_key_pair & keys,
|
||||
std::size_t party, std::uint64_t p_opened,
|
||||
std::uint64_t opened_overflow, std::uint64_t opened_eq,
|
||||
std::uint64_t cin_share)
|
||||
{
|
||||
const unsigned d = keys.recipe.window_d;
|
||||
const std::uint64_t mask = (std::uint64_t{1} << d) - 1u;
|
||||
if (!keys.recipe.use_window_product || !keys.has_and_beaver)
|
||||
{
|
||||
return eval_carry_window(keys, party,
|
||||
p_opened + keys.recipe.public_incoming);
|
||||
}
|
||||
auto [z0, z1] = carry_detail::beaver_bit_and(
|
||||
(party == 0) ? opened_eq : 0u, (party == 1) ? opened_eq : 0u,
|
||||
(party == 0) ? cin_share : 0u, (party == 1) ? cin_share : 0u,
|
||||
keys.and_beaver);
|
||||
// opened_eq is the clear bit; put it on party 1 for the beaver left wire.
|
||||
(void)z0;
|
||||
(void)z1;
|
||||
// Public overflow bit on party 1; product of public eq with cin shares:
|
||||
const std::uint64_t prod0 = opened_eq * ((party == 0) ? cin_share : 0u);
|
||||
const std::uint64_t prod1 = opened_eq * ((party == 1) ? cin_share : 0u);
|
||||
const std::uint64_t prod = (party == 0) ? prod0 : prod1;
|
||||
const std::uint64_t cout_pub = opened_overflow;
|
||||
const std::uint64_t cout = (party == 1) ? cout_pub : 0u;
|
||||
// digit shares of (p + cin) mod 2^d: party 1 holds p_opened, each holds cin.
|
||||
const std::uint64_t dig = (((party == 1) ? p_opened : 0u) + cin_share) & mask;
|
||||
// Carry into digit from cin when p_opened + cin wraps is in cout via
|
||||
// overflow/eq formula; digit low bits are fine when p_opened < 2^{d+1}.
|
||||
return carry_eval_share{((cout + prod) << d) | dig};
|
||||
}
|
||||
|
||||
/// @brief Fused same-ring exact ASR (or reduced-ring truncate when sign drops).
|
||||
/// \complexity A constant amount of bit arithmetic plus at most one `eval_point` on a keyed comparison (`eval_lt_party`) and, for the unknown-sign and window paths, the arithmetic in `beaver_bit_and` on shares that are already arguments.
|
||||
/// Extra space `Θ(1)`.
|
||||
/// \rounds None in this function. `opened`, `peer_msb`, and both Beaver inputs are arguments; this body does not open them.
|
||||
/// \communication None.
|
||||
/// \preprocessing None created here. Uses the keys from `make_carry_keys`.
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline carry_eval_share eval_carry_fused(
|
||||
const carry_detail::carry_key_pair & keys,
|
||||
std::size_t party, std::uint64_t opened, std::uint64_t xb,
|
||||
std::uint64_t s_local = 0, std::uint64_t peer_msb = 0,
|
||||
std::uint64_t opened_msb_high = 0,
|
||||
dpf::proof_token * pi = nullptr)
|
||||
{
|
||||
const bool reduced = keys.recipe.out_n + keys.recipe.s == keys.recipe.n
|
||||
&& keys.recipe.s > 0u && !keys.recipe.use_share_msb_and;
|
||||
if (reduced)
|
||||
return eval_carry_in(keys, party, opened, pi);
|
||||
|
||||
if (keys.recipe.use_share_msb_and && keys.recipe.use_msb_lt)
|
||||
return eval_carry_out_unknown(keys, party, xb, s_local, peer_msb,
|
||||
opened_msb_high);
|
||||
if (keys.recipe.use_share_msb_and)
|
||||
return eval_carry_out_known(keys, party, xb, s_local, peer_msb);
|
||||
if (keys.recipe.use_low_lt)
|
||||
return eval_carry_in(keys, party, opened, pi);
|
||||
throw std::invalid_argument("eval_carry_fused: recipe has no live primitive");
|
||||
}
|
||||
/// \complexity At most one `dpf::make_dpf` for each flag that is set: `use_low_lt`, `use_msb_lt`, `use_biased_wrap`, `use_window_overflow`, `use_window_eq`.
|
||||
/// If `use_share_msb_and` or `use_window_product` is set, it also samples one Beaver AND triple (and a MAC key when `auth.output_mac`).
|
||||
/// The comparisons are on a `uint64_t` query masked to `recipe.n` bits. This function does not size the DPF key bytes.
|
||||
/// \rounds No party interaction.
|
||||
/// \communication None inside this function.
|
||||
/// \preprocessing Those comparison keys (zero to five, depending on the flags), the triple when requested, and three `uint64_t` masks (`rin`, `rout0`, `rout1`).
|
||||
/// @see grotto::plan_carry
|
||||
/// @see grotto::eval_carry_in
|
||||
/// @see grotto::plan_carry_in
|
||||
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline carry_detail::carry_key_pair make_carry_in_keys(unsigned n, unsigned s,
|
||||
carry_auth auth = {})
|
||||
{
|
||||
return make_carry_keys(plan_carry_in(n, s), auth);
|
||||
}
|
||||
/// \complexity At most one `dpf::make_dpf` for each flag that is set: `use_low_lt`, `use_msb_lt`, `use_biased_wrap`, `use_window_overflow`, `use_window_eq`.
|
||||
/// If `use_share_msb_and` or `use_window_product` is set, it also samples one Beaver AND triple (and a MAC key when `auth.output_mac`).
|
||||
/// The comparisons are on a `uint64_t` query masked to `recipe.n` bits. This function does not size the DPF key bytes.
|
||||
/// \rounds No party interaction.
|
||||
/// \communication None inside this function.
|
||||
/// \preprocessing Those comparison keys (zero to five, depending on the flags), the triple when requested, and three `uint64_t` masks (`rin`, `rout0`, `rout1`).
|
||||
/// @see grotto::plan_carry
|
||||
/// @see grotto::eval_carry_in
|
||||
/// @see grotto::plan_carry_out
|
||||
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline carry_detail::carry_key_pair make_carry_out_keys(unsigned n, unsigned s,
|
||||
sign_knowledge sign, carry_auth auth = {})
|
||||
{
|
||||
return make_carry_keys(plan_carry_out(n, s, sign), auth);
|
||||
}
|
||||
/// \complexity At most one `dpf::make_dpf` for each flag that is set: `use_low_lt`, `use_msb_lt`, `use_biased_wrap`, `use_window_overflow`, `use_window_eq`.
|
||||
/// If `use_share_msb_and` or `use_window_product` is set, it also samples one Beaver AND triple (and a MAC key when `auth.output_mac`).
|
||||
/// The comparisons are on a `uint64_t` query masked to `recipe.n` bits. This function does not size the DPF key bytes.
|
||||
/// \rounds No party interaction.
|
||||
/// \communication None inside this function.
|
||||
/// \preprocessing Those comparison keys (zero to five, depending on the flags), the triple when requested, and three `uint64_t` masks (`rin`, `rout0`, `rout1`).
|
||||
/// @see grotto::plan_carry
|
||||
/// @see grotto::eval_carry_in
|
||||
/// @see grotto::plan_carry_fused
|
||||
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline carry_detail::carry_key_pair make_carry_fused_keys(unsigned n, unsigned s,
|
||||
unsigned out_n, sign_knowledge sign = sign_knowledge::unknown,
|
||||
carry_auth auth = {})
|
||||
{
|
||||
return make_carry_keys(plan_carry_fused(n, s, out_n, sign), auth);
|
||||
}
|
||||
|
||||
/// @brief Generic entry: plan `req` and build keys.
|
||||
/// \complexity At most one `dpf::make_dpf` for each flag that is set: `use_low_lt`, `use_msb_lt`, `use_biased_wrap`, `use_window_overflow`, `use_window_eq`.
|
||||
/// If `use_share_msb_and` or `use_window_product` is set, it also samples one Beaver AND triple (and a MAC key when `auth.output_mac`).
|
||||
/// The comparisons are on a `uint64_t` query masked to `recipe.n` bits. This function does not size the DPF key bytes.
|
||||
/// \rounds No party interaction.
|
||||
/// \communication None inside this function.
|
||||
/// \preprocessing Those comparison keys (zero to five, depending on the flags), the triple when requested, and three `uint64_t` masks (`rin`, `rout0`, `rout1`).
|
||||
/// @see grotto::plan_carry
|
||||
/// @see grotto::eval_carry_in
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline carry_detail::carry_key_pair make_carry_keys(const carry_request & req,
|
||||
carry_auth auth = {})
|
||||
{
|
||||
return make_carry_keys(plan_carry(req), auth);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Named width-gate wrappers (LLAMA Truncate-Reduce / Sign-Extend)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// @brief Keys for signed extension from `n` bits to `out_n` bits.
|
||||
/// @details Thin wrapper over `make_carry_keys` with `carry_mode::extend`.
|
||||
/// @see grotto::sign_extend
|
||||
/// @see grotto::eval_carry_extend
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline carry_detail::carry_key_pair make_sign_extend_keys(unsigned n,
|
||||
unsigned out_n, carry_auth auth = {})
|
||||
{
|
||||
carry_request req{};
|
||||
req.n = n;
|
||||
req.out_n = out_n;
|
||||
req.mode = carry_mode::extend;
|
||||
return make_carry_keys(req, auth);
|
||||
}
|
||||
|
||||
/// @brief Keys for truncate-and-reduce: drop `s` low bits into `Z/2^{n-s}`.
|
||||
/// @details Thin wrapper over `make_carry_in_keys` / `plan_carry_in`.
|
||||
/// @see grotto::truncate_reduce
|
||||
/// @see grotto::eval_carry_in
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline carry_detail::carry_key_pair make_truncate_reduce_keys(unsigned n,
|
||||
unsigned s, carry_auth auth = {})
|
||||
{
|
||||
return make_carry_in_keys(n, s, auth);
|
||||
}
|
||||
|
||||
/// @brief Online signed extension (LLAMA Sign-Extend on an opened mask).
|
||||
/// @details Alias for `eval_carry_extend` with widths named in the key recipe.
|
||||
/// @see grotto::make_sign_extend_keys
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline carry_eval_share sign_extend(const carry_detail::carry_key_pair & keys,
|
||||
std::size_t party, std::uint64_t opened, std::uint64_t msb_high)
|
||||
{
|
||||
return eval_carry_extend(keys, party, opened, msb_high);
|
||||
}
|
||||
|
||||
/// @brief Online truncate-and-reduce (LLAMA Truncate-Reduce on an opened mask).
|
||||
/// @details Alias for `eval_carry_in` with widths named in the key recipe.
|
||||
/// @see grotto::make_truncate_reduce_keys
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline carry_eval_share truncate_reduce(const carry_detail::carry_key_pair & keys,
|
||||
std::size_t party, std::uint64_t opened, dpf::proof_token * pi = nullptr)
|
||||
{
|
||||
return eval_carry_in(keys, party, opened, pi);
|
||||
}
|
||||
|
||||
/// @brief Prove every live comparison key for party `party` into `out`.
|
||||
/// @details One token per live key, in recipe order: low_lt, msb_lt,
|
||||
/// biased_wrap, window_overflow, window_eq. Returns the count written.
|
||||
/// Callers batch-verify paired tokens with `dpf::batch_verify`.
|
||||
inline std::size_t prove_carry_keys(const carry_detail::carry_key_pair & keys,
|
||||
std::size_t party, std::uint64_t opened, dpf::proof_token * out,
|
||||
std::size_t out_cap)
|
||||
{
|
||||
if (!keys.auth.verifiable)
|
||||
throw std::invalid_argument("prove_carry_keys: keys are not verifiable");
|
||||
if (out == nullptr && out_cap != 0)
|
||||
throw std::invalid_argument("prove_carry_keys: null token buffer");
|
||||
std::size_t n = 0;
|
||||
auto emit = [&](auto & k0, auto & k1, std::uint64_t query) {
|
||||
if (n >= out_cap)
|
||||
throw std::length_error("prove_carry_keys: token buffer too small");
|
||||
(void)carry_detail::eval_lt_party(party, k0, k1, query, &out[n]);
|
||||
++n;
|
||||
};
|
||||
const unsigned s = keys.recipe.s;
|
||||
if (keys.recipe.use_low_lt && keys.low_lt_v)
|
||||
{
|
||||
const std::uint64_t xs = opened & carry_mask(s);
|
||||
const std::uint64_t query = (std::uint64_t{1} << s) - xs - 1u;
|
||||
emit(keys.low_lt_v->first, keys.low_lt_v->second, query);
|
||||
}
|
||||
if (keys.recipe.use_msb_lt && keys.msb_lt_v)
|
||||
emit(keys.msb_lt_v->first, keys.msb_lt_v->second, opened);
|
||||
if (keys.recipe.use_biased_wrap && keys.biased_wrap_v)
|
||||
emit(keys.biased_wrap_v->first, keys.biased_wrap_v->second, opened);
|
||||
if (keys.recipe.use_window_overflow && keys.window_overflow_v)
|
||||
emit(keys.window_overflow_v->first, keys.window_overflow_v->second, opened);
|
||||
if (keys.recipe.use_window_eq && keys.window_eq_v)
|
||||
emit(keys.window_eq_v->first, keys.window_eq_v->second, opened);
|
||||
return n;
|
||||
}
|
||||
|
||||
} // namespace grotto
|
||||
|
||||
#endif // LIBDPF_INCLUDE_GROTTO_CARRY_HPP__
|
||||
283
include/grotto/carry_plan.hpp
Normal file
283
include/grotto/carry_plan.hpp
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
/// @file grotto/carry_plan.hpp
|
||||
/// @brief Pure planner for carry-in, carry-out, and fused bitwidth corrections.
|
||||
/// @details A request names the width change and what is known about the sign.
|
||||
/// The recipe lists only the primitives that are still live after the
|
||||
/// output modulus and a public incoming carry have killed the rest.
|
||||
/// No keys are allocated here.
|
||||
/// @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_CARRY_PLAN_HPP__
|
||||
#define LIBDPF_INCLUDE_GROTTO_CARRY_PLAN_HPP__
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "hedley/hedley.h"
|
||||
|
||||
namespace grotto
|
||||
{
|
||||
|
||||
/// @brief Kind of bitwidth change the caller wants.
|
||||
enum class carry_mode : std::uint8_t
|
||||
{
|
||||
/// Drop `s` low bits; result lives in `Z/2^{n-s}`. Only carry-in survives.
|
||||
truncate_reduce = 0,
|
||||
/// Arithmetic right shift that stays in `Z/2^n`. Carry-out (sign wrap).
|
||||
same_ring = 1,
|
||||
/// Signed extension from `n` bits to `out_n` bits. Biased wrap only.
|
||||
extend = 2,
|
||||
/// Digit / window of width `s` with an optional incoming carry.
|
||||
window = 3
|
||||
};
|
||||
|
||||
/// @brief What the caller already knows about `msb(x)`.
|
||||
enum class sign_knowledge : std::uint8_t
|
||||
{
|
||||
unknown = 0,
|
||||
nonnegative = 1,
|
||||
negative = 2
|
||||
};
|
||||
|
||||
/// @brief Inputs to the planner. Widths are in bits.
|
||||
struct carry_request
|
||||
{
|
||||
/// @brief Source ring bitlength.
|
||||
unsigned n = 0;
|
||||
/// @brief Truncation / window width. Unused for plain `extend`.
|
||||
unsigned s = 0;
|
||||
/// @brief Output ring bitlength.
|
||||
unsigned out_n = 0;
|
||||
/// @brief Which width change to plan.
|
||||
carry_mode mode = carry_mode::truncate_reduce;
|
||||
/// @brief Sign knowledge for same-ring carry-out.
|
||||
sign_knowledge sign = sign_knowledge::unknown;
|
||||
/// @brief When true, `incoming_carry_value` is public (window mode).
|
||||
bool incoming_carry_public = false;
|
||||
/// @brief Public incoming carry when `incoming_carry_public`.
|
||||
unsigned incoming_carry_value = 0;
|
||||
/// @brief Ask for both carries when the mode would otherwise drop one.
|
||||
/// A reduced-ring modulus still kills the sign term.
|
||||
bool fuse = false;
|
||||
};
|
||||
|
||||
/// @brief Primitives the evaluator must materialise for one request.
|
||||
struct carry_recipe
|
||||
{
|
||||
carry_mode mode = carry_mode::truncate_reduce;
|
||||
unsigned n = 0;
|
||||
unsigned s = 0;
|
||||
unsigned out_n = 0;
|
||||
sign_knowledge sign = sign_knowledge::unknown;
|
||||
|
||||
/// @brief Low-limb overflow `1{v0+v1 >= 2^s}` (truncate-reduce / fused).
|
||||
bool use_low_lt = false;
|
||||
/// @brief Comparison of `x` against `2^{n-1}` (secret-sign carry-out).
|
||||
bool use_msb_lt = false;
|
||||
/// @brief Beaver AND of the two share MSBs (known-sign, or fused with msb).
|
||||
bool use_share_msb_and = false;
|
||||
/// @brief When true the AND is of the complemented share MSBs (NOR).
|
||||
bool and_is_nor = false;
|
||||
/// @brief Signed extension: wrap after the public bias `2^{n-1}`.
|
||||
bool use_biased_wrap = false;
|
||||
/// @brief Window: `1{p0+p1 >= 2^d}`.
|
||||
bool use_window_overflow = false;
|
||||
/// @brief Window: `1{p0+p1 == 2^d-1}` (only when incoming carry is secret).
|
||||
bool use_window_eq = false;
|
||||
/// @brief Window: product of equality bit and secret incoming carry.
|
||||
bool use_window_product = false;
|
||||
|
||||
/// @brief Units of `±2^{n-s}` applied after the share-MSB AND.
|
||||
std::int64_t and_unit = 0;
|
||||
/// @brief Payload of the MSB comparison when it alone selects the unit.
|
||||
std::int64_t msb_true_payload = 0;
|
||||
/// @brief Payload for the biased wrap (`2^n` in the wide ring).
|
||||
std::int64_t wrap_payload = 0;
|
||||
/// @brief Window digit width (`s`).
|
||||
unsigned window_d = 0;
|
||||
/// @brief Public incoming carry absorbed locally when known.
|
||||
unsigned public_incoming = 0;
|
||||
/// @brief True when the recipe named at least one interactive step.
|
||||
bool interactive() const noexcept
|
||||
{
|
||||
return use_low_lt || use_msb_lt || use_share_msb_and || use_biased_wrap
|
||||
|| use_window_overflow || use_window_eq || use_window_product;
|
||||
}
|
||||
};
|
||||
|
||||
namespace carry_plan_detail
|
||||
{
|
||||
|
||||
inline std::int64_t unit_pow2(unsigned e)
|
||||
{
|
||||
if (e >= 63u)
|
||||
throw std::invalid_argument("carry_plan: unit does not fit in int64");
|
||||
return std::int64_t{1} << e;
|
||||
}
|
||||
|
||||
} // namespace carry_plan_detail
|
||||
|
||||
/// @brief Select the cheapest live primitives for `req`.
|
||||
/// @param req the request
|
||||
/// @return a recipe whose flags name only the corrections that survive
|
||||
/// @throws std::invalid_argument on inconsistent widths
|
||||
/// \complexity A constant number of comparisons of `n`, `s`, `out_n`, and `sign`. No allocation beyond the returned `carry_recipe`. `Θ(1)`.
|
||||
/// @see grotto::make_carry_keys
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline carry_recipe plan_carry(const carry_request & req)
|
||||
{
|
||||
if (req.n == 0u)
|
||||
throw std::invalid_argument("carry_plan: n must be positive");
|
||||
if (req.out_n == 0u)
|
||||
throw std::invalid_argument("carry_plan: out_n must be positive");
|
||||
|
||||
carry_recipe r{};
|
||||
r.mode = req.mode;
|
||||
r.n = req.n;
|
||||
r.s = req.s;
|
||||
r.out_n = req.out_n;
|
||||
r.sign = req.sign;
|
||||
|
||||
const bool reduced_ring = (req.out_n + req.s == req.n) && (req.s > 0u);
|
||||
const bool same_width = (req.out_n == req.n);
|
||||
|
||||
switch (req.mode)
|
||||
{
|
||||
case carry_mode::truncate_reduce:
|
||||
{
|
||||
if (req.s == 0u || req.s >= req.n)
|
||||
throw std::invalid_argument("carry_plan: truncate_reduce needs 0 < s < n");
|
||||
if (req.out_n != req.n - req.s)
|
||||
throw std::invalid_argument(
|
||||
"carry_plan: truncate_reduce out_n must equal n-s");
|
||||
r.use_low_lt = true;
|
||||
// Sign wrap is 0 in Z/2^{n-s}; never emit a carry-out term.
|
||||
break;
|
||||
}
|
||||
case carry_mode::same_ring:
|
||||
{
|
||||
if (req.s >= req.n)
|
||||
throw std::invalid_argument("carry_plan: same_ring needs s < n");
|
||||
if (!same_width)
|
||||
{
|
||||
// Caller asked for a fused shift that reduces the ring: drop
|
||||
// carry-out and keep only the low-limb correction.
|
||||
if (req.out_n != req.n - req.s)
|
||||
throw std::invalid_argument(
|
||||
"carry_plan: same_ring out_n must be n or n-s");
|
||||
r.use_low_lt = true;
|
||||
break;
|
||||
}
|
||||
if (req.fuse || req.s > 0u)
|
||||
r.use_low_lt = (req.s > 0u) && req.fuse;
|
||||
|
||||
if (req.sign == sign_knowledge::unknown)
|
||||
{
|
||||
r.use_msb_lt = true;
|
||||
r.use_share_msb_and = true;
|
||||
r.msb_true_payload = 1; // returns msb bit; combined with AND below
|
||||
r.and_unit = carry_plan_detail::unit_pow2(req.n - req.s);
|
||||
}
|
||||
else if (req.sign == sign_knowledge::nonnegative)
|
||||
{
|
||||
r.use_share_msb_and = true;
|
||||
r.and_is_nor = false;
|
||||
r.and_unit = carry_plan_detail::unit_pow2(req.n - req.s);
|
||||
}
|
||||
else
|
||||
{
|
||||
r.use_share_msb_and = true;
|
||||
r.and_is_nor = true;
|
||||
r.and_unit = -carry_plan_detail::unit_pow2(req.n - req.s);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case carry_mode::extend:
|
||||
{
|
||||
if (req.out_n <= req.n)
|
||||
throw std::invalid_argument("carry_plan: extend needs out_n > n");
|
||||
// Known sign does not switch to the share-MSB AND.
|
||||
r.use_biased_wrap = true;
|
||||
r.wrap_payload = carry_plan_detail::unit_pow2(req.n);
|
||||
break;
|
||||
}
|
||||
case carry_mode::window:
|
||||
{
|
||||
if (req.s == 0u || req.s > req.n)
|
||||
throw std::invalid_argument("carry_plan: window needs 0 < s <= n");
|
||||
r.window_d = req.s;
|
||||
r.use_window_overflow = true;
|
||||
if (req.incoming_carry_public)
|
||||
{
|
||||
r.public_incoming = req.incoming_carry_value;
|
||||
// Public carry 0 drops the equality; a public 1 still needs the
|
||||
// all-ones test, but the product is local (multiply by 1).
|
||||
if (req.incoming_carry_value != 0u)
|
||||
r.use_window_eq = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
r.use_window_eq = true;
|
||||
r.use_window_product = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Fused same-ring request that somehow landed with a reduced modulus:
|
||||
// the planner already dropped the sign term above via `!same_width`.
|
||||
(void)reduced_ring;
|
||||
return r;
|
||||
}
|
||||
|
||||
/// @brief Fill a truncate-and-reduce request and plan it.
|
||||
/// \complexity Fills a `carry_request` and calls `plan_carry`. `Θ(1)`.
|
||||
/// @see grotto::plan_carry
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline carry_recipe plan_carry_in(unsigned n, unsigned s)
|
||||
{
|
||||
carry_request req{};
|
||||
req.n = n;
|
||||
req.s = s;
|
||||
req.out_n = n - s;
|
||||
req.mode = carry_mode::truncate_reduce;
|
||||
return plan_carry(req);
|
||||
}
|
||||
|
||||
/// @brief Fill a same-ring carry-out request and plan it.
|
||||
/// \complexity Fills a `carry_request` and calls `plan_carry`. `Θ(1)`.
|
||||
/// @see grotto::plan_carry
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline carry_recipe plan_carry_out(unsigned n, unsigned s, sign_knowledge sign)
|
||||
{
|
||||
carry_request req{};
|
||||
req.n = n;
|
||||
req.s = s;
|
||||
req.out_n = n;
|
||||
req.mode = carry_mode::same_ring;
|
||||
req.sign = sign;
|
||||
req.fuse = false;
|
||||
return plan_carry(req);
|
||||
}
|
||||
|
||||
/// @brief Fill a fused same-ring exact-shift request and plan it.
|
||||
/// @details When `out_n == n-s` the sign term is dropped automatically.
|
||||
/// \complexity Fills a `carry_request` and calls `plan_carry`. `Θ(1)`.
|
||||
/// @see grotto::plan_carry
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline carry_recipe plan_carry_fused(unsigned n, unsigned s, unsigned out_n,
|
||||
sign_knowledge sign = sign_knowledge::unknown)
|
||||
{
|
||||
carry_request req{};
|
||||
req.n = n;
|
||||
req.s = s;
|
||||
req.out_n = out_n;
|
||||
req.mode = carry_mode::same_ring;
|
||||
req.sign = sign;
|
||||
req.fuse = true;
|
||||
return plan_carry(req);
|
||||
}
|
||||
|
||||
} // namespace grotto
|
||||
|
||||
#endif // LIBDPF_INCLUDE_GROTTO_CARRY_PLAN_HPP__
|
||||
518
include/grotto/closed_form.hpp
Normal file
518
include/grotto/closed_form.hpp
Normal file
|
|
@ -0,0 +1,518 @@
|
|||
/// @file grotto/closed_form.hpp
|
||||
/// @brief Closed forms of the principal, range, and window maps.
|
||||
/// @details Inverse hyperbolics, inverse trig, SELU / ELU / CELU, softsign,
|
||||
/// tanhshrink, the logistic / exponential / Laplace / Cauchy
|
||||
/// quantiles, sinc, and the extra powers are compositions of
|
||||
/// `eval_reduced` and `eval_window`. `atan` on `[0, tan(π/8)]` is a
|
||||
/// odd power series; larger arguments reduce by `π/4` and `π/2`.
|
||||
/// Roots and `x^{-0.1}` go through `ln` and `exp`. `x^{1.5}` is
|
||||
/// `x √x` and `x^{-3}` is the reciprocal of an exact cube.
|
||||
/// Precision is one of 8, 12, ..., 32. These compositions inherit
|
||||
/// the range-reduction error, so they are not a 1-ulp claim.
|
||||
|
||||
#ifndef LIBDPF_INCLUDE_GROTTO_CLOSED_FORM_HPP__
|
||||
#define LIBDPF_INCLUDE_GROTTO_CLOSED_FORM_HPP__
|
||||
|
||||
#include "grotto/range_lut.hpp"
|
||||
#include "grotto/window_lut.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace grotto
|
||||
{
|
||||
|
||||
enum class closed : unsigned
|
||||
{
|
||||
atanh = 0,
|
||||
asinh,
|
||||
acosh,
|
||||
atan,
|
||||
acot,
|
||||
asec,
|
||||
acsc,
|
||||
asech,
|
||||
acsch,
|
||||
acoth,
|
||||
selu,
|
||||
elu,
|
||||
celu,
|
||||
softsign,
|
||||
tanhshrink,
|
||||
logistic,
|
||||
exponential,
|
||||
laplace,
|
||||
cauchy,
|
||||
sinc,
|
||||
cbrt,
|
||||
qtrt,
|
||||
icbrt,
|
||||
iqtrt,
|
||||
pow_m01,
|
||||
pow_p15,
|
||||
pow_m3,
|
||||
};
|
||||
|
||||
/// @brief Evaluate one closed form at a fixed-point raw value.
|
||||
/// @param which the closed-form function
|
||||
/// @param fractional_bits precision in {8, 12, ..., 32}
|
||||
/// @param raw the fixed-point argument
|
||||
/// @return the fixed-point result
|
||||
/// @throws std::invalid_argument if the precision is not a principal step
|
||||
/// @throws std::domain_error at a pole or outside the function's domain
|
||||
/// \complexity A constant number of `eval_reduced` / `eval_window` calls, plus the loops in this file:
|
||||
/// `atan_series` runs `n = 1 .. 47` (stops on a zero term), `sinc_series` runs `n = 1 .. 16`, `newton_sqrt_raw` runs 4 steps, `newton_icbrt_raw` runs 6 steps after a right-shift log of the magnitude.
|
||||
/// Extra space `Θ(1)`. These compositions are not a 1-ulp claim; the file comment says they inherit range-reduction error.
|
||||
/// @see grotto::eval_reduced
|
||||
/// @see grotto::eval_window
|
||||
/// @note Not a 1-ulp claim. Poles and domain exits throw `std::domain_error`.
|
||||
inline std::int64_t eval_closed(closed which, unsigned fractional_bits, std::int64_t raw);
|
||||
|
||||
namespace closed_detail
|
||||
{
|
||||
|
||||
using range_detail::div_raw;
|
||||
using range_detail::mul_raw;
|
||||
using range_detail::one_raw;
|
||||
using range_detail::scale_unit;
|
||||
using range_detail::u128;
|
||||
|
||||
inline constexpr u128 selu_alpha_64 = (u128{1} << 64) | 12419514725947086766ULL;
|
||||
inline constexpr u128 selu_scale_64 = (u128{1} << 64) | 935268138030932704ULL;
|
||||
inline constexpr u128 tenth_64 = 1844674407370955162ULL;
|
||||
inline constexpr u128 three_halves_64 = (u128{1} << 64) | 9223372036854775808ULL;
|
||||
|
||||
inline std::int64_t require_precision(unsigned fractional_bits)
|
||||
{
|
||||
if (!principal_precision(fractional_bits))
|
||||
throw std::invalid_argument("closed form: precision must be 8, 12, ..., 32");
|
||||
return one_raw(fractional_bits);
|
||||
}
|
||||
|
||||
inline std::int64_t abs_raw(std::int64_t raw)
|
||||
{
|
||||
if (raw >= 0)
|
||||
return raw;
|
||||
const auto mag = -static_cast<__int128>(raw);
|
||||
if (mag > INT64_MAX)
|
||||
throw std::overflow_error("closed form: magnitude does not fit int64");
|
||||
return static_cast<std::int64_t>(mag);
|
||||
}
|
||||
|
||||
inline std::int64_t half_of(std::int64_t raw)
|
||||
{
|
||||
const bool neg = raw < 0;
|
||||
auto mag = static_cast<std::uint64_t>(neg ? -static_cast<__int128>(raw) : raw);
|
||||
const std::uint64_t bit = mag & 1u;
|
||||
mag >>= 1;
|
||||
if (bit)
|
||||
++mag;
|
||||
if (mag > static_cast<std::uint64_t>(INT64_MAX))
|
||||
throw std::overflow_error("closed form: value does not fit int64");
|
||||
const auto out = static_cast<std::int64_t>(mag);
|
||||
return neg ? -out : out;
|
||||
}
|
||||
|
||||
inline std::int64_t div_int(std::int64_t raw, int denominator)
|
||||
{
|
||||
if (denominator <= 0)
|
||||
throw std::invalid_argument("closed form: divisor must be positive");
|
||||
const bool neg = raw < 0;
|
||||
auto mag = static_cast<__int128>(neg ? -static_cast<__int128>(raw) : raw);
|
||||
const __int128 den = denominator;
|
||||
__int128 quot = mag / den;
|
||||
if ((mag % den) * 2 >= den)
|
||||
++quot;
|
||||
if (quot > INT64_MAX)
|
||||
throw std::overflow_error("closed form: value does not fit int64");
|
||||
const auto out = static_cast<std::int64_t>(quot);
|
||||
return neg ? -out : out;
|
||||
}
|
||||
|
||||
inline std::int64_t pi_over_4(unsigned fractional_bits)
|
||||
{
|
||||
return scale_unit(range_detail::pi_over_4_64, fractional_bits);
|
||||
}
|
||||
|
||||
inline std::int64_t pi_over_2(unsigned fractional_bits)
|
||||
{
|
||||
const __int128 wide = static_cast<__int128>(pi_over_4(fractional_bits)) << 1;
|
||||
if (wide > INT64_MAX)
|
||||
throw std::overflow_error("closed form: pi/2 does not fit");
|
||||
return static_cast<std::int64_t>(wide);
|
||||
}
|
||||
|
||||
inline std::int64_t atan_series(unsigned fractional_bits, std::int64_t x)
|
||||
{
|
||||
__int128 acc = x;
|
||||
std::int64_t power = x;
|
||||
const std::int64_t x2 = mul_raw(x, x, fractional_bits);
|
||||
int sign = -1;
|
||||
for (int n = 1; n < 48; ++n)
|
||||
{
|
||||
power = mul_raw(power, x2, fractional_bits);
|
||||
const std::int64_t term = div_int(power, 2 * n + 1);
|
||||
if (term == 0)
|
||||
break;
|
||||
acc += sign < 0 ? -static_cast<__int128>(term) : term;
|
||||
sign = -sign;
|
||||
}
|
||||
if (acc > INT64_MAX || acc < INT64_MIN)
|
||||
throw std::overflow_error("closed form: atan does not fit");
|
||||
return static_cast<std::int64_t>(acc);
|
||||
}
|
||||
|
||||
inline std::int64_t atan_positive(unsigned fractional_bits, std::int64_t mag)
|
||||
{
|
||||
const std::int64_t one = one_raw(fractional_bits);
|
||||
if (mag > one)
|
||||
return pi_over_2(fractional_bits) - atan_positive(fractional_bits, div_raw(one, mag, fractional_bits));
|
||||
const std::int64_t bound = scale_unit(range_detail::sqrt2_64, fractional_bits) - one;
|
||||
if (mag > bound)
|
||||
{
|
||||
const std::int64_t t = div_raw(mag - one, mag + one, fractional_bits);
|
||||
return pi_over_4(fractional_bits) + atan_series(fractional_bits, t);
|
||||
}
|
||||
return atan_series(fractional_bits, mag);
|
||||
}
|
||||
|
||||
inline std::int64_t eval_atan(unsigned fractional_bits, std::int64_t raw)
|
||||
{
|
||||
require_precision(fractional_bits);
|
||||
if (raw < 0)
|
||||
return -atan_positive(fractional_bits, abs_raw(raw));
|
||||
return atan_positive(fractional_bits, raw);
|
||||
}
|
||||
|
||||
inline std::int64_t eval_ln_abs(unsigned fractional_bits, std::int64_t raw)
|
||||
{
|
||||
return eval_reduced(reduced::ln, fractional_bits, abs_raw(raw));
|
||||
}
|
||||
|
||||
inline std::int64_t eval_cbrt_abs(unsigned fractional_bits, std::int64_t mag)
|
||||
{
|
||||
if (mag == 0)
|
||||
return 0;
|
||||
const std::int64_t ln = eval_ln_abs(fractional_bits, mag);
|
||||
return eval_reduced(reduced::exp, fractional_bits, div_int(ln, 3));
|
||||
}
|
||||
|
||||
inline range_detail::u256 shl_u128(range_detail::u128 value, unsigned shift)
|
||||
{
|
||||
if (shift == 0)
|
||||
return range_detail::u256{value, 0};
|
||||
if (shift < 128)
|
||||
return range_detail::u256{value << shift, value >> (128u - shift)};
|
||||
return range_detail::u256{0, value << (shift - 128u)};
|
||||
}
|
||||
|
||||
inline bool u256_less(range_detail::u256 a, range_detail::u256 b)
|
||||
{
|
||||
if (a.hi != b.hi)
|
||||
return a.hi < b.hi;
|
||||
return a.lo < b.lo;
|
||||
}
|
||||
|
||||
/// @brief Rounded `(num << shift) / den`.
|
||||
inline range_detail::u128 div_shifted(range_detail::u128 num, range_detail::u128 den, unsigned shift)
|
||||
{
|
||||
if (den == 0)
|
||||
throw std::domain_error("closed form: division by zero");
|
||||
const range_detail::u256 target = shl_u128(num, shift);
|
||||
range_detail::u128 lo = 0;
|
||||
range_detail::u128 hi = 1;
|
||||
while (u256_less(range_detail::mul_u128(hi, den), target) ||
|
||||
(!u256_less(target, range_detail::mul_u128(hi, den)) && hi < (range_detail::u128{1} << 80)))
|
||||
{
|
||||
if (hi > (range_detail::u128{1} << 100))
|
||||
break;
|
||||
hi <<= 1;
|
||||
}
|
||||
while (lo + 1 < hi)
|
||||
{
|
||||
const range_detail::u128 mid = lo + (hi - lo) / 2;
|
||||
if (u256_less(target, range_detail::mul_u128(mid, den)))
|
||||
hi = mid;
|
||||
else
|
||||
lo = mid;
|
||||
}
|
||||
const range_detail::u256 half = range_detail::mul_u128(lo + lo + 1, den);
|
||||
if (!u256_less(target, half))
|
||||
return lo + 1;
|
||||
return lo;
|
||||
}
|
||||
|
||||
inline std::int64_t newton_sqrt_raw(unsigned fractional_bits, std::int64_t raw)
|
||||
{
|
||||
const unsigned K = fractional_bits * 2u;
|
||||
range_detail::u128 y = static_cast<range_detail::u128>(
|
||||
std::max<std::int64_t>(eval_reduced(reduced::sqrt, fractional_bits, raw), 1))
|
||||
<< fractional_bits;
|
||||
const range_detail::u128 x = static_cast<range_detail::u128>(raw) << fractional_bits;
|
||||
for (int step = 0; step < 4; ++step)
|
||||
{
|
||||
const range_detail::u128 quot = div_shifted(x, y, K);
|
||||
y = (y + quot + 1) >> 1;
|
||||
}
|
||||
const range_detail::u128 prod = range_detail::round_u256(
|
||||
range_detail::mul_u128(static_cast<range_detail::u128>(raw), y), K);
|
||||
if (prod > static_cast<range_detail::u128>(INT64_MAX))
|
||||
throw std::overflow_error("closed form: power does not fit int64");
|
||||
return static_cast<std::int64_t>(prod);
|
||||
}
|
||||
|
||||
inline std::int64_t newton_icbrt_raw(unsigned fractional_bits, std::int64_t mag)
|
||||
{
|
||||
const unsigned K = fractional_bits * 2u;
|
||||
int log = 0;
|
||||
auto bits = static_cast<std::uint64_t>(mag);
|
||||
while (bits > 1)
|
||||
{
|
||||
bits >>= 1;
|
||||
++log;
|
||||
}
|
||||
const int exp = log - static_cast<int>(fractional_bits);
|
||||
const int third = exp >= 0 ? exp / 3 : -(( -exp + 2) / 3);
|
||||
range_detail::u128 y = range_detail::u128{1} << static_cast<unsigned>(K + std::max(third, 0));
|
||||
if (third < 0)
|
||||
y >>= static_cast<unsigned>(-third);
|
||||
if (y == 0)
|
||||
y = 1;
|
||||
const range_detail::u128 x = static_cast<range_detail::u128>(mag) << fractional_bits;
|
||||
for (int step = 0; step < 6; ++step)
|
||||
{
|
||||
const range_detail::u128 y2 = range_detail::round_u256(range_detail::mul_u128(y, y), K);
|
||||
if (y2 == 0)
|
||||
break;
|
||||
const range_detail::u128 quot = div_shifted(x, y2, K);
|
||||
y = (y + y + quot) / 3;
|
||||
if (y == 0)
|
||||
y = 1;
|
||||
}
|
||||
const range_detail::u128 numer = range_detail::u128{1} << (K + fractional_bits);
|
||||
const range_detail::u128 inv = (numer + y / 2) / y;
|
||||
if (inv > static_cast<range_detail::u128>(INT64_MAX))
|
||||
throw std::overflow_error("closed form: inverse cube root does not fit int64");
|
||||
return static_cast<std::int64_t>(inv);
|
||||
}
|
||||
|
||||
inline std::int64_t sinc_series(unsigned fractional_bits, std::int64_t raw)
|
||||
{
|
||||
constexpr unsigned extra = 16;
|
||||
__int128 acc = __int128{1} << (fractional_bits + extra);
|
||||
__int128 term = acc;
|
||||
const std::int64_t mag = raw < 0 ? -raw : raw;
|
||||
for (int n = 1; n <= 16; ++n)
|
||||
{
|
||||
term = range_detail::shr_round_i128(term * mag, fractional_bits);
|
||||
term = range_detail::shr_round_i128(term * mag, fractional_bits);
|
||||
term = range_detail::div_round_i128(term, (2 * n) * (2 * n + 1));
|
||||
if (term == 0)
|
||||
break;
|
||||
acc += (n % 2) != 0 ? -term : term;
|
||||
}
|
||||
return range_detail::round_i128(acc, extra);
|
||||
}
|
||||
|
||||
inline std::int64_t square_plus(unsigned fractional_bits, std::int64_t raw, int sign)
|
||||
{
|
||||
const std::int64_t one = one_raw(fractional_bits);
|
||||
const std::int64_t sq = mul_raw(raw, raw, fractional_bits);
|
||||
const __int128 sum = static_cast<__int128>(sq) + (sign < 0 ? -one : one);
|
||||
if (sum < 0)
|
||||
throw std::domain_error("closed form: square is below the root domain");
|
||||
if (sum > INT64_MAX)
|
||||
throw std::overflow_error("closed form: square does not fit");
|
||||
return eval_reduced(reduced::sqrt, fractional_bits, static_cast<std::int64_t>(sum));
|
||||
}
|
||||
|
||||
} // namespace closed_detail
|
||||
/// \complexity A constant number of `eval_reduced` / `eval_window` calls, plus the loops in this file:
|
||||
/// `atan_series` runs `n = 1 .. 47` (stops on a zero term), `sinc_series` runs `n = 1 .. 16`, `newton_sqrt_raw` runs 4 steps, `newton_icbrt_raw` runs 6 steps after a right-shift log of the magnitude.
|
||||
/// Extra space `Θ(1)`. These compositions are not a 1-ulp claim; the file comment says they inherit range-reduction error.
|
||||
/// @see grotto::eval_reduced
|
||||
/// @see grotto::eval_window
|
||||
/// @note Not a 1-ulp claim. Poles and domain exits throw `std::domain_error`.
|
||||
|
||||
inline std::int64_t eval_closed(closed which, unsigned fractional_bits, std::int64_t raw)
|
||||
{
|
||||
using namespace closed_detail;
|
||||
const std::int64_t one = require_precision(fractional_bits);
|
||||
switch (which)
|
||||
{
|
||||
case closed::atan:
|
||||
return eval_atan(fractional_bits, raw);
|
||||
case closed::acot:
|
||||
return pi_over_2(fractional_bits) - eval_atan(fractional_bits, raw);
|
||||
case closed::asec:
|
||||
case closed::acsc:
|
||||
{
|
||||
if (abs_raw(raw) < one)
|
||||
throw std::domain_error("closed form: inverse secant requires |x| >= 1");
|
||||
const std::int64_t inv = div_raw(one, raw, fractional_bits);
|
||||
return which == closed::asec
|
||||
? eval_window(window::acos, fractional_bits, inv)
|
||||
: eval_window(window::asin, fractional_bits, inv);
|
||||
}
|
||||
case closed::atanh:
|
||||
{
|
||||
if (abs_raw(raw) >= one)
|
||||
throw std::domain_error("closed form: atanh domain is (-1, 1)");
|
||||
const std::int64_t plus = eval_reduced(reduced::log1p, fractional_bits, raw);
|
||||
const std::int64_t minus = eval_reduced(reduced::log1p, fractional_bits, -raw);
|
||||
return half_of(plus - minus);
|
||||
}
|
||||
case closed::asinh:
|
||||
{
|
||||
const std::int64_t mag = abs_raw(raw);
|
||||
std::int64_t sum = 0;
|
||||
try
|
||||
{
|
||||
sum = mag + square_plus(fractional_bits, mag, +1);
|
||||
}
|
||||
catch (const std::overflow_error &)
|
||||
{
|
||||
sum = 0;
|
||||
}
|
||||
const std::int64_t value = sum == 0
|
||||
? eval_ln_abs(fractional_bits, mag) + range_detail::ln2_raw(fractional_bits)
|
||||
: eval_reduced(reduced::ln, fractional_bits, sum);
|
||||
return raw < 0 ? -value : value;
|
||||
}
|
||||
case closed::acosh:
|
||||
{
|
||||
if (raw < one)
|
||||
throw std::domain_error("closed form: acosh domain is [1, inf)");
|
||||
return eval_reduced(reduced::ln, fractional_bits,
|
||||
raw + square_plus(fractional_bits, raw, -1));
|
||||
}
|
||||
case closed::asech:
|
||||
{
|
||||
if (raw <= 0 || raw > one)
|
||||
throw std::domain_error("closed form: asech domain is (0, 1]");
|
||||
return eval_closed(closed::acosh, fractional_bits, div_raw(one, raw, fractional_bits));
|
||||
}
|
||||
case closed::acsch:
|
||||
{
|
||||
if (raw == 0)
|
||||
throw std::domain_error("closed form: acsch pole");
|
||||
return eval_closed(closed::asinh, fractional_bits, div_raw(one, raw, fractional_bits));
|
||||
}
|
||||
case closed::acoth:
|
||||
{
|
||||
if (abs_raw(raw) <= one)
|
||||
throw std::domain_error("closed form: acoth domain is |x| > 1");
|
||||
return eval_closed(closed::atanh, fractional_bits, div_raw(one, raw, fractional_bits));
|
||||
}
|
||||
case closed::elu:
|
||||
return raw > 0 ? raw : eval_reduced(reduced::expm1, fractional_bits, raw);
|
||||
case closed::celu:
|
||||
return eval_closed(closed::elu, fractional_bits, raw);
|
||||
case closed::selu:
|
||||
{
|
||||
const std::int64_t body = raw > 0
|
||||
? raw
|
||||
: mul_raw(scale_unit(selu_alpha_64, fractional_bits),
|
||||
eval_reduced(reduced::expm1, fractional_bits, raw), fractional_bits);
|
||||
return mul_raw(scale_unit(selu_scale_64, fractional_bits), body, fractional_bits);
|
||||
}
|
||||
case closed::softsign:
|
||||
{
|
||||
const std::int64_t mag = abs_raw(raw);
|
||||
return div_raw(raw, one + mag, fractional_bits);
|
||||
}
|
||||
case closed::tanhshrink:
|
||||
return raw - eval_window(window::tanh, fractional_bits, raw);
|
||||
case closed::logistic:
|
||||
{
|
||||
if (raw <= 0 || raw >= one)
|
||||
throw std::domain_error("closed form: quantile domain is (0, 1)");
|
||||
const std::int64_t num = eval_reduced(reduced::ln, fractional_bits, raw);
|
||||
const std::int64_t den = eval_reduced(reduced::ln, fractional_bits, one - raw);
|
||||
return num - den;
|
||||
}
|
||||
case closed::exponential:
|
||||
{
|
||||
if (raw <= 0 || raw >= one)
|
||||
throw std::domain_error("closed form: quantile domain is (0, 1)");
|
||||
return -eval_reduced(reduced::ln, fractional_bits, one - raw);
|
||||
}
|
||||
case closed::laplace:
|
||||
{
|
||||
if (raw <= 0 || raw >= one)
|
||||
throw std::domain_error("closed form: quantile domain is (0, 1)");
|
||||
const std::int64_t half = one >> 1;
|
||||
if (raw <= half)
|
||||
return eval_reduced(reduced::ln, fractional_bits, raw << 1);
|
||||
return -eval_reduced(reduced::ln, fractional_bits, (one - raw) << 1);
|
||||
}
|
||||
case closed::cauchy:
|
||||
{
|
||||
if (raw <= 0 || raw >= one)
|
||||
throw std::domain_error("closed form: quantile domain is (0, 1)");
|
||||
const std::int64_t half = one >> 1;
|
||||
const std::int64_t pi = pi_over_2(fractional_bits) << 1;
|
||||
const std::int64_t angle = mul_raw(pi, raw - half, fractional_bits);
|
||||
return eval_reduced(reduced::tan, fractional_bits, angle);
|
||||
}
|
||||
case closed::sinc:
|
||||
if (raw == 0)
|
||||
return one;
|
||||
if (abs_raw(raw) <= one)
|
||||
return sinc_series(fractional_bits, raw);
|
||||
return div_raw(eval_reduced(reduced::sin, fractional_bits, raw), raw, fractional_bits);
|
||||
case closed::cbrt:
|
||||
case closed::icbrt:
|
||||
{
|
||||
if (which == closed::icbrt)
|
||||
{
|
||||
if (raw == 0)
|
||||
throw std::domain_error("closed form: inverse cube root of zero");
|
||||
const std::int64_t value = newton_icbrt_raw(fractional_bits, abs_raw(raw));
|
||||
return raw < 0 ? -value : value;
|
||||
}
|
||||
const std::int64_t root = eval_cbrt_abs(fractional_bits, abs_raw(raw));
|
||||
return raw < 0 ? -root : root;
|
||||
}
|
||||
case closed::qtrt:
|
||||
case closed::iqtrt:
|
||||
{
|
||||
if (raw < 0)
|
||||
throw std::domain_error("closed form: fourth root requires x >= 0");
|
||||
if (raw == 0)
|
||||
return which == closed::qtrt ? 0 : throw std::domain_error("closed form: inverse fourth root of zero"), 0;
|
||||
const std::int64_t ln = eval_ln_abs(fractional_bits, raw);
|
||||
const std::int64_t root = eval_reduced(reduced::exp, fractional_bits, div_int(ln, 4));
|
||||
return which == closed::qtrt ? root : div_raw(one, root, fractional_bits);
|
||||
}
|
||||
case closed::pow_m01:
|
||||
{
|
||||
if (raw <= 0)
|
||||
throw std::domain_error("closed form: x^{-0.1} requires x > 0");
|
||||
const std::int64_t ln = eval_ln_abs(fractional_bits, raw);
|
||||
const std::int64_t scaled = mul_raw(ln, scale_unit(tenth_64, fractional_bits), fractional_bits);
|
||||
return eval_reduced(reduced::exp, fractional_bits, -scaled);
|
||||
}
|
||||
case closed::pow_p15:
|
||||
{
|
||||
if (raw < 0)
|
||||
throw std::domain_error("closed form: x^{1.5} requires x >= 0");
|
||||
if (raw == 0)
|
||||
return 0;
|
||||
return newton_sqrt_raw(fractional_bits, raw);
|
||||
}
|
||||
case closed::pow_m3:
|
||||
{
|
||||
if (raw == 0)
|
||||
throw std::domain_error("closed form: x^{-3} pole");
|
||||
const std::int64_t sq = mul_raw(raw, raw, fractional_bits);
|
||||
const std::int64_t cube = mul_raw(sq, raw, fractional_bits);
|
||||
return div_raw(one, cube, fractional_bits);
|
||||
}
|
||||
}
|
||||
throw std::invalid_argument("closed form: unknown map");
|
||||
}
|
||||
|
||||
} // namespace grotto
|
||||
|
||||
#endif // LIBDPF_INCLUDE_GROTTO_CLOSED_FORM_HPP__
|
||||
|
|
@ -73,6 +73,9 @@ struct constant_lut
|
|||
return values.size() - 1;
|
||||
return values.size();
|
||||
}
|
||||
/// \complexity `upper_bound` on `bounds`. `Θ(log P)` comparisons, `P = linear_parts()`. Extra space `Θ(1)`.
|
||||
/// @param x raw domain point
|
||||
/// @return the piece value
|
||||
|
||||
HEDLEY_NO_THROW
|
||||
std::int64_t operator()(Raw x) const noexcept
|
||||
|
|
@ -161,7 +164,7 @@ inline bool magnitude_ge_pow10(u128 mag, int k, unsigned fractional_bits) noexce
|
|||
}
|
||||
|
||||
/// @brief Smallest positive magnitude whose base-10 log is at least `k`.
|
||||
/// @param k the `k`
|
||||
/// @param k index or exponent
|
||||
/// @param fractional_bits the number of fractional bits
|
||||
/// @return Smallest positive magnitude whose base-10 log is at least `k`
|
||||
HEDLEY_NO_THROW
|
||||
|
|
@ -522,6 +525,8 @@ struct exact_lut<exact_constant::clrsb>
|
|||
[=](Raw raw) { return eval<Raw>(raw, fractional_bits); });
|
||||
}
|
||||
};
|
||||
/// \complexity One call to `exact_lut<Which>::eval`. Sign maps are a branch. Bit maps scan at most the raw width. `Θ(w)` bit operations, extra space `Θ(1)`.
|
||||
/// @see grotto::make_exact_constant_lut
|
||||
|
||||
template <exact_constant Which, typename Raw>
|
||||
std::int64_t evaluate_exact(Raw raw, unsigned fractional_bits)
|
||||
|
|
@ -531,6 +536,15 @@ std::int64_t evaluate_exact(Raw raw, unsigned fractional_bits)
|
|||
throw std::invalid_argument("fractional bits exceed the raw width");
|
||||
return exact_lut<Which>::template eval<Raw>(raw, fractional_bits);
|
||||
}
|
||||
/// \complexity Dispatches to `exact_lut<Which>::project`. Sign patterns emit a constant number of cuts.
|
||||
/// `ilogb`, `ceil_ilogb`, `clz`, and `clrsb` push one cut per bit of the raw width (`Θ(w)`). `ilog10` walks powers of ten.
|
||||
/// Extra space is the cut and value vectors.
|
||||
/// @see grotto::evaluate_exact
|
||||
/// @see grotto::dyadic_lut
|
||||
/// @param which which degree-0 map
|
||||
/// @param fractional_bits must not exceed the raw width
|
||||
/// @return the piecewise-constant table
|
||||
/// @note Following Storrier, Vadapalli, Lyons, and Henry, ePrint 2023/108, Appendix D: the degree-0 exact gadgets.
|
||||
|
||||
template <typename Raw>
|
||||
constant_lut<Raw> make_exact_constant_lut(exact_constant which, unsigned fractional_bits)
|
||||
|
|
@ -577,6 +591,8 @@ enum class threshold_cmp
|
|||
gt,
|
||||
geq
|
||||
};
|
||||
/// \complexity One comparison. `Θ(1)`.
|
||||
/// @see grotto::make_threshold_lut
|
||||
|
||||
template <typename Raw>
|
||||
HEDLEY_NO_THROW
|
||||
|
|
@ -591,6 +607,8 @@ std::int64_t evaluate_threshold(Raw raw, Raw bound, threshold_cmp kind) noexcept
|
|||
}
|
||||
return 0;
|
||||
}
|
||||
/// \complexity Two or three cuts (the bound, and `bound + 1` for `leq` / `gt`). `Θ(1)`.
|
||||
/// @see grotto::evaluate_threshold
|
||||
|
||||
template <typename Raw>
|
||||
constant_lut<Raw> make_threshold_lut(Raw bound, threshold_cmp kind)
|
||||
|
|
@ -610,6 +628,8 @@ constant_lut<Raw> make_threshold_lut(Raw bound, threshold_cmp kind)
|
|||
/// @param high the upper endpoint
|
||||
/// @return `1` on the inclusive clip window `[low, high]`, `0` outside it
|
||||
/// @throws std::invalid_argument if `low > high`
|
||||
/// \complexity Two or three cuts. `Θ(1)`.
|
||||
/// @see grotto::make_threshold_lut
|
||||
template <typename Raw>
|
||||
constant_lut<Raw> make_interval_lut(Raw low, Raw high)
|
||||
{
|
||||
|
|
@ -637,6 +657,8 @@ constant_lut<Raw> make_interval_lut(Raw low, Raw high)
|
|||
/// @param high the upper endpoint
|
||||
/// @return `floor(min(max(raw, low), high) / modulus)`, division toward -infinity
|
||||
/// @throws std::invalid_argument if `modulus must be positive`
|
||||
/// \complexity One clamp and one division. `Θ(1)`. Division is toward -infinity, as the comment on the declaration says.
|
||||
/// @see grotto::make_clipped_quotient_lut
|
||||
template <typename Raw>
|
||||
std::int64_t evaluate_clipped_quotient(Raw raw, Raw modulus, Raw low, Raw high)
|
||||
{
|
||||
|
|
@ -652,6 +674,9 @@ std::int64_t evaluate_clipped_quotient(Raw raw, Raw modulus, Raw low, Raw high)
|
|||
const __int128 neg = -n;
|
||||
return static_cast<std::int64_t>(-((neg + d - 1) / d));
|
||||
}
|
||||
/// \complexity The cut loop steps by `modulus` from `low` through `high`. The header rejects spans with more than `2^16` pieces.
|
||||
/// Time and extra space `Θ((high - low) / modulus)` in raw units.
|
||||
/// @see grotto::evaluate_clipped_quotient
|
||||
|
||||
template <typename Raw>
|
||||
constant_lut<Raw> make_clipped_quotient_lut(Raw modulus, Raw low, Raw high)
|
||||
|
|
|
|||
277
include/grotto/dwt_lut.hpp
Normal file
277
include/grotto/dwt_lut.hpp
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
/// @file grotto/dwt_lut.hpp
|
||||
/// @brief Haar and bior(5,3) compressed lookup tables.
|
||||
/// @details Cleartext evaluators for Reis, Ugurbil, Wagh, Henry, and de Vega,
|
||||
/// PoPETs 2025 ([ePrint 2025/013](@ref bib_wave)). A power-of-two
|
||||
/// grid is reduced by a discrete wavelet transform and stored as
|
||||
/// fixed-point approximation coefficients. Haar evaluates with one
|
||||
/// lookup of the high bits. bior(5,3) evaluates Equation (8): two
|
||||
/// adjacent coefficients, weighted by `(2^j - lsb)` and `lsb`.
|
||||
/// @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_DWT_LUT_HPP__
|
||||
#define LIBDPF_INCLUDE_GROTTO_DWT_LUT_HPP__
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
namespace grotto
|
||||
{
|
||||
|
||||
/// @brief Which wavelet compresses the table.
|
||||
enum class dwt_family : unsigned
|
||||
{
|
||||
/// Orthogonal Haar. One approximation coefficient per block of `2^depth` samples.
|
||||
haar = 0,
|
||||
/// Biorthogonal bior(5,3), PyWavelets `bior2.2`. Two taps at evaluation.
|
||||
bior53,
|
||||
};
|
||||
|
||||
/// @brief Fixed-point Haar or bior(5,3) table.
|
||||
/// @details `coeff` holds the quantized approximation coefficients.
|
||||
/// `operator()` is the cleartext evaluation those MPC protocols return.
|
||||
struct dwt_lut
|
||||
{
|
||||
dwt_family family = dwt_family::haar;
|
||||
/// Transform depth `j`. The kept index is `raw >> depth`.
|
||||
unsigned depth = 0;
|
||||
/// `log2` of the sample count. Inputs lie in `[0, 2^{domain_bits})`.
|
||||
unsigned domain_bits = 0;
|
||||
/// Fraction bits of every stored coefficient and of the returned word.
|
||||
unsigned fractional_bits = 0;
|
||||
std::vector<std::int64_t> coeff;
|
||||
|
||||
/// \complexity Haar is one indexing step, `Θ(1)`. bior(5,3) is two multiplications and a shift by `2j`, `Θ(1)`. Extra space `Θ(1)`.
|
||||
/// @param raw index on the sampled grid, in `[0, 2^{domain_bits})`
|
||||
/// @return fixed-point approximation, `fractional_bits` fraction bits
|
||||
std::int64_t operator()(std::uint64_t raw) const
|
||||
{
|
||||
if (domain_bits == 0 || domain_bits > 30 || depth == 0 || depth > domain_bits)
|
||||
throw std::logic_error("dwt lut: table is not initialized");
|
||||
const std::uint64_t domain = std::uint64_t{1} << domain_bits;
|
||||
if (raw >= domain)
|
||||
throw std::out_of_range("dwt lut: input is outside the sampled domain");
|
||||
const std::uint64_t msb = raw >> depth;
|
||||
if (family == dwt_family::haar)
|
||||
{
|
||||
if (msb >= coeff.size())
|
||||
throw std::out_of_range("dwt lut: Haar index");
|
||||
return coeff[static_cast<std::size_t>(msb)];
|
||||
}
|
||||
if (coeff.empty() || msb >= coeff.size())
|
||||
throw std::out_of_range("dwt lut: bior index");
|
||||
// Smooth extension prepends two coefficients, so bin `msb` lives at
|
||||
// `msb + 2` and its neighbor at `msb + 3` (Equation (8), artifact indexing).
|
||||
const std::size_t n = coeff.size();
|
||||
const auto c0 = coeff[static_cast<std::size_t>((msb + 2) % n)];
|
||||
const auto c1 = coeff[static_cast<std::size_t>((msb + 3) % n)];
|
||||
const std::uint64_t span = std::uint64_t{1} << depth;
|
||||
const std::uint64_t lsb = raw & (span - 1);
|
||||
const __int128 comb = __int128(c0) * static_cast<__int128>(span - lsb)
|
||||
+ __int128(c1) * static_cast<__int128>(lsb);
|
||||
const __int128 den = __int128{1} << (2 * depth);
|
||||
return detail_floor_div(comb, den);
|
||||
}
|
||||
|
||||
private:
|
||||
static std::int64_t detail_floor_div(__int128 num, __int128 den)
|
||||
{
|
||||
__int128 q = num / den;
|
||||
const __int128 r = num % den;
|
||||
if (r != 0 && num < 0)
|
||||
--q;
|
||||
if (q > std::numeric_limits<std::int64_t>::max()
|
||||
|| q < std::numeric_limits<std::int64_t>::min())
|
||||
throw std::overflow_error("dwt lut: value does not fit int64");
|
||||
return static_cast<std::int64_t>(q);
|
||||
}
|
||||
};
|
||||
|
||||
namespace dwt_detail
|
||||
{
|
||||
|
||||
inline unsigned log2_pow2(std::size_t n)
|
||||
{
|
||||
if (n < 2 || (n & (n - 1)) != 0)
|
||||
throw std::invalid_argument("dwt lut: sample count must be a power of two, at least 2");
|
||||
unsigned bits = 0;
|
||||
while ((std::size_t{1} << bits) != n)
|
||||
++bits;
|
||||
if (bits > 30)
|
||||
throw std::invalid_argument("dwt lut: sample count exceeds 2^30");
|
||||
return bits;
|
||||
}
|
||||
|
||||
inline std::int64_t quantize(double y, unsigned fractional_bits)
|
||||
{
|
||||
const double scaled = std::floor(y * std::ldexp(1.0, static_cast<int>(fractional_bits)));
|
||||
const double lo = static_cast<double>(std::numeric_limits<std::int64_t>::min());
|
||||
if (!(scaled >= lo && scaled < 0x1p63))
|
||||
throw std::overflow_error("dwt lut: coefficient does not fit int64");
|
||||
return static_cast<std::int64_t>(scaled);
|
||||
}
|
||||
|
||||
inline double half_exp(int k)
|
||||
{
|
||||
double scale = std::ldexp(1.0, k / 2);
|
||||
if (k % 2 != 0)
|
||||
scale *= (k < 0) ? std::sqrt(0.5) : std::sqrt(2.0);
|
||||
return scale;
|
||||
}
|
||||
|
||||
inline std::vector<double> down_approx(const std::vector<double> & in, const double * filt, int F)
|
||||
{
|
||||
const int N = static_cast<int>(in.size());
|
||||
if (N < 1)
|
||||
throw std::invalid_argument("dwt lut: empty approximation");
|
||||
const int expect = (N + F - 1) / 2;
|
||||
if (N < 2)
|
||||
{
|
||||
double sum = 0;
|
||||
for (int j = 0; j < F; ++j)
|
||||
sum += filt[j];
|
||||
return std::vector<double>(static_cast<std::size_t>(expect), in[0] * sum);
|
||||
}
|
||||
|
||||
std::vector<double> out;
|
||||
out.reserve(static_cast<std::size_t>(expect));
|
||||
int i = 1;
|
||||
while (i < F && i < N)
|
||||
{
|
||||
double s = 0;
|
||||
int j = 0;
|
||||
for (; j <= i; ++j)
|
||||
s += filt[j] * in[static_cast<std::size_t>(i - j)];
|
||||
for (int k = 1; j < F; ++j, ++k)
|
||||
s += filt[j] * (in[0] + static_cast<double>(k) * (in[0] - in[1]));
|
||||
out.push_back(s);
|
||||
i += 2;
|
||||
}
|
||||
while (i < N)
|
||||
{
|
||||
double s = 0;
|
||||
for (int j = 0; j < F; ++j)
|
||||
s += in[static_cast<std::size_t>(i - j)] * filt[j];
|
||||
out.push_back(s);
|
||||
i += 2;
|
||||
}
|
||||
while (i < F)
|
||||
{
|
||||
double s = 0;
|
||||
int j = 0;
|
||||
for (int k = i - N + 1; i - j >= N; ++j, --k)
|
||||
s += filt[j] * (in[static_cast<std::size_t>(N - 1)]
|
||||
+ static_cast<double>(k) * (in[static_cast<std::size_t>(N - 1)]
|
||||
- in[static_cast<std::size_t>(N - 2)]));
|
||||
for (; j <= i; ++j)
|
||||
s += filt[j] * in[static_cast<std::size_t>(i - j)];
|
||||
for (int k = 1; j < F; ++j, ++k)
|
||||
s += filt[j] * (in[0] + static_cast<double>(k) * (in[0] - in[1]));
|
||||
out.push_back(s);
|
||||
i += 2;
|
||||
}
|
||||
while (i < N + F - 1)
|
||||
{
|
||||
double s = 0;
|
||||
int j = 0;
|
||||
for (int k = i - N + 1; i - j >= N; ++j, --k)
|
||||
s += filt[j] * (in[static_cast<std::size_t>(N - 1)]
|
||||
+ static_cast<double>(k) * (in[static_cast<std::size_t>(N - 1)]
|
||||
- in[static_cast<std::size_t>(N - 2)]));
|
||||
for (; j < F; ++j)
|
||||
s += filt[j] * in[static_cast<std::size_t>(i - j)];
|
||||
out.push_back(s);
|
||||
i += 2;
|
||||
}
|
||||
if (static_cast<int>(out.size()) != expect)
|
||||
throw std::logic_error("dwt lut: approximation length");
|
||||
return out;
|
||||
}
|
||||
|
||||
inline dwt_lut build(dwt_family family, const std::vector<double> & samples,
|
||||
unsigned fractional_bits, unsigned depth)
|
||||
{
|
||||
if (fractional_bits > 62)
|
||||
throw std::invalid_argument("dwt lut: fractional width does not fit");
|
||||
const unsigned domain_bits = log2_pow2(samples.size());
|
||||
if (depth == 0 || depth > domain_bits)
|
||||
throw std::invalid_argument("dwt lut: depth must lie in 1 .. domain bits");
|
||||
|
||||
const double s2 = std::sqrt(2.0);
|
||||
const double haar_lo[2] = {s2 / 2, s2 / 2};
|
||||
const double s2_8 = s2 / 8;
|
||||
const double s2_4 = s2 / 4;
|
||||
const double bior_lo[6] = {0.0, -s2_8, s2_4, 3 * s2_4, s2_4, -s2_8};
|
||||
|
||||
const double * filt = haar_lo;
|
||||
int flen = 2;
|
||||
int scale_sign = -1;
|
||||
if (family == dwt_family::bior53)
|
||||
{
|
||||
filt = bior_lo;
|
||||
flen = 6;
|
||||
scale_sign = 1;
|
||||
}
|
||||
|
||||
std::vector<double> approx = samples;
|
||||
for (unsigned level = 0; level < depth; ++level)
|
||||
approx = down_approx(approx, filt, flen);
|
||||
|
||||
const double scale = half_exp(scale_sign * static_cast<int>(depth));
|
||||
dwt_lut table;
|
||||
table.family = family;
|
||||
table.depth = depth;
|
||||
table.domain_bits = domain_bits;
|
||||
table.fractional_bits = fractional_bits;
|
||||
table.coeff.reserve(approx.size());
|
||||
for (double a : approx)
|
||||
table.coeff.push_back(quantize(a * scale, fractional_bits));
|
||||
return table;
|
||||
}
|
||||
|
||||
} // namespace dwt_detail
|
||||
|
||||
/// @brief Sample `f` on `i · 2^{-fractional_bits}` for `i` in `[0, 2^{domain_bits})`.
|
||||
/// \complexity `Θ(2^{domain_bits})` calls and extra space.
|
||||
/// @tparam Fn callable `double(double)`
|
||||
template <typename Fn>
|
||||
std::vector<double> sample_dwt_signal(unsigned domain_bits, unsigned fractional_bits, Fn && f)
|
||||
{
|
||||
if (domain_bits == 0 || domain_bits > 30)
|
||||
throw std::invalid_argument("dwt lut: domain bits must lie in 1 .. 30");
|
||||
if (fractional_bits > 62)
|
||||
throw std::invalid_argument("dwt lut: fractional width does not fit");
|
||||
const std::size_t n = std::size_t{1} << domain_bits;
|
||||
const double step = std::ldexp(1.0, -static_cast<int>(fractional_bits));
|
||||
std::vector<double> samples(n);
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
samples[i] = f(static_cast<double>(i) * step);
|
||||
return samples;
|
||||
}
|
||||
|
||||
/// \complexity One smooth-extension low-pass per level. `Θ(N)` arithmetic and extra space, `N` the sample count.
|
||||
/// @param samples real signal, length `2^{domain bits}`
|
||||
/// @param fractional_bits fraction bits of the stored words
|
||||
/// @param depth transform depth `j`, in `1 .. log2(samples)`
|
||||
dwt_lut make_haar_dwt_lut(const std::vector<double> & samples,
|
||||
unsigned fractional_bits, unsigned depth)
|
||||
{
|
||||
return dwt_detail::build(dwt_family::haar, samples, fractional_bits, depth);
|
||||
}
|
||||
|
||||
/// \complexity One smooth-extension low-pass per level. `Θ(N)` arithmetic and extra space, `N` the sample count.
|
||||
/// @param samples real signal, length `2^{domain bits}`
|
||||
/// @param fractional_bits fraction bits of the stored words
|
||||
/// @param depth transform depth `j`, in `1 .. log2(samples)`
|
||||
dwt_lut make_bior53_dwt_lut(const std::vector<double> & samples,
|
||||
unsigned fractional_bits, unsigned depth)
|
||||
{
|
||||
return dwt_detail::build(dwt_family::bior53, samples, fractional_bits, depth);
|
||||
}
|
||||
|
||||
} // namespace grotto
|
||||
|
||||
#endif // LIBDPF_INCLUDE_GROTTO_DWT_LUT_HPP__
|
||||
|
|
@ -227,6 +227,9 @@ easy_lut<Raw> predicate_lut(unsigned fractional_bits, bool at_or_below_zero,
|
|||
}
|
||||
|
||||
} // namespace detail
|
||||
/// \complexity Two or three cuts around 0, from `predicate_lut` / `steps_from_cuts`. `Θ(1)` time and extra space.
|
||||
/// @see grotto::make_exact_constant_lut
|
||||
/// @see grotto::easy_lut
|
||||
|
||||
template <typename Raw>
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
|
|
@ -234,6 +237,9 @@ easy_lut<Raw> make_positive_lut(unsigned fractional_bits = 0)
|
|||
{
|
||||
return detail::predicate_lut<Raw>(fractional_bits, false, false, true);
|
||||
}
|
||||
/// \complexity Two or three cuts around 0, from `predicate_lut` / `steps_from_cuts`. `Θ(1)` time and extra space.
|
||||
/// @see grotto::make_exact_constant_lut
|
||||
/// @see grotto::easy_lut
|
||||
|
||||
template <typename Raw>
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
|
|
@ -241,6 +247,9 @@ easy_lut<Raw> make_negative_lut(unsigned fractional_bits = 0)
|
|||
{
|
||||
return detail::predicate_lut<Raw>(fractional_bits, true, false, false);
|
||||
}
|
||||
/// \complexity Two or three cuts around 0, from `predicate_lut` / `steps_from_cuts`. `Θ(1)` time and extra space.
|
||||
/// @see grotto::make_exact_constant_lut
|
||||
/// @see grotto::easy_lut
|
||||
|
||||
template <typename Raw>
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
|
|
@ -248,6 +257,9 @@ easy_lut<Raw> make_nonnegative_lut(unsigned fractional_bits = 0)
|
|||
{
|
||||
return detail::predicate_lut<Raw>(fractional_bits, false, true, true);
|
||||
}
|
||||
/// \complexity Two or three cuts around 0, from `predicate_lut` / `steps_from_cuts`. `Θ(1)` time and extra space.
|
||||
/// @see grotto::make_exact_constant_lut
|
||||
/// @see grotto::easy_lut
|
||||
|
||||
template <typename Raw>
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
|
|
@ -255,6 +267,9 @@ easy_lut<Raw> make_nonpositive_lut(unsigned fractional_bits = 0)
|
|||
{
|
||||
return detail::predicate_lut<Raw>(fractional_bits, true, true, false);
|
||||
}
|
||||
/// \complexity Two or three cuts around 0, from `predicate_lut` / `steps_from_cuts`. `Θ(1)` time and extra space.
|
||||
/// @see grotto::make_exact_constant_lut
|
||||
/// @see grotto::easy_lut
|
||||
|
||||
template <typename Raw>
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
|
|
@ -262,6 +277,9 @@ easy_lut<Raw> make_zero_lut(unsigned fractional_bits = 0)
|
|||
{
|
||||
return detail::predicate_lut<Raw>(fractional_bits, false, true, false);
|
||||
}
|
||||
/// \complexity Two or three cuts around 0, from `predicate_lut` / `steps_from_cuts`. `Θ(1)` time and extra space.
|
||||
/// @see grotto::make_exact_constant_lut
|
||||
/// @see grotto::easy_lut
|
||||
|
||||
template <typename Raw>
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
|
|
@ -269,6 +287,9 @@ easy_lut<Raw> make_nonzero_lut(unsigned fractional_bits = 0)
|
|||
{
|
||||
return detail::predicate_lut<Raw>(fractional_bits, true, false, true);
|
||||
}
|
||||
/// \complexity Two or three cuts around 0, from `predicate_lut` / `steps_from_cuts`. `Θ(1)` time and extra space.
|
||||
/// @see grotto::make_exact_constant_lut
|
||||
/// @see grotto::easy_lut
|
||||
|
||||
template <typename Raw>
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
|
|
@ -283,6 +304,8 @@ easy_lut<Raw> make_signum_lut(unsigned fractional_bits = 0)
|
|||
return detail::easy_poly{one, 0, 0, 1};
|
||||
});
|
||||
}
|
||||
/// \complexity One cut per bit of `raw_width<Raw>()` (the powers of two, plus 0 and 1). `Θ(w)` time and extra space, `w` the raw width.
|
||||
/// @see grotto::eval_bit_width
|
||||
|
||||
template <typename Raw>
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
|
|
@ -296,6 +319,8 @@ easy_lut<Raw> make_clz_lut(unsigned fractional_bits = 0)
|
|||
return detail::unit_poly(detail::clz_of<Raw>(raw), fractional_bits);
|
||||
});
|
||||
}
|
||||
/// \complexity Cuts at `±2^e` across the raw width. `Θ(w)` time and extra space.
|
||||
/// @see grotto::exact_constant
|
||||
|
||||
template <typename Raw>
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
|
|
@ -316,6 +341,9 @@ easy_lut<Raw> make_clrsb_lut(unsigned fractional_bits = 0)
|
|||
return detail::unit_poly(detail::clrsb_of<Raw>(raw), fractional_bits);
|
||||
});
|
||||
}
|
||||
/// \complexity One cut per bit on each side of zero (`Θ(w)`), then one `ilogb_units` (a `floor_log2`) per piece. `Θ(w)` time and extra space.
|
||||
/// @see grotto::exact_constant
|
||||
/// @note `ilogb(0)` is the sentinel `ilog_of_zero`, not a logarithm.
|
||||
|
||||
template <typename Raw>
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
|
|
@ -339,6 +367,10 @@ easy_lut<Raw> make_ilogb_lut(unsigned fractional_bits = 0)
|
|||
fractional_bits);
|
||||
});
|
||||
}
|
||||
/// \complexity For each decimal exponent from `ilog10_units(1)` to the exponent of the type max, two binary searches over the positive raw domain (`Θ(w)` probes each).
|
||||
/// The exponent count is `Θ(w)`. Time `Θ(w²)` probes. Extra space is the cut vector, one cut per exponent class.
|
||||
/// @see grotto::exact_constant
|
||||
/// @note `ilog10(0)` is `ilog_of_zero`.
|
||||
|
||||
template <typename Raw>
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
|
|
@ -401,10 +433,14 @@ easy_lut<Raw> make_ilog10_lut(unsigned fractional_bits = 0)
|
|||
/// @details Index 0 is the sign bit. Larger indexes are refused: the bit is constant
|
||||
/// on `2^{index+1}` intervals.
|
||||
/// @tparam Raw underlying representation
|
||||
/// @param index the index
|
||||
/// @param index bit index counting down from the MSB; must be `< msb_bit_limit`
|
||||
/// @param fractional_bits the number of fractional bits
|
||||
/// @return Bit `index` counting down from the most significant bit of `Raw`
|
||||
/// @throws std::invalid_argument if `only the most significant bits are piecewise-cheap`
|
||||
/// \complexity The hot bit is constant on steps of `2^{width-1-index}`. The cut loop emits `Θ(2^{index})` boundaries, and `index` must be `< msb_bit_limit` (8), so at most 256 cuts.
|
||||
/// Time and extra space `Θ(2^{index})`.
|
||||
/// @see grotto::easy_lut
|
||||
/// @see grotto::eval_bit_width
|
||||
template <typename Raw>
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
easy_lut<Raw> make_msb_lut(unsigned index, unsigned fractional_bits = 0)
|
||||
|
|
|
|||
|
|
@ -40,6 +40,9 @@ struct easy_lut
|
|||
|
||||
HEDLEY_NO_THROW
|
||||
std::size_t parts() const noexcept { return c0.size(); }
|
||||
/// \complexity `upper_bound` on `bounds` (`Θ(log P)` comparisons, `P = parts()`), then three multiplies for the quadratic. Extra space `Θ(1)`.
|
||||
/// @param x raw fixed-point input
|
||||
/// @return the rounded piece value
|
||||
|
||||
std::int64_t operator()(Raw x) const
|
||||
{
|
||||
|
|
@ -155,6 +158,11 @@ inline std::int64_t denom_shift(unsigned shift)
|
|||
}
|
||||
|
||||
} // namespace detail
|
||||
/// \complexity Assembles a constant number of pieces (the knots are a fixed integer pattern shifted by `fractional_bits`). `Θ(1)` time and extra space, aside from the returned vectors of that constant length.
|
||||
/// @see grotto::easy_lut
|
||||
/// @see grotto::eval_window
|
||||
/// @param fractional_bits fractional bits; integer knots become `k << fractional_bits`
|
||||
/// @tparam Raw signed raw word
|
||||
|
||||
template <typename Raw>
|
||||
easy_lut<Raw> make_abs_lut(unsigned fractional_bits = 0)
|
||||
|
|
@ -167,6 +175,11 @@ easy_lut<Raw> make_abs_lut(unsigned fractional_bits = 0)
|
|||
return p;
|
||||
});
|
||||
}
|
||||
/// \complexity Assembles a constant number of pieces (the knots are a fixed integer pattern shifted by `fractional_bits`). `Θ(1)` time and extra space, aside from the returned vectors of that constant length.
|
||||
/// @see grotto::easy_lut
|
||||
/// @see grotto::eval_window
|
||||
/// @param fractional_bits fractional bits; integer knots become `k << fractional_bits`
|
||||
/// @tparam Raw signed raw word
|
||||
|
||||
template <typename Raw>
|
||||
easy_lut<Raw> make_relu_lut(unsigned fractional_bits = 0)
|
||||
|
|
@ -182,6 +195,9 @@ easy_lut<Raw> make_relu_lut(unsigned fractional_bits = 0)
|
|||
/// @tparam Raw underlying representation
|
||||
/// @param shift the bit shift
|
||||
/// @return Negative side is `x / 2^shift`, rounded to nearest, ties away from zero
|
||||
/// \complexity Assembles a constant number of pieces. `Θ(1)` time and extra space, aside from the returned vectors of that constant length.
|
||||
/// @see grotto::easy_lut
|
||||
/// @see grotto::eval_window
|
||||
template <typename Raw>
|
||||
easy_lut<Raw> make_leaky_relu_lut(unsigned shift)
|
||||
{
|
||||
|
|
@ -192,6 +208,11 @@ easy_lut<Raw> make_leaky_relu_lut(unsigned shift)
|
|||
return detail::easy_poly{0, 1, 0, den};
|
||||
});
|
||||
}
|
||||
/// \complexity Assembles a constant number of pieces (the knots are a fixed integer pattern shifted by `fractional_bits`). `Θ(1)` time and extra space, aside from the returned vectors of that constant length.
|
||||
/// @see grotto::easy_lut
|
||||
/// @see grotto::eval_window
|
||||
/// @param fractional_bits fractional bits; integer knots become `k << fractional_bits`
|
||||
/// @tparam Raw signed raw word
|
||||
|
||||
template <typename Raw>
|
||||
easy_lut<Raw> make_squared_relu_lut(unsigned fractional_bits)
|
||||
|
|
@ -233,18 +254,35 @@ easy_lut<Raw> clip_to(unsigned fractional_bits, std::int64_t low_units, std::int
|
|||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/// @brief Clip to `[low_units, high_units]` in raw units, then shift the knots by `fractional_bits`.
|
||||
/// @tparam Raw signed raw word
|
||||
/// @param fractional_bits fractional bits; integer knots become `k << fractional_bits`
|
||||
/// @param low_units inclusive lower clip, in integer units before the fractional shift
|
||||
/// @param high_units inclusive upper clip, in integer units before the fractional shift
|
||||
/// \complexity Assembles a constant number of pieces. `Θ(1)` time and extra space.
|
||||
/// @see grotto::easy_lut
|
||||
/// @see grotto::eval_window
|
||||
template <typename Raw>
|
||||
easy_lut<Raw> make_clip_lut(unsigned fractional_bits, std::int64_t low_units, std::int64_t high_units)
|
||||
{
|
||||
return detail::clip_to<Raw>(fractional_bits, low_units, high_units);
|
||||
}
|
||||
/// \complexity Assembles a constant number of pieces (the knots are a fixed integer pattern shifted by `fractional_bits`). `Θ(1)` time and extra space, aside from the returned vectors of that constant length.
|
||||
/// @see grotto::easy_lut
|
||||
/// @see grotto::eval_window
|
||||
/// @param fractional_bits fractional bits; integer knots become `k << fractional_bits`
|
||||
/// @tparam Raw signed raw word
|
||||
|
||||
template <typename Raw>
|
||||
easy_lut<Raw> make_relu6_lut(unsigned fractional_bits)
|
||||
{
|
||||
return make_clip_lut<Raw>(fractional_bits, 0, 6);
|
||||
}
|
||||
/// \complexity Assembles a constant number of pieces (the knots are a fixed integer pattern shifted by `fractional_bits`). `Θ(1)` time and extra space, aside from the returned vectors of that constant length.
|
||||
/// @see grotto::easy_lut
|
||||
/// @see grotto::eval_window
|
||||
/// @param fractional_bits fractional bits; integer knots become `k << fractional_bits`
|
||||
/// @tparam Raw signed raw word
|
||||
|
||||
template <typename Raw>
|
||||
easy_lut<Raw> make_hardtanh_lut(unsigned fractional_bits)
|
||||
|
|
@ -256,6 +294,9 @@ easy_lut<Raw> make_hardtanh_lut(unsigned fractional_bits)
|
|||
/// @tparam Raw underlying representation
|
||||
/// @param fractional_bits the number of fractional bits
|
||||
/// @return `0` on `[-1, 1]`, `x - 1` above, `x + 1` below
|
||||
/// \complexity Assembles a constant number of pieces (the knots are a fixed integer pattern shifted by `fractional_bits`). `Θ(1)` time and extra space, aside from the returned vectors of that constant length.
|
||||
/// @see grotto::easy_lut
|
||||
/// @see grotto::eval_window
|
||||
template <typename Raw>
|
||||
easy_lut<Raw> make_softshrink_lut(unsigned fractional_bits)
|
||||
{
|
||||
|
|
@ -281,6 +322,9 @@ easy_lut<Raw> make_softshrink_lut(unsigned fractional_bits)
|
|||
/// @tparam Raw underlying representation
|
||||
/// @param fractional_bits the number of fractional bits
|
||||
/// @return `0` on `[-1, 1]`, identity outside
|
||||
/// \complexity Assembles a constant number of pieces (the knots are a fixed integer pattern shifted by `fractional_bits`). `Θ(1)` time and extra space, aside from the returned vectors of that constant length.
|
||||
/// @see grotto::easy_lut
|
||||
/// @see grotto::eval_window
|
||||
template <typename Raw>
|
||||
easy_lut<Raw> make_hardshrink_lut(unsigned fractional_bits)
|
||||
{
|
||||
|
|
@ -305,6 +349,9 @@ easy_lut<Raw> make_hardshrink_lut(unsigned fractional_bits)
|
|||
/// @param fractional_bits the number of fractional bits
|
||||
/// @return `0` left of `-3`, `1` right of `3`, `(x + 3) / 6` between, rounded
|
||||
/// @throws std::invalid_argument if `fractional width does not fit`
|
||||
/// \complexity Assembles a constant number of pieces (the knots are a fixed integer pattern shifted by `fractional_bits`). `Θ(1)` time and extra space, aside from the returned vectors of that constant length.
|
||||
/// @see grotto::easy_lut
|
||||
/// @see grotto::eval_window
|
||||
template <typename Raw>
|
||||
easy_lut<Raw> make_hardsigmoid_lut(unsigned fractional_bits)
|
||||
{
|
||||
|
|
@ -335,6 +382,9 @@ easy_lut<Raw> make_hardsigmoid_lut(unsigned fractional_bits)
|
|||
/// @param fractional_bits the number of fractional bits
|
||||
/// @return `0` left of `-3`, `x` right of `3`, `x(x + 3) / 6` between, rounded
|
||||
/// @throws std::invalid_argument if `fractional width does not fit the denominator`
|
||||
/// \complexity Assembles a constant number of pieces (the knots are a fixed integer pattern shifted by `fractional_bits`). `Θ(1)` time and extra space, aside from the returned vectors of that constant length.
|
||||
/// @see grotto::easy_lut
|
||||
/// @see grotto::eval_window
|
||||
template <typename Raw>
|
||||
easy_lut<Raw> make_hardswish_lut(unsigned fractional_bits)
|
||||
{
|
||||
|
|
@ -360,6 +410,25 @@ easy_lut<Raw> make_hardswish_lut(unsigned fractional_bits)
|
|||
});
|
||||
}
|
||||
|
||||
/// @brief Appendix D leaky ReLU: identity on the right, `x/100` on the left.
|
||||
/// @details `make_leaky_relu_lut` is the dyadic slope `1/2^shift`. This one is
|
||||
/// the paper's slope `1/100`, rounded to nearest, ties away from zero.
|
||||
/// The fractional scale cancels, so the denominator does not depend
|
||||
/// on `fractional_bits`.
|
||||
/// @tparam Raw underlying representation
|
||||
/// @return two pieces, degree 1
|
||||
/// \complexity Assembles two pieces. `Θ(1)` time and extra space, aside from the returned vectors.
|
||||
/// @see grotto::make_leaky_relu_lut
|
||||
template <typename Raw>
|
||||
easy_lut<Raw> make_leaky_relu_hundredth_lut()
|
||||
{
|
||||
return detail::assemble_easy<Raw>({0}, [](std::int64_t raw) {
|
||||
if (raw >= 0)
|
||||
return detail::kIdentity;
|
||||
return detail::easy_poly{0, 1, 0, 100};
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace grotto
|
||||
|
||||
#endif // LIBDPF_INCLUDE_GROTTO_EASY_LUT_HPP__
|
||||
|
|
|
|||
407
include/grotto/exact_steps.hpp
Normal file
407
include/grotto/exact_steps.hpp
Normal file
|
|
@ -0,0 +1,407 @@
|
|||
/// @file grotto/exact_steps.hpp
|
||||
/// @brief Exact Grotto steps: digit lengths, integer logs, and word bits.
|
||||
/// @details Counts and boolean results are fixed-point integers,
|
||||
/// `n << fractional_bits`. `ilog16(0)`, `ilog256(0)`, and
|
||||
/// `logstar` of a non-positive input return `ilog_of_zero`.
|
||||
/// `dec_width`, `oct_width`, and `b64_width` count digits of
|
||||
/// `floor(|x|)` in bases 10, 8, and 64; zero has length 1.
|
||||
/// `bit_width`, `bit_floor`, `bit_ceil`, `countl_one`, and
|
||||
/// `has_single_bit` use the unsigned `Raw` bit pattern, matching
|
||||
/// the C++ `<bit>` operations on that width.
|
||||
/// `deg2rad` and `rad2deg` are the linear scale by `π/180` and
|
||||
/// `180/π`. They do not wrap the angle.
|
||||
|
||||
#ifndef LIBDPF_INCLUDE_GROTTO_EXACT_STEPS_HPP__
|
||||
#define LIBDPF_INCLUDE_GROTTO_EXACT_STEPS_HPP__
|
||||
|
||||
#include "grotto/dyadic_lut.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace grotto
|
||||
{
|
||||
|
||||
namespace exact_detail
|
||||
{
|
||||
|
||||
inline int ceil_div(int numerator, int denominator)
|
||||
{
|
||||
if (numerator >= 0)
|
||||
return (numerator + denominator - 1) / denominator;
|
||||
return -((-numerator) / denominator);
|
||||
}
|
||||
|
||||
template <typename Raw>
|
||||
int floor_log2_real(std::int64_t raw, unsigned fractional_bits, bool & exact_power)
|
||||
{
|
||||
const detail::u128 mag = detail::magnitude<Raw>(raw);
|
||||
exact_power = mag != 0 && (mag & (mag - 1)) == 0;
|
||||
return detail::floor_log2_u128(mag) - static_cast<int>(fractional_bits);
|
||||
}
|
||||
|
||||
inline std::int64_t abs_floor(std::int64_t raw, unsigned fractional_bits)
|
||||
{
|
||||
if (fractional_bits >= 63)
|
||||
throw std::invalid_argument("exact step: fractional width does not fit");
|
||||
const detail::u128 mag = raw < 0
|
||||
? detail::u128(-static_cast<__int128>(raw))
|
||||
: detail::u128(raw);
|
||||
return static_cast<std::int64_t>(mag >> fractional_bits);
|
||||
}
|
||||
|
||||
inline int positive_length(std::int64_t magnitude, int base)
|
||||
{
|
||||
if (magnitude <= 0)
|
||||
return 1;
|
||||
if (base == 2 || base == 8 || base == 64)
|
||||
{
|
||||
int log = 0;
|
||||
auto n = static_cast<std::uint64_t>(magnitude);
|
||||
while (n > 1)
|
||||
{
|
||||
n >>= 1;
|
||||
++log;
|
||||
}
|
||||
const int group = base == 2 ? 1 : (base == 8 ? 3 : 6);
|
||||
return log / group + 1;
|
||||
}
|
||||
int digits = 0;
|
||||
while (magnitude > 0)
|
||||
{
|
||||
magnitude /= base;
|
||||
++digits;
|
||||
}
|
||||
return digits;
|
||||
}
|
||||
|
||||
} // namespace exact_detail
|
||||
|
||||
/// @brief `ceil(log2(|x|) / 4)`, the integer log base 16.
|
||||
/// \complexity A constant number of shifts and a `countl` / `bit_width` style scan of the `Raw` width (at most 64 iterations of `n >>= 1` in `positive_length` / `floor_log2_real`). `Θ(width)` bit operations, extra space `Θ(1)`.
|
||||
/// Counts are returned as `units << fractional_bits`.
|
||||
/// @see grotto::make_exact_constant_lut
|
||||
/// @see grotto::eval_ilog16
|
||||
template <typename Raw>
|
||||
std::int64_t eval_ilog16(std::int64_t raw, unsigned fractional_bits)
|
||||
{
|
||||
if (raw == 0)
|
||||
return ilog_of_zero;
|
||||
bool exact = false;
|
||||
const int e = exact_detail::floor_log2_real<Raw>(raw, fractional_bits, exact);
|
||||
const int units = exact_detail::ceil_div(exact ? e : e + 1, 4);
|
||||
return detail::encode_units(units, fractional_bits);
|
||||
}
|
||||
|
||||
/// @brief `ceil(log2(|x|) / 8)`, the integer log base 256.
|
||||
/// \complexity A constant number of shifts and a `countl` / `bit_width` style scan of the `Raw` width (at most 64 iterations of `n >>= 1` in `positive_length` / `floor_log2_real`). `Θ(width)` bit operations, extra space `Θ(1)`.
|
||||
/// Counts are returned as `units << fractional_bits`.
|
||||
/// @see grotto::make_exact_constant_lut
|
||||
/// @see grotto::eval_ilog16
|
||||
template <typename Raw>
|
||||
std::int64_t eval_ilog256(std::int64_t raw, unsigned fractional_bits)
|
||||
{
|
||||
if (raw == 0)
|
||||
return ilog_of_zero;
|
||||
bool exact = false;
|
||||
const int e = exact_detail::floor_log2_real<Raw>(raw, fractional_bits, exact);
|
||||
const int units = exact_detail::ceil_div(exact ? e : e + 1, 8);
|
||||
return detail::encode_units(units, fractional_bits);
|
||||
}
|
||||
|
||||
/// @brief Iterated logarithm. `log*(x) = 0` for `|x| <= 1`, otherwise
|
||||
/// `1 + log*(log2(|x|))`. Non-positive inputs return `ilog_of_zero`.
|
||||
/// \complexity Five comparisons of the magnitude against `2^{fractional_bits + s}` for `s` in `{0,1,2,4,16}`. No iterated loop. `Θ(1)`, extra space `Θ(1)`.
|
||||
/// Non-positive inputs return `ilog_of_zero` (the dyadic sentinel), not a mathematical log-star.
|
||||
/// @see grotto::eval_ilog16
|
||||
template <typename Raw>
|
||||
std::int64_t eval_logstar(std::int64_t raw, unsigned fractional_bits)
|
||||
{
|
||||
if (raw <= 0)
|
||||
return ilog_of_zero;
|
||||
if (fractional_bits >= 63)
|
||||
throw std::invalid_argument("exact step: fractional width does not fit");
|
||||
const detail::u128 mag = detail::magnitude<Raw>(raw);
|
||||
const auto below = [&](unsigned shift) {
|
||||
if (fractional_bits + shift >= 128)
|
||||
return true;
|
||||
return mag <= (detail::u128{1} << (fractional_bits + shift));
|
||||
};
|
||||
int units = 5;
|
||||
if (below(0))
|
||||
units = 0;
|
||||
else if (below(1))
|
||||
units = 1;
|
||||
else if (below(2))
|
||||
units = 2;
|
||||
else if (below(4))
|
||||
units = 3;
|
||||
else if (below(16))
|
||||
units = 4;
|
||||
return detail::encode_units(units, fractional_bits);
|
||||
}
|
||||
|
||||
/// @brief `std::bit_width` of the unsigned `Raw` pattern.
|
||||
/// \complexity A constant number of shifts and a `countl` / `bit_width` style scan of the `Raw` width (at most 64 iterations of `n >>= 1` in `positive_length` / `floor_log2_real`). `Θ(width)` bit operations, extra space `Θ(1)`.
|
||||
/// Counts are returned as `units << fractional_bits`.
|
||||
/// @see grotto::make_exact_constant_lut
|
||||
/// @see grotto::eval_ilog16
|
||||
template <typename Raw>
|
||||
std::int64_t eval_bit_width(std::int64_t raw, unsigned fractional_bits)
|
||||
{
|
||||
const auto bits = detail::raw_bits<Raw>(raw);
|
||||
int units = 0;
|
||||
if (bits != 0)
|
||||
units = 64 - __builtin_clzll(bits);
|
||||
return detail::encode_units(units, fractional_bits);
|
||||
}
|
||||
|
||||
/// @brief `std::countl_one` of the unsigned `Raw` pattern.
|
||||
/// \complexity A constant number of shifts and a `countl` / `bit_width` style scan of the `Raw` width (at most 64 iterations of `n >>= 1` in `positive_length` / `floor_log2_real`). `Θ(width)` bit operations, extra space `Θ(1)`.
|
||||
/// Counts are returned as `units << fractional_bits`.
|
||||
/// @see grotto::make_exact_constant_lut
|
||||
/// @see grotto::eval_ilog16
|
||||
template <typename Raw>
|
||||
std::int64_t eval_countl_one(std::int64_t raw, unsigned fractional_bits)
|
||||
{
|
||||
const int units = detail::countl_one_width(
|
||||
detail::raw_bits<Raw>(raw), detail::raw_width<Raw>());
|
||||
return detail::encode_units(units, fractional_bits);
|
||||
}
|
||||
|
||||
/// @brief `std::has_single_bit` of the unsigned `Raw` pattern.
|
||||
/// \complexity A constant number of shifts and a `countl` / `bit_width` style scan of the `Raw` width (at most 64 iterations of `n >>= 1` in `positive_length` / `floor_log2_real`). `Θ(width)` bit operations, extra space `Θ(1)`.
|
||||
/// Counts are returned as `units << fractional_bits`.
|
||||
/// @see grotto::make_exact_constant_lut
|
||||
/// @see grotto::eval_ilog16
|
||||
template <typename Raw>
|
||||
std::int64_t eval_has_single_bit(std::int64_t raw, unsigned fractional_bits)
|
||||
{
|
||||
const auto bits = detail::raw_bits<Raw>(raw);
|
||||
const bool on = bits != 0 && (bits & (bits - 1)) == 0;
|
||||
return detail::encode_units(on ? 1 : 0, fractional_bits);
|
||||
}
|
||||
|
||||
/// @brief `std::bit_floor` of the unsigned `Raw` pattern, as a raw word.
|
||||
/// \complexity A constant number of shifts and a `countl` / `bit_width` style scan of the `Raw` width (at most 64 iterations of `n >>= 1` in `positive_length` / `floor_log2_real`). `Θ(width)` bit operations, extra space `Θ(1)`.
|
||||
/// Counts are returned as `units << fractional_bits`.
|
||||
/// @see grotto::make_exact_constant_lut
|
||||
/// @see grotto::eval_ilog16
|
||||
template <typename Raw>
|
||||
std::int64_t eval_bit_floor(std::int64_t raw)
|
||||
{
|
||||
const auto bits = detail::raw_bits<Raw>(raw);
|
||||
if (bits == 0)
|
||||
return 0;
|
||||
const int log = 63 - __builtin_clzll(bits);
|
||||
return static_cast<std::int64_t>(std::uint64_t{1} << static_cast<unsigned>(log));
|
||||
}
|
||||
|
||||
/// @brief `std::bit_ceil` of the unsigned `Raw` pattern, as a raw word.
|
||||
/// \complexity A constant number of shifts and a `countl` / `bit_width` style scan of the `Raw` width (at most 64 iterations of `n >>= 1` in `positive_length` / `floor_log2_real`). `Θ(width)` bit operations, extra space `Θ(1)`.
|
||||
/// Counts are returned as `units << fractional_bits`.
|
||||
/// @see grotto::make_exact_constant_lut
|
||||
/// @see grotto::eval_ilog16
|
||||
template <typename Raw>
|
||||
std::int64_t eval_bit_ceil(std::int64_t raw)
|
||||
{
|
||||
const auto bits = detail::raw_bits<Raw>(raw);
|
||||
if (bits <= 1)
|
||||
return 1;
|
||||
if ((bits & (bits - 1)) == 0)
|
||||
return static_cast<std::int64_t>(bits);
|
||||
const int log = 64 - __builtin_clzll(bits);
|
||||
if (log >= 63)
|
||||
throw std::overflow_error("exact step: bit_ceil does not fit int64");
|
||||
return std::int64_t{1} << log;
|
||||
}
|
||||
|
||||
/// @brief `floor(x)` at the same fractional scale.
|
||||
/// \complexity A constant number of shifts and a `countl` / `bit_width` style scan of the `Raw` width (at most 64 iterations of `n >>= 1` in `positive_length` / `floor_log2_real`). `Θ(width)` bit operations, extra space `Θ(1)`.
|
||||
/// Counts are returned as `units << fractional_bits`.
|
||||
/// @see grotto::make_exact_constant_lut
|
||||
/// @see grotto::eval_ilog16
|
||||
inline std::int64_t eval_dec_floor(std::int64_t raw, unsigned fractional_bits)
|
||||
{
|
||||
if (fractional_bits >= 63)
|
||||
throw std::invalid_argument("exact step: fractional width does not fit");
|
||||
const std::int64_t one = std::int64_t{1} << fractional_bits;
|
||||
std::int64_t q = raw / one;
|
||||
const std::int64_t r = raw - q * one;
|
||||
if (r < 0)
|
||||
--q;
|
||||
return q << fractional_bits;
|
||||
}
|
||||
|
||||
/// @brief `ceil(x)` at the same fractional scale.
|
||||
/// \complexity A constant number of shifts and a `countl` / `bit_width` style scan of the `Raw` width (at most 64 iterations of `n >>= 1` in `positive_length` / `floor_log2_real`). `Θ(width)` bit operations, extra space `Θ(1)`.
|
||||
/// Counts are returned as `units << fractional_bits`.
|
||||
/// @see grotto::make_exact_constant_lut
|
||||
/// @see grotto::eval_ilog16
|
||||
inline std::int64_t eval_dec_ceil(std::int64_t raw, unsigned fractional_bits)
|
||||
{
|
||||
if (fractional_bits >= 63)
|
||||
throw std::invalid_argument("exact step: fractional width does not fit");
|
||||
const std::int64_t one = std::int64_t{1} << fractional_bits;
|
||||
std::int64_t q = raw / one;
|
||||
const std::int64_t r = raw - q * one;
|
||||
if (r > 0)
|
||||
++q;
|
||||
return q << fractional_bits;
|
||||
}
|
||||
|
||||
/// @brief Digits of `floor(|x|)` in base 10, 8, or 64. Zero has length 1.
|
||||
/// \complexity A constant number of shifts and a `countl` / `bit_width` style scan of the `Raw` width (at most 64 iterations of `n >>= 1` in `positive_length` / `floor_log2_real`). `Θ(width)` bit operations, extra space `Θ(1)`.
|
||||
/// Counts are returned as `units << fractional_bits`.
|
||||
/// @see grotto::make_exact_constant_lut
|
||||
/// @see grotto::eval_ilog16
|
||||
inline std::int64_t eval_value_length(std::int64_t raw, unsigned fractional_bits, int base)
|
||||
{
|
||||
if (base != 8 && base != 10 && base != 64)
|
||||
throw std::invalid_argument("exact step: length base must be 8, 10, or 64");
|
||||
const int units = exact_detail::positive_length(
|
||||
exact_detail::abs_floor(raw, fractional_bits), base);
|
||||
return detail::encode_units(units, fractional_bits);
|
||||
}
|
||||
/// \complexity A constant number of shifts and a `countl` / `bit_width` style scan of the `Raw` width (at most 64 iterations of `n >>= 1` in `positive_length` / `floor_log2_real`). `Θ(width)` bit operations, extra space `Θ(1)`.
|
||||
/// Counts are returned as `units << fractional_bits`.
|
||||
/// @see grotto::make_exact_constant_lut
|
||||
/// @see grotto::eval_ilog16
|
||||
|
||||
inline std::int64_t eval_dec_width(std::int64_t raw, unsigned fractional_bits)
|
||||
{
|
||||
return eval_value_length(raw, fractional_bits, 10);
|
||||
}
|
||||
/// \complexity A constant number of shifts and a `countl` / `bit_width` style scan of the `Raw` width (at most 64 iterations of `n >>= 1` in `positive_length` / `floor_log2_real`). `Θ(width)` bit operations, extra space `Θ(1)`.
|
||||
/// Counts are returned as `units << fractional_bits`.
|
||||
/// @see grotto::make_exact_constant_lut
|
||||
/// @see grotto::eval_ilog16
|
||||
|
||||
inline std::int64_t eval_oct_width(std::int64_t raw, unsigned fractional_bits)
|
||||
{
|
||||
return eval_value_length(raw, fractional_bits, 8);
|
||||
}
|
||||
/// \complexity A constant number of shifts and a `countl` / `bit_width` style scan of the `Raw` width (at most 64 iterations of `n >>= 1` in `positive_length` / `floor_log2_real`). `Θ(width)` bit operations, extra space `Θ(1)`.
|
||||
/// Counts are returned as `units << fractional_bits`.
|
||||
/// @see grotto::make_exact_constant_lut
|
||||
/// @see grotto::eval_ilog16
|
||||
|
||||
inline std::int64_t eval_b64_width(std::int64_t raw, unsigned fractional_bits)
|
||||
{
|
||||
return eval_value_length(raw, fractional_bits, 64);
|
||||
}
|
||||
|
||||
/// @brief True when `floor(|x|)` is a single decimal digit, including 0.
|
||||
/// \complexity A constant number of shifts and a `countl` / `bit_width` style scan of the `Raw` width (at most 64 iterations of `n >>= 1` in `positive_length` / `floor_log2_real`). `Θ(width)` bit operations, extra space `Θ(1)`.
|
||||
/// Counts are returned as `units << fractional_bits`.
|
||||
/// @see grotto::make_exact_constant_lut
|
||||
/// @see grotto::eval_ilog16
|
||||
inline std::int64_t eval_has_single_digit(std::int64_t raw, unsigned fractional_bits)
|
||||
{
|
||||
const std::int64_t mag = exact_detail::abs_floor(raw, fractional_bits);
|
||||
return detail::encode_units(mag <= 9 ? 1 : 0, fractional_bits);
|
||||
}
|
||||
|
||||
namespace exact_detail
|
||||
{
|
||||
|
||||
inline constexpr detail::u128 pi_over_180_64 = 321956420358983237ULL;
|
||||
inline constexpr detail::u128 rad_to_deg_64 = (detail::u128{57} << 64) | 5456168980075999427ULL;
|
||||
|
||||
inline std::int64_t scale_angle(detail::u128 mag64, unsigned fractional_bits)
|
||||
{
|
||||
if (fractional_bits > 64)
|
||||
throw std::invalid_argument("exact step: fractional width does not fit");
|
||||
const unsigned shift = 64u - fractional_bits;
|
||||
detail::u128 mag = mag64;
|
||||
if (shift > 0)
|
||||
{
|
||||
mag += detail::u128{1} << (shift - 1);
|
||||
mag >>= shift;
|
||||
}
|
||||
if (mag > static_cast<detail::u128>(INT64_MAX))
|
||||
throw std::overflow_error("exact step: angle scale does not fit");
|
||||
return static_cast<std::int64_t>(mag);
|
||||
}
|
||||
|
||||
inline std::int64_t mul_angle(std::int64_t raw, std::int64_t factor, unsigned fractional_bits)
|
||||
{
|
||||
const __int128 prod = static_cast<__int128>(raw) * factor;
|
||||
const bool neg = prod < 0;
|
||||
auto mag = static_cast<detail::u128>(neg ? -prod : prod);
|
||||
if (fractional_bits > 0)
|
||||
{
|
||||
mag += detail::u128{1} << (fractional_bits - 1);
|
||||
mag >>= fractional_bits;
|
||||
}
|
||||
if (mag > static_cast<detail::u128>(INT64_MAX))
|
||||
throw std::overflow_error("exact step: angle does not fit int64");
|
||||
const auto out = static_cast<std::int64_t>(mag);
|
||||
return neg ? -out : out;
|
||||
}
|
||||
|
||||
} // namespace exact_detail
|
||||
|
||||
/// @brief Degrees to radians, `x * π / 180`.
|
||||
/// \complexity A constant number of shifts and a `countl` / `bit_width` style scan of the `Raw` width (at most 64 iterations of `n >>= 1` in `positive_length` / `floor_log2_real`). `Θ(width)` bit operations, extra space `Θ(1)`.
|
||||
/// Counts are returned as `units << fractional_bits`.
|
||||
/// @see grotto::make_exact_constant_lut
|
||||
/// @see grotto::eval_ilog16
|
||||
inline std::int64_t eval_deg2rad(std::int64_t raw, unsigned fractional_bits)
|
||||
{
|
||||
return exact_detail::mul_angle(
|
||||
raw, exact_detail::scale_angle(exact_detail::pi_over_180_64, fractional_bits),
|
||||
fractional_bits);
|
||||
}
|
||||
|
||||
/// @brief Radians to degrees, `x * 180 / π`.
|
||||
/// \complexity A constant number of shifts and a `countl` / `bit_width` style scan of the `Raw` width (at most 64 iterations of `n >>= 1` in `positive_length` / `floor_log2_real`). `Θ(width)` bit operations, extra space `Θ(1)`.
|
||||
/// Counts are returned as `units << fractional_bits`.
|
||||
/// @see grotto::make_exact_constant_lut
|
||||
/// @see grotto::eval_ilog16
|
||||
inline std::int64_t eval_rad2deg(std::int64_t raw, unsigned fractional_bits)
|
||||
{
|
||||
return exact_detail::mul_angle(
|
||||
raw, exact_detail::scale_angle(exact_detail::rad_to_deg_64, fractional_bits),
|
||||
fractional_bits);
|
||||
}
|
||||
|
||||
/// @brief Piecewise-constant LUT of a step whose value is an encoded integer.
|
||||
/// @details The domain scan is the cut set. `Raw` wider than 16 bits is refused.
|
||||
/// \complexity Scans every raw value from `numeric_limits<Raw>::min()` to `max()`. The header rejects `width > 16`, so the scan is `Θ(2^w)` with `w ≤ 16`.
|
||||
/// The result stores one cut per change of `at`. Extra space is that cut vector.
|
||||
/// @see grotto::eval_dec_floor
|
||||
/// @see grotto::easy_lut
|
||||
template <typename Raw, typename At>
|
||||
easy_lut<Raw> make_exact_step_lut(unsigned fractional_bits, At && at)
|
||||
{
|
||||
(void)fractional_bits;
|
||||
constexpr int width = detail::raw_width<Raw>();
|
||||
if (width > 16)
|
||||
throw std::invalid_argument("exact step lut: raw width must be at most 16");
|
||||
using lim = std::numeric_limits<Raw>;
|
||||
const auto minv = static_cast<std::int64_t>(lim::min());
|
||||
const auto maxv = static_cast<std::int64_t>(lim::max());
|
||||
std::vector<std::int64_t> cuts;
|
||||
auto previous = at(minv);
|
||||
for (std::int64_t raw = minv + 1; raw <= maxv; ++raw)
|
||||
{
|
||||
const auto value = at(raw);
|
||||
if (value != previous)
|
||||
{
|
||||
cuts.push_back(raw);
|
||||
previous = value;
|
||||
}
|
||||
if (raw == maxv)
|
||||
break;
|
||||
}
|
||||
return detail::steps_from_cuts<Raw>(std::move(cuts), [=](std::int64_t raw) {
|
||||
return detail::easy_poly{at(raw), 0, 0, 1};
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace grotto
|
||||
|
||||
#endif // LIBDPF_INCLUDE_GROTTO_EXACT_STEPS_HPP__
|
||||
|
|
@ -39,7 +39,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.
|
||||
/// @param rounded the `rounded`
|
||||
/// @param rounded already-rounded finite double
|
||||
/// @return Integer value of an already-rounded finite double, as a 256-bit word
|
||||
HEDLEY_NO_THROW
|
||||
inline uint256_t uint256_from_rounded_double(double rounded) noexcept
|
||||
|
|
@ -102,7 +102,7 @@ struct is_static_castable<To, From,
|
|||
/// @brief Low `bits` of `wide`, saturated to all-ones when `wide` does not fit.
|
||||
/// @tparam Raw underlying representation
|
||||
/// @tparam Bits bits
|
||||
/// @param wide the `wide`
|
||||
/// @param wide wide integer before it is narrowed
|
||||
/// @return Low `bits` of `wide`, saturated to all-ones when `wide` does not fit
|
||||
template <typename Raw, std::size_t Bits>
|
||||
HEDLEY_NO_THROW
|
||||
|
|
@ -212,7 +212,7 @@ inline IntegralType rounded_double_to_integral(double rounded) noexcept
|
|||
/// @tparam IntegralType underlying integral type
|
||||
/// @tparam FractionalBits number of fractional bits
|
||||
/// @tparam T value type
|
||||
/// @param integer_value the `integer_value`
|
||||
/// @param integer_value mathematical integer; stored as `integer_value << FractionalBits`
|
||||
/// @return the returned `IntegralType`
|
||||
template <typename IntegralType,
|
||||
unsigned FractionalBits,
|
||||
|
|
@ -240,7 +240,7 @@ inline constexpr bool is_signed_rep_v =
|
|||
/// @brief Two's-complement negate via the unsigned width. Defined for the
|
||||
/// most-negative value (wraps); signed `-x` would be UB there.
|
||||
/// @tparam IntegralType underlying integral type
|
||||
/// @param x the `x`
|
||||
/// @param x the input value
|
||||
/// @return Two's-complement negate via the unsigned width
|
||||
template <typename IntegralType>
|
||||
HEDLEY_ALWAYS_INLINE
|
||||
|
|
@ -269,8 +269,8 @@ constexpr IntegralType raw_abs(IntegralType x) noexcept
|
|||
/// @brief Remainder with the sign of `a` and magnitude `< |b|` (C++ `%` /
|
||||
/// `std::fmod`). Zero divisor → 0; this type has no NaN.
|
||||
/// @tparam IntegralType underlying integral type
|
||||
/// @param a the `a`
|
||||
/// @param b the `b`
|
||||
/// @param a left-hand operand
|
||||
/// @param b right-hand operand
|
||||
/// @return Remainder with the sign of `a` and magnitude `< |b|` (C++ `%` / `std::fmod`)
|
||||
template <typename IntegralType>
|
||||
HEDLEY_ALWAYS_INLINE
|
||||
|
|
@ -292,10 +292,20 @@ template <unsigned FractionalBits,
|
|||
HEDLEY_NO_THROW
|
||||
auto constexpr make_fixed_from_integral_type(IntegralType value) noexcept;
|
||||
|
||||
/// @tparam FractionalBits Number of fractional bits used in the fixed-point
|
||||
/// @brief representation.
|
||||
/// @tparam IntegralType The underlying integral type used for the fixed-point
|
||||
/// representation.
|
||||
/// @brief Fixed-point value with `FractionalBits` bits after the binary point.
|
||||
/// @tparam FractionalBits Number of fractional bits. A DPF on this type has
|
||||
/// depth equal to the backend width, not `FractionalBits`.
|
||||
/// @tparam IntegralType Backend word. Defaults to `uint64_t`, so
|
||||
/// `fixedpoint<16>` is a Q48.16 value.
|
||||
/// @note `fixedpoint(3)` is the mathematical value 3, stored as
|
||||
/// `3 << FractionalBits`. It is not a raw word. `from_raw` is bit-exact.
|
||||
/// @note Leaf addition, subtraction, and multiplication act on that raw word
|
||||
/// and do not shift the binary point. See `dpf::leaf_arithmetic`.
|
||||
/// @note A signed backend flips the MSB when the value is a DPF input
|
||||
/// (`dpf::utils::flip_msb_for_input`).
|
||||
/// @see grotto::fixed_mul
|
||||
/// @see [Input types](@ref input_types)
|
||||
/// @see [Output types](@ref output_types)
|
||||
template <unsigned FractionalBits,
|
||||
typename IntegralType = GROTTO_FIXED_DEFAULT_INTEGRAL_REPRESENTATION>
|
||||
struct fixedpoint
|
||||
|
|
@ -341,7 +351,7 @@ public:
|
|||
|
||||
/// @brief Value c'tor
|
||||
/// @details Initializes the fixed-point with the value determined by `desired`, using the <a href="https://en.cppreference.com/w/cpp/numeric/fenv/FE_round">current rounding mode</a> for the least-significant bit.
|
||||
/// @param desired the `desired`
|
||||
/// @param desired real value, rounded with the current rounding mode
|
||||
HEDLEY_ALWAYS_INLINE
|
||||
HEDLEY_NO_THROW
|
||||
constexpr fixedpoint(double desired) noexcept // NOLINT (implicit c'tor)
|
||||
|
|
@ -355,7 +365,7 @@ public:
|
|||
/// Use `from_raw` for a bit-exact encoding.
|
||||
/// @tparam T value type
|
||||
/// @tparam T value type
|
||||
/// @param integer_value the `integer_value`
|
||||
/// @param integer_value mathematical integer; stored as `integer_value << FractionalBits`
|
||||
template <typename T,
|
||||
std::enable_if_t<
|
||||
std::is_integral_v<T>
|
||||
|
|
@ -370,6 +380,9 @@ public:
|
|||
/// @brief Bit-exact construction from the backend integer encoding.
|
||||
/// @param raw the underlying integer
|
||||
/// @return Bit-exact construction from the backend integer encoding
|
||||
/// \complexity `Θ(1)`. Copies the backend word. No shift and no rounding.
|
||||
/// @note Bit-exact. Unlike `fixedpoint(3)`, this does not mean the mathematical value `raw`.
|
||||
/// @see grotto::fixedpoint
|
||||
HEDLEY_ALWAYS_INLINE
|
||||
HEDLEY_NO_THROW
|
||||
static constexpr fixedpoint from_raw(integral_type raw) noexcept
|
||||
|
|
@ -401,7 +414,7 @@ public:
|
|||
|
||||
/// @brief Value assignment
|
||||
/// @details Assigns the fixed-point with a value determined by `desired`, using the <a href="https://en.cppreference.com/w/cpp/numeric/fenv/FE_round">current rounding mode</a> for the least-significant bit..
|
||||
/// @param desired the `desired`
|
||||
/// @param desired real value, rounded with the current rounding mode
|
||||
/// @return `*this`
|
||||
HEDLEY_ALWAYS_INLINE
|
||||
HEDLEY_NO_THROW
|
||||
|
|
@ -557,6 +570,9 @@ public:
|
|||
/// @details Computes the sum of two fixed-point numbers
|
||||
/// @param rhs the right-hand operand
|
||||
/// @return Binary addition operator
|
||||
/// \complexity One backend-word operation. `Θ(1)` time and extra space in that word width.
|
||||
/// @note Same-scale addition and subtraction add the raw words. That is the fixed-point sum; the binary point does not move.
|
||||
/// @see grotto::fixed_mul
|
||||
HEDLEY_ALWAYS_INLINE
|
||||
HEDLEY_NO_THROW
|
||||
HEDLEY_PURE
|
||||
|
|
@ -579,6 +595,9 @@ public:
|
|||
/// @brief Binary subtraction operator
|
||||
/// @param rhs the right-hand operand
|
||||
/// @return Binary subtraction operator
|
||||
/// \complexity One backend-word operation. `Θ(1)` time and extra space in that word width.
|
||||
/// @note Same-scale addition and subtraction add the raw words. That is the fixed-point sum; the binary point does not move.
|
||||
/// @see grotto::fixed_mul
|
||||
HEDLEY_ALWAYS_INLINE
|
||||
HEDLEY_NO_THROW
|
||||
HEDLEY_PURE
|
||||
|
|
@ -602,6 +621,11 @@ public:
|
|||
/// @tparam FractionalBits1 fractional bits1
|
||||
/// @param rhs the right-hand operand
|
||||
/// @return Binary multiplication operator
|
||||
/// \complexity One product of the raw backend words. The result is `make_fixed_from_integral_type` at `FractionalBits + FractionalBits1` fractional bits.
|
||||
/// `Θ(1)` in the backend width (the 64-bit or 128-bit branch in `multiplies` is the rescaling multiply; this operator does not loop).
|
||||
/// @note This is not leaf multiply. Leaf multiply stays on the raw word and does not change `FractionalBits`.
|
||||
/// @see grotto::fixed_mul
|
||||
/// @see grotto::fixedpoint
|
||||
template <unsigned FractionalBits1>
|
||||
HEDLEY_ALWAYS_INLINE
|
||||
HEDLEY_NO_THROW
|
||||
|
|
@ -751,7 +775,7 @@ public:
|
|||
// struct make_fixed_from_integral_type_tag {};
|
||||
|
||||
/// @brief Determine if a floating-point is within range
|
||||
/// @param d the `d`
|
||||
/// @param d real value
|
||||
/// @return Determine if a floating-point is within range
|
||||
HEDLEY_ALWAYS_INLINE
|
||||
HEDLEY_NO_THROW
|
||||
|
|
@ -790,7 +814,7 @@ public:
|
|||
/// @tparam IntegralType underlying integral type
|
||||
/// @tparam Mask mask
|
||||
/// @param mask the bit mask
|
||||
/// @param x the `x`
|
||||
/// @param x the input value
|
||||
/// @return Bit test with the mask on the left
|
||||
template <unsigned FractionalBits,
|
||||
typename IntegralType,
|
||||
|
|
@ -829,6 +853,14 @@ operator>>(std::basic_istream<CharT, Traits> & is,
|
|||
return is;
|
||||
}
|
||||
|
||||
/// @brief Bit-exact fixed-point from a backend word.
|
||||
/// @tparam FractionalBits number of fractional bits
|
||||
/// @tparam IntegralType backend word type
|
||||
/// @param value the raw encoding, not the mathematical integer
|
||||
/// @return `fixedpoint::from_raw(value)`
|
||||
/// @note This does not shift by `FractionalBits`. `make_fixed_from_integral_type(1)` is the raw word 1, whereas `fixedpoint(1)` is the value 1.
|
||||
/// @see grotto::fixedpoint::from_raw
|
||||
/// \complexity `Θ(1)`. One `from_raw`.
|
||||
template <unsigned FractionalBits,
|
||||
typename IntegralType>
|
||||
HEDLEY_NO_THROW
|
||||
|
|
@ -836,6 +868,9 @@ auto constexpr make_fixed_from_integral_type(IntegralType value) noexcept
|
|||
{
|
||||
return fixedpoint<FractionalBits, IntegralType>::from_raw(value);
|
||||
}
|
||||
/// \complexity One scale by `2^{FractionalBits}` (`ldexp`) and `nearbyint` under the current rounding mode. `Θ(1)`.
|
||||
/// @note This is the real value, same as `fixedpoint(double)`. It is not `from_raw`.
|
||||
/// @see grotto::fixedpoint
|
||||
|
||||
template <unsigned FractionalBits,
|
||||
typename IntegralType = GROTTO_FIXED_DEFAULT_INTEGRAL_REPRESENTATION>
|
||||
|
|
@ -845,6 +880,9 @@ static constexpr auto make_fixed(double d)
|
|||
{
|
||||
return fixedpoint<FractionalBits, IntegralType>(d);
|
||||
}
|
||||
/// \complexity One scale by `2^{FractionalBits}` (`ldexp`) and `nearbyint` under the current rounding mode. `Θ(1)`.
|
||||
/// @note This is the real value, same as `fixedpoint(double)`. It is not `from_raw`.
|
||||
/// @see grotto::fixedpoint
|
||||
|
||||
template <typename FixedType>
|
||||
HEDLEY_ALWAYS_INLINE
|
||||
|
|
@ -857,10 +895,12 @@ static constexpr auto make_fixed(double d)
|
|||
/// @brief Creates a fixed-point number from a double with bounds checking.
|
||||
/// @tparam FractionalBits number of fractional bits
|
||||
/// @tparam IntegralType underlying integral type
|
||||
/// @param d the `d`
|
||||
/// @param d real value
|
||||
/// @return Creates a fixed-point number from a double with bounds checking
|
||||
/// @throws std::range_error if the input double is outside the representable
|
||||
/// range of the fixed-point number.
|
||||
/// \complexity Two comparisons against `numeric_limits`, then one `make_fixed`. `Θ(1)`.
|
||||
/// @see grotto::make_fixed
|
||||
template <unsigned FractionalBits,
|
||||
typename IntegralType = GROTTO_FIXED_DEFAULT_INTEGRAL_REPRESENTATION>
|
||||
HEDLEY_ALWAYS_INLINE
|
||||
|
|
@ -881,6 +921,8 @@ static auto make_fixed_safe(double d)
|
|||
|
||||
return make_fixed<FractionalBits, IntegralType>(d);
|
||||
}
|
||||
/// \complexity One left or right shift of the raw word by the difference of the fractional widths. No rounding step. `Θ(1)`.
|
||||
/// @see grotto::fixedpoint
|
||||
|
||||
template <unsigned ToFractionalBits,
|
||||
unsigned FromFractionalBits,
|
||||
|
|
@ -906,6 +948,8 @@ static constexpr auto precision_of(fixedpoint<FractionalBits, IntegralType>) noe
|
|||
{
|
||||
return FractionalBits;
|
||||
}
|
||||
/// \complexity Adds one to the raw word (one ULP). `Θ(1)`.
|
||||
/// @see grotto::fixedpoint
|
||||
|
||||
template <unsigned FractionalBits,
|
||||
typename IntegralType>
|
||||
|
|
@ -916,6 +960,8 @@ constexpr auto nextafter(fixedpoint<FractionalBits, IntegralType> f) noexcept
|
|||
{
|
||||
return make_fixed_from_integral_type<FractionalBits, IntegralType>(f.integral_representation()+1);
|
||||
}
|
||||
/// \complexity Subtracts one from the raw word (one ULP). `Θ(1)`.
|
||||
/// @see grotto::fixedpoint
|
||||
|
||||
template <unsigned FractionalBits,
|
||||
typename IntegralType>
|
||||
|
|
@ -926,6 +972,8 @@ constexpr auto nextbefore(fixedpoint<FractionalBits, IntegralType> f) noexcept
|
|||
{
|
||||
return make_fixed_from_integral_type<FractionalBits, IntegralType>(f.integral_representation()-1);
|
||||
}
|
||||
/// \complexity Two's-complement absolute value of the raw word via `raw_neg` when the backend is signed. `Θ(1)`.
|
||||
/// @see grotto::fixedpoint
|
||||
|
||||
template <unsigned FractionalBits,
|
||||
typename IntegralType>
|
||||
|
|
@ -946,6 +994,8 @@ constexpr auto fabs(fixedpoint<FractionalBits, IntegralType> v) noexcept
|
|||
}
|
||||
return v;
|
||||
}
|
||||
/// \complexity `raw_fmod` on the backend words: remainder with the sign of the dividend. `Θ(1)` word operations. A zero modulus returns 0.
|
||||
/// @see grotto::fixedpoint
|
||||
|
||||
template <unsigned FractionalBits,
|
||||
typename IntegralType>
|
||||
|
|
@ -959,6 +1009,8 @@ constexpr auto fmod(fixedpoint<FractionalBits, IntegralType> v,
|
|||
detail::raw_fmod(v.integral_representation(),
|
||||
modulus.integral_representation()));
|
||||
}
|
||||
/// \complexity `raw_fmod` on the backend words: remainder with the sign of the dividend. `Θ(1)` word operations. A zero modulus returns 0.
|
||||
/// @see grotto::fixedpoint
|
||||
|
||||
template <unsigned FractionalBits,
|
||||
typename IntegralType>
|
||||
|
|
@ -970,6 +1022,8 @@ constexpr auto fmod(fixedpoint<FractionalBits, IntegralType> v,
|
|||
{
|
||||
return fmod(v, make_fixed<FractionalBits, IntegralType>(modulus));
|
||||
}
|
||||
/// \complexity `raw_fmod` on the backend words: remainder with the sign of the dividend. `Θ(1)` word operations. A zero modulus returns 0.
|
||||
/// @see grotto::fixedpoint
|
||||
|
||||
template <unsigned FractionalBits,
|
||||
typename IntegralType,
|
||||
|
|
@ -996,6 +1050,12 @@ enum fixed_cast_policy
|
|||
use_arg_sum //< for multiplies only
|
||||
};
|
||||
|
||||
/// @brief Apply `BinaryOperator` after casting both fixed-point arguments to one fractional width.
|
||||
/// @tparam BinaryOperator operation applied after the cast
|
||||
/// @tparam Mode which operand's fractional width wins (`use_max_arg` by default)
|
||||
/// @see grotto::precision_cast
|
||||
/// @see grotto::multiplies
|
||||
/// \complexity One `precision_cast` (a raw shift) when the fractional widths differ, then one call of `BinaryOperator`. `Θ(1)`.
|
||||
template <typename BinaryOperator,
|
||||
fixed_cast_policy Mode = use_max_arg>
|
||||
struct binary_operator_precast_wrapper
|
||||
|
|
@ -1042,6 +1102,12 @@ struct binary_operator_precast_wrapper
|
|||
}
|
||||
};
|
||||
|
||||
/// @brief Fixed-point product that chooses how many fractional bits to keep.
|
||||
/// @tparam Mode which operand's fractional width the product uses; `use_arg_sum` keeps both
|
||||
/// @see grotto::fixed_mul
|
||||
/// @see grotto::fixedpoint
|
||||
/// @note The raw product is shifted so the binary point matches `Mode`. That is a rescale. Leaf multiply does not do this.
|
||||
/// \complexity One backend multiply (64-bit or 128-bit branch) and one shift. `Θ(1)` time and extra space.
|
||||
template <fixed_cast_policy Mode = use_arg_sum>
|
||||
struct multiplies
|
||||
{
|
||||
|
|
@ -1123,6 +1189,12 @@ struct multiplies
|
|||
}
|
||||
};
|
||||
|
||||
/// @name Comparisons with double on the left
|
||||
/// @brief `double` compared with `fixedpoint` by reversing the member operator.
|
||||
/// \complexity One call to the matching member comparison. `Θ(1)`.
|
||||
/// @see grotto::fixedpoint
|
||||
/// @{
|
||||
|
||||
template <unsigned FractionalBits,
|
||||
typename IntegralType>
|
||||
HEDLEY_ALWAYS_INLINE
|
||||
|
|
@ -1173,6 +1245,13 @@ constexpr bool operator>=(double lhs, fixedpoint<FractionalBits, IntegralType> r
|
|||
return (rhs <= lhs);
|
||||
}
|
||||
|
||||
/// @}
|
||||
|
||||
/// @brief Horner polynomial in `fixedpoint` coefficients, constant term at index 0.
|
||||
/// @tparam FixedPointType coefficient type
|
||||
/// @tparam Degree number of coefficients (degree is one less when `Degree > 0`)
|
||||
/// @see grotto::multiplies
|
||||
/// \complexity `operator()` walks the coefficients once: `Θ(Degree)` fixed-point multiplies and adds. Extra space `Θ(1)`.
|
||||
template <typename FixedPointType,
|
||||
std::size_t Degree>
|
||||
struct fixedpoint_polynomial : public std::array<FixedPointType, Degree>
|
||||
|
|
@ -1203,6 +1282,20 @@ static constexpr auto evaluate(const fixedpoint_polynomial<FixedPointType, Degre
|
|||
namespace fixedpoint_literals
|
||||
{
|
||||
|
||||
/// @name Fixed-point literals
|
||||
/// @brief `_fixed0` through `_fixed64` in `grotto::fixedpoint_literals`.
|
||||
///
|
||||
/// `1.5_fixed16` is `fixedpoint<16>` holding the value 1.5, via
|
||||
/// `make_fixed<16>`. It is not a raw word. The DPF depth of the
|
||||
/// result is the backend width (64 bits for the default), not `N`.
|
||||
/// @param val the `long double` literal
|
||||
/// @return `fixedpoint<N>` for the suffix `_fixedN`
|
||||
/// @see grotto::make_fixed
|
||||
/// @see grotto::fixedpoint
|
||||
/// \complexity Each literal is one call to `make_fixed` (scale by
|
||||
/// `2^N` and round). `Θ(1)` time and extra space. No protocol.
|
||||
/// @{
|
||||
|
||||
constexpr auto operator "" _fixed0(long double val)
|
||||
{
|
||||
return grotto::make_fixed<0>(val);
|
||||
|
|
@ -1497,6 +1590,8 @@ constexpr auto operator "" _fixed64(long double val)
|
|||
return grotto::make_fixed<64>(val);
|
||||
}
|
||||
|
||||
/// @}
|
||||
|
||||
} // namespace grotto::fixedpoint_literals
|
||||
|
||||
} // namespace grotto
|
||||
|
|
@ -1597,6 +1692,9 @@ struct make_from_integral_value<grotto::fixedpoint<FractionalBits, IntegralType>
|
|||
static_cast<IntegralType>(val));
|
||||
}
|
||||
};
|
||||
/// \complexity One XOR of `msb_of_v<IntegralType>` into the raw word when `uses_signed_msb_v` is true. `Θ(1)`.
|
||||
/// @note Signed backends flip that MSB on the way into a DPF input. Unsigned backends do not.
|
||||
/// @see grotto::fixedpoint
|
||||
|
||||
template <unsigned FractionalBits,
|
||||
typename IntegralType>
|
||||
|
|
@ -1623,6 +1721,11 @@ struct flip_msb_for_input<grotto::fixedpoint<FractionalBits, IntegralType>>
|
|||
|
||||
namespace dpf::leaf_arithmetic
|
||||
{
|
||||
/// \complexity One SIMD 128-bit add of the raw backend words (`integral_representation()` for multiply).
|
||||
/// `Θ(1)` time and extra space.
|
||||
/// @note Does not shift by `FractionalBits`. The binary point of the leaf word stays where it was.
|
||||
/// @see grotto::fixedpoint
|
||||
/// @see grotto::fixed_mul
|
||||
|
||||
HEDLEY_PRAGMA(GCC diagnostic push)
|
||||
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
|
||||
|
|
@ -1634,6 +1737,11 @@ struct add_t<grotto::fixedpoint<FractionalBits, IntegralType>, simde__m128i>
|
|||
return add_t<IntegralType, simde__m128i>{}(a, b);
|
||||
}
|
||||
};
|
||||
/// \complexity One SIMD 256-bit add of the raw backend words (`integral_representation()` for multiply).
|
||||
/// `Θ(1)` time and extra space.
|
||||
/// @note Does not shift by `FractionalBits`. The binary point of the leaf word stays where it was.
|
||||
/// @see grotto::fixedpoint
|
||||
/// @see grotto::fixed_mul
|
||||
|
||||
template <unsigned FractionalBits, typename IntegralType>
|
||||
struct add_t<grotto::fixedpoint<FractionalBits, IntegralType>, simde__m256i>
|
||||
|
|
@ -1643,6 +1751,11 @@ struct add_t<grotto::fixedpoint<FractionalBits, IntegralType>, simde__m256i>
|
|||
return add_t<IntegralType, simde__m256i>{}(a, b);
|
||||
}
|
||||
};
|
||||
/// \complexity One SIMD 128-bit subtract of the raw backend words (`integral_representation()` for multiply).
|
||||
/// `Θ(1)` time and extra space.
|
||||
/// @note Does not shift by `FractionalBits`. The binary point of the leaf word stays where it was.
|
||||
/// @see grotto::fixedpoint
|
||||
/// @see grotto::fixed_mul
|
||||
|
||||
template <unsigned FractionalBits, typename IntegralType>
|
||||
struct subtract_t<grotto::fixedpoint<FractionalBits, IntegralType>, simde__m128i>
|
||||
|
|
@ -1652,6 +1765,11 @@ struct subtract_t<grotto::fixedpoint<FractionalBits, IntegralType>, simde__m128i
|
|||
return subtract_t<IntegralType, simde__m128i>{}(a, b);
|
||||
}
|
||||
};
|
||||
/// \complexity One SIMD 256-bit subtract of the raw backend words (`integral_representation()` for multiply).
|
||||
/// `Θ(1)` time and extra space.
|
||||
/// @note Does not shift by `FractionalBits`. The binary point of the leaf word stays where it was.
|
||||
/// @see grotto::fixedpoint
|
||||
/// @see grotto::fixed_mul
|
||||
|
||||
template <unsigned FractionalBits, typename IntegralType>
|
||||
struct subtract_t<grotto::fixedpoint<FractionalBits, IntegralType>, simde__m256i>
|
||||
|
|
@ -1661,6 +1779,11 @@ struct subtract_t<grotto::fixedpoint<FractionalBits, IntegralType>, simde__m256i
|
|||
return subtract_t<IntegralType, simde__m256i>{}(a, b);
|
||||
}
|
||||
};
|
||||
/// \complexity One SIMD 128-bit multiply of the raw backend words (`integral_representation()` for multiply).
|
||||
/// `Θ(1)` time and extra space.
|
||||
/// @note Does not shift by `FractionalBits`. The binary point of the leaf word stays where it was.
|
||||
/// @see grotto::fixedpoint
|
||||
/// @see grotto::fixed_mul
|
||||
|
||||
template <unsigned FractionalBits, typename IntegralType>
|
||||
struct multiply_t<grotto::fixedpoint<FractionalBits, IntegralType>, simde__m128i>
|
||||
|
|
@ -1671,6 +1794,11 @@ struct multiply_t<grotto::fixedpoint<FractionalBits, IntegralType>, simde__m128i
|
|||
return multiply_t<IntegralType, simde__m128i>{}(a, b.integral_representation());
|
||||
}
|
||||
};
|
||||
/// \complexity One SIMD 256-bit multiply of the raw backend words (`integral_representation()` for multiply).
|
||||
/// `Θ(1)` time and extra space.
|
||||
/// @note Does not shift by `FractionalBits`. The binary point of the leaf word stays where it was.
|
||||
/// @see grotto::fixedpoint
|
||||
/// @see grotto::fixed_mul
|
||||
|
||||
template <unsigned FractionalBits, typename IntegralType>
|
||||
struct multiply_t<grotto::fixedpoint<FractionalBits, IntegralType>, simde__m256i>
|
||||
|
|
|
|||
617
include/grotto/fixedpoint_beaver.hpp
Normal file
617
include/grotto/fixedpoint_beaver.hpp
Normal file
|
|
@ -0,0 +1,617 @@
|
|||
/// @file grotto/fixedpoint_beaver.hpp
|
||||
/// @brief Additive-share evaluation of `fixed_mul`.
|
||||
/// @details One Beaver triple in `Z/2^{multiply_bits}Z` is the product. Every
|
||||
/// other step is linear, or a masked comparison that lifts a narrower
|
||||
/// share or drops low bits:
|
||||
///
|
||||
/// - bits above `multiply_bits` are discarded locally;
|
||||
/// - a narrower operand (and a product narrower than `modulus_bits`)
|
||||
/// is lifted by `eta + r - w·2^{src}`, with `w` the carry of the
|
||||
/// secret mask, and a signed lift then replicates the sign;
|
||||
/// - `align_shift > 0` is a truncate-and-reduce of that modulus;
|
||||
/// - `align_shift < 0` is a local left shift.
|
||||
///
|
||||
/// The opened window matches `fixed_mul`, including the final sign
|
||||
/// extension into the storage word. Each lift extends by at most 64
|
||||
/// bits and starts from at most 64 bits, so the carry bit stays a
|
||||
/// `uint64_t` comparison and scaling it is homomorphic in the
|
||||
/// destination ring. A right shift discards at most 64 bits. The
|
||||
/// modulus and the multiply ring are at most 128 bits.
|
||||
///
|
||||
/// `prep` is a dealer record (both key halves, both mask shares),
|
||||
/// same as `carry_key_pair`. It is independent of the data and is
|
||||
/// reused. `sample_fixed_mul_beaver_triple` is one product and is
|
||||
/// fresh each time. `eval_fixed_mul_beaver` is one party: `exchange`
|
||||
/// sends one `uint64_t` and returns the peer's word, in the same
|
||||
/// order on both sides.
|
||||
/// @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_FIXEDPOINT_BEAVER_HPP__
|
||||
#define LIBDPF_INCLUDE_GROTTO_FIXEDPOINT_BEAVER_HPP__
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include "hedley/hedley.h"
|
||||
|
||||
#include "dpf.hpp"
|
||||
#include "grotto/fixedpoint_mul.hpp"
|
||||
|
||||
namespace grotto
|
||||
{
|
||||
|
||||
namespace fixed_mul_beaver_detail
|
||||
{
|
||||
|
||||
using u128 = simde_uint128;
|
||||
|
||||
HEDLEY_NO_THROW
|
||||
constexpr u128 bit_mask(unsigned bits) noexcept
|
||||
{
|
||||
if (bits == 0u)
|
||||
return 0;
|
||||
if (bits >= 128u)
|
||||
return ~u128{0};
|
||||
return (u128{1} << bits) - 1;
|
||||
}
|
||||
|
||||
HEDLEY_NO_THROW
|
||||
constexpr unsigned u64_limbs(unsigned bits) noexcept
|
||||
{
|
||||
return bits > 64u ? 2u : 1u;
|
||||
}
|
||||
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline u128 draw_mod(unsigned bits)
|
||||
{
|
||||
if (bits == 0u)
|
||||
return 0;
|
||||
if (bits <= 64u)
|
||||
{
|
||||
const std::uint64_t m = static_cast<std::uint64_t>(bit_mask(bits));
|
||||
return dpf::uniform_sample<std::uint64_t>() & m;
|
||||
}
|
||||
return dpf::uniform_sample<u128>() & bit_mask(bits);
|
||||
}
|
||||
|
||||
/// @brief Low 128 bits of `(uint64)m * factor`, then reduced mod `2^dest`.
|
||||
HEDLEY_NO_THROW
|
||||
inline u128 mul_u64(std::uint64_t m, u128 factor, unsigned dest) noexcept
|
||||
{
|
||||
const std::uint64_t f0 = static_cast<std::uint64_t>(factor);
|
||||
const std::uint64_t f1 = static_cast<std::uint64_t>(factor >> 64);
|
||||
const u128 p0 = u128{m} * f0;
|
||||
const u128 p1 = u128{m} * f1;
|
||||
return (p0 + (p1 << 64)) & bit_mask(dest);
|
||||
}
|
||||
|
||||
HEDLEY_NO_THROW
|
||||
inline u128 mul_mod(u128 a, u128 b, unsigned bits) noexcept
|
||||
{
|
||||
a &= bit_mask(bits);
|
||||
b &= bit_mask(bits);
|
||||
if (bits <= 64u)
|
||||
{
|
||||
return (u128{static_cast<std::uint64_t>(a)}
|
||||
* static_cast<std::uint64_t>(b)) & bit_mask(bits);
|
||||
}
|
||||
const std::uint64_t a0 = static_cast<std::uint64_t>(a);
|
||||
const std::uint64_t a1 = static_cast<std::uint64_t>(a >> 64);
|
||||
const std::uint64_t b0 = static_cast<std::uint64_t>(b);
|
||||
const std::uint64_t b1 = static_cast<std::uint64_t>(b >> 64);
|
||||
const u128 p00 = u128{a0} * b0;
|
||||
const u128 mid = (p00 >> 64)
|
||||
+ static_cast<std::uint64_t>(u128{a0} * b1)
|
||||
+ static_cast<std::uint64_t>(u128{a1} * b0);
|
||||
return ((u128)static_cast<std::uint64_t>(p00) | (mid << 64)) & bit_mask(bits);
|
||||
}
|
||||
|
||||
HEDLEY_NO_THROW
|
||||
inline u128 add_mod(u128 a, u128 b, unsigned bits) noexcept
|
||||
{
|
||||
return (a + b) & bit_mask(bits);
|
||||
}
|
||||
|
||||
HEDLEY_NO_THROW
|
||||
inline u128 sub_mod(u128 a, u128 b, unsigned bits) noexcept
|
||||
{
|
||||
return (a - b) & bit_mask(bits);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
HEDLEY_NO_THROW
|
||||
u128 share_bits(const T & value) noexcept
|
||||
{
|
||||
std::uint64_t raw[4] = {};
|
||||
detail::store_raw_limbs(value, raw);
|
||||
return u128{raw[0]} | (u128{raw[1]} << 64);
|
||||
}
|
||||
|
||||
using cmp_pair = decltype(dpf::make_dpf(std::uint64_t{0},
|
||||
dpf::lt(std::uint64_t{1})));
|
||||
|
||||
struct bit_key
|
||||
{
|
||||
cmp_pair keys;
|
||||
};
|
||||
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline bit_key make_bit_key(std::uint64_t alpha)
|
||||
{
|
||||
return bit_key{dpf::make_dpf(alpha, dpf::lt(std::uint64_t{1}))};
|
||||
}
|
||||
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline std::uint64_t eval_bit(const bit_key & key, std::size_t party,
|
||||
std::uint64_t query)
|
||||
{
|
||||
if (party == 0u)
|
||||
return dpf::eval_point(dpf::cmp, key.keys.first, query).raw();
|
||||
return dpf::eval_point(dpf::cmp, key.keys.second, query).raw();
|
||||
}
|
||||
|
||||
/// @brief Public sum of the two parties' words, mod `2^bits`.
|
||||
template <typename Exchange>
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
u128 open_sum(u128 mine, unsigned bits, Exchange & exchange)
|
||||
{
|
||||
const std::uint64_t low = exchange(static_cast<std::uint64_t>(mine));
|
||||
u128 peer = low;
|
||||
if (bits > 64u)
|
||||
{
|
||||
const std::uint64_t hi = exchange(static_cast<std::uint64_t>(mine >> 64));
|
||||
peer |= u128{hi} << 64;
|
||||
}
|
||||
return (mine + peer) & bit_mask(bits);
|
||||
}
|
||||
|
||||
struct lift_keys
|
||||
{
|
||||
bool live = false;
|
||||
bool sign = false;
|
||||
unsigned src = 0;
|
||||
unsigned dest = 0;
|
||||
/// @brief Secret mask mod `2^src`. Shares sum to `r` in `Z/2^128`.
|
||||
u128 r = 0;
|
||||
u128 r_share[2]{};
|
||||
/// @brief `lt` at `r`. Eval at `T-1` is a share of `1{r >= T}`.
|
||||
std::optional<bit_key> wrap{};
|
||||
};
|
||||
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline lift_keys make_lift(unsigned src, unsigned dest, bool sign)
|
||||
{
|
||||
lift_keys k;
|
||||
k.live = true;
|
||||
k.sign = sign;
|
||||
k.src = src;
|
||||
k.dest = dest;
|
||||
k.r = draw_mod(src);
|
||||
k.r_share[0] = dpf::uniform_sample<u128>();
|
||||
k.r_share[1] = k.r - k.r_share[0];
|
||||
k.wrap = make_bit_key(static_cast<std::uint64_t>(k.r));
|
||||
return k;
|
||||
}
|
||||
|
||||
struct shift_keys
|
||||
{
|
||||
bool live = false;
|
||||
unsigned n = 0;
|
||||
unsigned s = 0;
|
||||
u128 rin = 0;
|
||||
u128 rin_share[2]{};
|
||||
u128 rout_share[2]{};
|
||||
std::optional<bit_key> low{};
|
||||
};
|
||||
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline shift_keys make_shift(unsigned n, unsigned s)
|
||||
{
|
||||
shift_keys k;
|
||||
k.live = true;
|
||||
k.n = n;
|
||||
k.s = s;
|
||||
k.rin = draw_mod(n);
|
||||
k.rin_share[0] = draw_mod(n);
|
||||
k.rin_share[1] = (k.rin - k.rin_share[0]) & bit_mask(n);
|
||||
const u128 neg = (u128{0} - k.rin) & bit_mask(n);
|
||||
const std::uint64_t alpha = static_cast<std::uint64_t>(neg & bit_mask(s));
|
||||
k.low = make_bit_key(alpha);
|
||||
const unsigned out = n - s;
|
||||
const u128 y_hi = neg >> s;
|
||||
k.rout_share[0] = draw_mod(out);
|
||||
k.rout_share[1] = (y_hi - k.rout_share[0]) & bit_mask(out);
|
||||
return k;
|
||||
}
|
||||
|
||||
/// @brief Share of `1{r >= T}` for `r` in `[0, 2^src)`. `T == 0` is the public 1.
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline std::uint64_t ge_mask(const lift_keys & keys, std::size_t party,
|
||||
u128 threshold)
|
||||
{
|
||||
if (threshold == 0)
|
||||
return party == 1u ? std::uint64_t{1} : 0u;
|
||||
if (threshold > bit_mask(keys.src))
|
||||
return 0u;
|
||||
return eval_bit(*keys.wrap, party,
|
||||
static_cast<std::uint64_t>(threshold) - 1u);
|
||||
}
|
||||
|
||||
template <typename Exchange>
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
u128 apply_lift(u128 share, const lift_keys & keys, std::size_t party,
|
||||
Exchange & exchange)
|
||||
{
|
||||
const std::uint64_t src_mask = static_cast<std::uint64_t>(bit_mask(keys.src));
|
||||
const std::uint64_t xs = static_cast<std::uint64_t>(share) & src_mask;
|
||||
const std::uint64_t rs = static_cast<std::uint64_t>(keys.r_share[party]) & src_mask;
|
||||
const std::uint64_t delta = (xs - rs) & src_mask;
|
||||
const std::uint64_t peer = exchange(delta);
|
||||
const std::uint64_t eta = (delta + peer) & src_mask;
|
||||
// `1{r >= 2^src - eta}` is the carry `r + eta >= 2^src`.
|
||||
const u128 mod = u128{1} << keys.src;
|
||||
const std::uint64_t w = ge_mask(keys, party, mod - eta);
|
||||
u128 y = keys.r_share[party];
|
||||
y -= mul_u64(w, mod, keys.dest);
|
||||
if (party == 0u)
|
||||
y += eta;
|
||||
if (keys.sign)
|
||||
{
|
||||
const u128 half = u128{1} << (keys.src - 1u);
|
||||
std::uint64_t msb = 0;
|
||||
if (u128{eta} < half)
|
||||
msb = ge_mask(keys, party, half - eta) - w;
|
||||
else
|
||||
msb = (party == 1u ? std::uint64_t{1} : 0u) - w
|
||||
+ ge_mask(keys, party, mod + half - eta);
|
||||
const u128 high = bit_mask(keys.dest) & ~bit_mask(keys.src);
|
||||
y += mul_u64(msb, high, keys.dest);
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
template <typename Exchange>
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
u128 apply_shift(u128 share, const shift_keys & keys, std::size_t party,
|
||||
Exchange & exchange)
|
||||
{
|
||||
const u128 opened = open_sum(share + keys.rin_share[party], keys.n, exchange);
|
||||
const u128 xs = opened & bit_mask(keys.s);
|
||||
const u128 query128 = (u128{1} << keys.s) - xs - 1;
|
||||
const std::uint64_t t = eval_bit(*keys.low, party,
|
||||
static_cast<std::uint64_t>(query128));
|
||||
// `t` sums to the carry in `Z/2^64`. The output modulus is at most 64
|
||||
// bits for every window this header accepts, so that extra multiple of
|
||||
// `2^64` lands outside the modulus.
|
||||
u128 acc = keys.rout_share[party] + t;
|
||||
if (party == 1u)
|
||||
acc += opened >> keys.s;
|
||||
return acc;
|
||||
}
|
||||
|
||||
} // namespace fixed_mul_beaver_detail
|
||||
|
||||
/// @brief Which steps of one `fixed_mul` window are interactive.
|
||||
/// @tparam IntegerBits integer bits kept in the product, including the sign
|
||||
/// @tparam FractionalBits fraction bits kept in the product
|
||||
/// @tparam LhsFractionalBits fractional bits of the left operand
|
||||
/// @tparam LhsIntegral integral type of the left operand
|
||||
/// @tparam RhsFractionalBits fractional bits of the right operand
|
||||
/// @tparam RhsIntegral integral type of the right operand
|
||||
template <unsigned IntegerBits,
|
||||
unsigned FractionalBits,
|
||||
unsigned LhsFractionalBits,
|
||||
typename LhsIntegral,
|
||||
unsigned RhsFractionalBits,
|
||||
typename RhsIntegral>
|
||||
struct fixed_mul_beaver_shape
|
||||
{
|
||||
using plan = fixed_mul_plan<IntegerBits, FractionalBits,
|
||||
LhsFractionalBits, LhsIntegral, RhsFractionalBits, RhsIntegral>;
|
||||
using result_integral = typename plan::integral_type;
|
||||
|
||||
static constexpr unsigned storage_bits =
|
||||
static_cast<unsigned>(dpf::utils::bitlength_of_v<result_integral>);
|
||||
static constexpr bool active = plan::modulus_bits > 0u
|
||||
&& plan::multiply_bits > 0u;
|
||||
static constexpr bool lhs_lift = active
|
||||
&& plan::multiply_bits > plan::lhs_width;
|
||||
static constexpr bool rhs_lift = active
|
||||
&& plan::multiply_bits > plan::rhs_width;
|
||||
static constexpr bool product_lift = active
|
||||
&& plan::modulus_bits > plan::multiply_bits;
|
||||
static constexpr bool shift_right = active && plan::align_shift > 0;
|
||||
static constexpr bool shift_left = active && plan::align_shift < 0;
|
||||
static constexpr bool result_lift = active
|
||||
&& storage_bits > plan::out_bits;
|
||||
|
||||
static constexpr bool lift_ok(bool lift, unsigned src, unsigned dest) noexcept
|
||||
{
|
||||
if (!lift)
|
||||
return true;
|
||||
return src >= 1u && src <= 64u && dest > src && dest <= 128u
|
||||
&& (dest - src) <= 64u;
|
||||
}
|
||||
|
||||
static constexpr bool fits = !active
|
||||
|| (plan::multiply_bits <= 128u
|
||||
&& plan::modulus_bits <= 128u
|
||||
&& storage_bits <= 128u
|
||||
&& lift_ok(lhs_lift, plan::lhs_width, plan::multiply_bits)
|
||||
&& lift_ok(rhs_lift, plan::rhs_width, plan::multiply_bits)
|
||||
&& lift_ok(product_lift, plan::multiply_bits, plan::modulus_bits)
|
||||
&& lift_ok(result_lift, plan::out_bits, storage_bits)
|
||||
&& (!shift_right || (static_cast<unsigned>(plan::align_shift) <= 64u
|
||||
&& plan::out_bits <= 64u
|
||||
&& plan::modulus_bits <= 128u
|
||||
&& plan::modulus_bits > static_cast<unsigned>(plan::align_shift))));
|
||||
|
||||
/// @brief `uint64_t` words exchanged by one product. Zero when the window is empty.
|
||||
static constexpr unsigned messages = !active ? 0u
|
||||
: (lhs_lift ? 1u : 0u)
|
||||
+ (rhs_lift ? 1u : 0u)
|
||||
+ 2u * fixed_mul_beaver_detail::u64_limbs(plan::multiply_bits)
|
||||
+ (product_lift ? 1u : 0u)
|
||||
+ (shift_right
|
||||
? fixed_mul_beaver_detail::u64_limbs(plan::modulus_bits)
|
||||
: 0u)
|
||||
+ (result_lift ? 1u : 0u);
|
||||
};
|
||||
|
||||
/// @brief One Beaver triple in `Z/2^{multiply_bits}Z`.
|
||||
struct fixed_mul_beaver_triple
|
||||
{
|
||||
unsigned multiply_bits = 0;
|
||||
fixed_mul_beaver_detail::u128 a[2]{};
|
||||
fixed_mul_beaver_detail::u128 b[2]{};
|
||||
fixed_mul_beaver_detail::u128 ab[2]{};
|
||||
};
|
||||
|
||||
/// @brief Dealer masks and comparison keys for one window. Reused across products.
|
||||
template <unsigned IntegerBits,
|
||||
unsigned FractionalBits,
|
||||
unsigned LhsFractionalBits,
|
||||
typename LhsIntegral,
|
||||
unsigned RhsFractionalBits,
|
||||
typename RhsIntegral>
|
||||
struct fixed_mul_beaver_prep
|
||||
{
|
||||
using shape = fixed_mul_beaver_shape<IntegerBits, FractionalBits,
|
||||
LhsFractionalBits, LhsIntegral, RhsFractionalBits, RhsIntegral>;
|
||||
|
||||
fixed_mul_beaver_detail::lift_keys lhs{};
|
||||
fixed_mul_beaver_detail::lift_keys rhs{};
|
||||
fixed_mul_beaver_detail::lift_keys product{};
|
||||
fixed_mul_beaver_detail::shift_keys shift{};
|
||||
fixed_mul_beaver_detail::lift_keys result{};
|
||||
};
|
||||
|
||||
/// @brief Pack a `mod2k` beaver pair into the fixed-point triple.
|
||||
inline fixed_mul_beaver_triple pack_mod2k_beaver(
|
||||
unsigned multiply_bits, const dpf::beavers::beaver2<dpf::mod2k> & triple)
|
||||
{
|
||||
fixed_mul_beaver_triple t;
|
||||
t.multiply_bits = multiply_bits;
|
||||
t.a[0] = triple.a.p0.raw;
|
||||
t.a[1] = triple.a.p1.raw;
|
||||
t.b[0] = triple.b.p0.raw;
|
||||
t.b[1] = triple.b.p1.raw;
|
||||
t.ab[0] = triple.ab.p0.raw;
|
||||
t.ab[1] = triple.ab.p1.raw;
|
||||
return t;
|
||||
}
|
||||
|
||||
/// @brief Sample one product triple from `sample_beaver2` in `Z/2^w Z`.
|
||||
/// @param multiply_bits ring width, `0` or `1 .. 128`
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline fixed_mul_beaver_triple sample_fixed_mul_beaver_triple(unsigned multiply_bits)
|
||||
{
|
||||
if (multiply_bits == 0u)
|
||||
return {};
|
||||
if (multiply_bits > 128u)
|
||||
throw std::invalid_argument("fixed_mul beaver triple exceeds 128 bits");
|
||||
dpf::beavers::mod2k_width_scope width(multiply_bits);
|
||||
return pack_mod2k_beaver(multiply_bits,
|
||||
dpf::beavers::sample_beaver2<dpf::mod2k>());
|
||||
}
|
||||
|
||||
/// @brief Same triple from copy `index` of a `mod2k` oracle.
|
||||
template <typename PRG = dpf::prg::aes128>
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
inline fixed_mul_beaver_triple sample_fixed_mul_beaver_triple(unsigned multiply_bits,
|
||||
const dpf::beavers::oracle<dpf::mod2k, PRG> & src, std::uint64_t index = 0)
|
||||
{
|
||||
if (multiply_bits == 0u)
|
||||
return {};
|
||||
if (multiply_bits > 128u)
|
||||
throw std::invalid_argument("fixed_mul beaver triple exceeds 128 bits");
|
||||
dpf::beavers::mod2k_width_scope width(multiply_bits);
|
||||
return pack_mod2k_beaver(multiply_bits,
|
||||
dpf::beavers::sample_beaver2(src, index));
|
||||
}
|
||||
|
||||
/// @brief Build the reusable comparisons for this window.
|
||||
/// \complexity One `dpf::make_dpf` per live lift (operand, product, result)
|
||||
/// and one for a right shift. A signed lift reuses that key at a second
|
||||
/// query. Each comparison is a `uint64_t` domain. No messages.
|
||||
/// \rounds No party interaction.
|
||||
/// \communication None.
|
||||
/// \preprocessing Those comparison keys and the mask shares.
|
||||
template <unsigned IntegerBits,
|
||||
unsigned FractionalBits,
|
||||
unsigned LhsFractionalBits,
|
||||
typename LhsIntegral,
|
||||
unsigned RhsFractionalBits,
|
||||
typename RhsIntegral>
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
fixed_mul_beaver_prep<IntegerBits, FractionalBits, LhsFractionalBits, LhsIntegral,
|
||||
RhsFractionalBits, RhsIntegral>
|
||||
make_fixed_mul_beaver_prep()
|
||||
{
|
||||
using shape = fixed_mul_beaver_shape<IntegerBits, FractionalBits,
|
||||
LhsFractionalBits, LhsIntegral, RhsFractionalBits, RhsIntegral>;
|
||||
using prep = fixed_mul_beaver_prep<IntegerBits, FractionalBits,
|
||||
LhsFractionalBits, LhsIntegral, RhsFractionalBits, RhsIntegral>;
|
||||
static_assert(shape::fits,
|
||||
"fixed_mul beaver: multiply and modulus must be at most 128 bits, "
|
||||
"each lift must start from at most 64 bits and extend by at most 64, "
|
||||
"and a right shift must discard at most 64 bits");
|
||||
prep out;
|
||||
if constexpr (!shape::active)
|
||||
return out;
|
||||
using plan = typename shape::plan;
|
||||
if constexpr (shape::lhs_lift)
|
||||
{
|
||||
out.lhs = fixed_mul_beaver_detail::make_lift(plan::lhs_width,
|
||||
plan::multiply_bits, plan::lhs_signed);
|
||||
}
|
||||
if constexpr (shape::rhs_lift)
|
||||
{
|
||||
out.rhs = fixed_mul_beaver_detail::make_lift(plan::rhs_width,
|
||||
plan::multiply_bits, plan::rhs_signed);
|
||||
}
|
||||
if constexpr (shape::product_lift)
|
||||
{
|
||||
out.product = fixed_mul_beaver_detail::make_lift(plan::multiply_bits,
|
||||
plan::modulus_bits, plan::operands_signed);
|
||||
}
|
||||
if constexpr (shape::shift_right)
|
||||
{
|
||||
out.shift = fixed_mul_beaver_detail::make_shift(plan::modulus_bits,
|
||||
static_cast<unsigned>(plan::align_shift));
|
||||
}
|
||||
if constexpr (shape::result_lift)
|
||||
{
|
||||
out.result = fixed_mul_beaver_detail::make_lift(plan::out_bits,
|
||||
shape::storage_bits, plan::result_is_signed);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/// @brief One party's share of `fixed_mul<IntegerBits, FractionalBits>(lhs, rhs)`.
|
||||
/// @details `lhs_share` and `rhs_share` are additive shares of each operand's
|
||||
/// raw integral word, in that word's own ring. The return value is
|
||||
/// this party's share of the product's raw integral word. `exchange(mine)`
|
||||
/// returns the peer's matching `uint64_t`. Both parties must call it
|
||||
/// the same number of times (`fixed_mul_beaver_shape::messages`).
|
||||
/// @tparam Exchange callable `std::uint64_t(std::uint64_t)`
|
||||
/// @param prep reusable dealer material from `make_fixed_mul_beaver_prep`
|
||||
/// @param triple fresh Beaver triple in the multiply ring
|
||||
/// @param party `0` or `1`
|
||||
/// @param lhs_share this party's share of the left raw word
|
||||
/// @param rhs_share this party's share of the right raw word
|
||||
/// @param exchange peer exchange for one `uint64_t`
|
||||
/// @return this party's share of the product, as a fixed-point word
|
||||
/// \complexity The Beaver product is `O(1)` 128-bit arithmetic. Each live
|
||||
/// lift or shift is one to three `eval_point` calls on a `uint64_t` DCF.
|
||||
/// \rounds One round if the caller pipelines every `exchange`; the callback
|
||||
/// itself is one word at a time. `messages` words in total.
|
||||
/// \communication `fixed_mul_beaver_shape::messages` words of `uint64_t`.
|
||||
/// \preprocessing `prep` and one `triple`.
|
||||
template <unsigned IntegerBits,
|
||||
unsigned FractionalBits,
|
||||
unsigned LhsFractionalBits,
|
||||
typename LhsIntegral,
|
||||
unsigned RhsFractionalBits,
|
||||
typename RhsIntegral,
|
||||
typename Exchange>
|
||||
HEDLEY_WARN_UNUSED_RESULT
|
||||
auto eval_fixed_mul_beaver(
|
||||
const fixed_mul_beaver_prep<IntegerBits, FractionalBits, LhsFractionalBits,
|
||||
LhsIntegral, RhsFractionalBits, RhsIntegral> & prep,
|
||||
const fixed_mul_beaver_triple & triple,
|
||||
std::size_t party,
|
||||
LhsIntegral lhs_share,
|
||||
RhsIntegral rhs_share,
|
||||
Exchange && exchange)
|
||||
-> typename fixed_mul_plan<IntegerBits, FractionalBits, LhsFractionalBits,
|
||||
LhsIntegral, RhsFractionalBits, RhsIntegral>::result_type
|
||||
{
|
||||
using shape = fixed_mul_beaver_shape<IntegerBits, FractionalBits,
|
||||
LhsFractionalBits, LhsIntegral, RhsFractionalBits, RhsIntegral>;
|
||||
using plan = typename shape::plan;
|
||||
using integral = typename plan::integral_type;
|
||||
static_assert(shape::fits,
|
||||
"fixed_mul beaver: multiply and modulus must be at most 128 bits, "
|
||||
"each lift must start from at most 64 bits and extend by at most 64, "
|
||||
"and a right shift must discard at most 64 bits");
|
||||
if (party > 1u)
|
||||
throw std::invalid_argument("fixed_mul beaver party is 0 or 1");
|
||||
|
||||
using namespace fixed_mul_beaver_detail;
|
||||
const auto zero = make_fixed_from_integral_type<FractionalBits, integral>(
|
||||
integral{});
|
||||
if constexpr (!shape::active)
|
||||
{
|
||||
(void)prep;
|
||||
(void)triple;
|
||||
(void)lhs_share;
|
||||
(void)rhs_share;
|
||||
(void)exchange;
|
||||
return zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (triple.multiply_bits != plan::multiply_bits)
|
||||
throw std::invalid_argument("fixed_mul beaver triple width does not match the window");
|
||||
|
||||
auto reduce = [&](u128 share, unsigned width, const lift_keys & lift,
|
||||
bool do_lift) -> u128 {
|
||||
const unsigned kept = width < 128u ? width : 128u;
|
||||
u128 limb = share & bit_mask(kept);
|
||||
if (!do_lift)
|
||||
return limb & bit_mask(plan::multiply_bits);
|
||||
return apply_lift(limb, lift, party, exchange);
|
||||
};
|
||||
|
||||
const u128 left = reduce(share_bits(lhs_share), plan::lhs_width,
|
||||
prep.lhs, shape::lhs_lift);
|
||||
const u128 right = reduce(share_bits(rhs_share), plan::rhs_width,
|
||||
prep.rhs, shape::rhs_lift);
|
||||
|
||||
const unsigned m = plan::multiply_bits;
|
||||
const u128 d = open_sum(sub_mod(left, triple.a[party], m), m, exchange);
|
||||
const u128 e = open_sum(sub_mod(right, triple.b[party], m), m, exchange);
|
||||
u128 prod = add_mod(triple.ab[party],
|
||||
add_mod(mul_mod(d, triple.b[party], m),
|
||||
mul_mod(e, triple.a[party], m), m), m);
|
||||
if (party == 1u)
|
||||
prod = add_mod(prod, mul_mod(d, e, m), m);
|
||||
|
||||
u128 wide = prod;
|
||||
if constexpr (shape::product_lift)
|
||||
wide = apply_lift(wide, prep.product, party, exchange);
|
||||
|
||||
u128 window = wide;
|
||||
if constexpr (shape::shift_right)
|
||||
window = apply_shift(wide, prep.shift, party, exchange);
|
||||
else if constexpr (shape::shift_left)
|
||||
{
|
||||
constexpr unsigned k = static_cast<unsigned>(-plan::align_shift);
|
||||
window = ((wide & bit_mask(plan::modulus_bits)) << k)
|
||||
& bit_mask(plan::out_bits);
|
||||
}
|
||||
else
|
||||
window = wide & bit_mask(plan::out_bits);
|
||||
|
||||
if constexpr (shape::result_lift)
|
||||
window = apply_lift(window, prep.result, party, exchange);
|
||||
|
||||
window &= bit_mask(shape::storage_bits);
|
||||
std::uint64_t limbs[4] = {
|
||||
static_cast<std::uint64_t>(window),
|
||||
static_cast<std::uint64_t>(window >> 64),
|
||||
0u,
|
||||
0u};
|
||||
return make_fixed_from_integral_type<FractionalBits, integral>(
|
||||
detail::limbs_to_integral<integral>(limbs));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace grotto
|
||||
|
||||
#endif // LIBDPF_INCLUDE_GROTTO_FIXEDPOINT_BEAVER_HPP__
|
||||
|
|
@ -29,11 +29,13 @@ namespace grotto
|
|||
/// integer part. Both discards are the low or high residue modulo a power of
|
||||
/// two, so a negative value is floored onto the output ulp.
|
||||
///
|
||||
/// A Beaver triple replaces only the multiply in `Z/2^multiply_bits Z`.
|
||||
/// Reducing each operand into that ring is local when it is a truncation or a
|
||||
/// zero-extend. Sign-extending a narrower signed operand, and replicating the
|
||||
/// product sign when `modulus_bits > multiply_bits`, are plaintext steps the
|
||||
/// MPC protocol has to reproduce (they are not local on additive shares).
|
||||
/// A Beaver triple replaces only the multiply in `Z/2^multiply_bits Z`
|
||||
/// (`grotto::eval_fixed_mul_beaver`). Dropping bits above that ring is local.
|
||||
/// A narrower share is lifted by cancelling the carry out of the operand
|
||||
/// width; a signed lift also replicates the sign. The same correction
|
||||
/// replicates the product sign when `modulus_bits > multiply_bits` and
|
||||
/// supplies the right shift by `align_shift`. Each of those lifts extends
|
||||
/// by at most 64 bits, and the shift discards at most 64 bits.
|
||||
/// @tparam IntegerBits number of integer bits, including the sign
|
||||
/// @tparam FractionalBits number of fractional bits
|
||||
/// @tparam LhsFractionalBits lhs fractional bits
|
||||
|
|
@ -206,11 +208,11 @@ constexpr void store_raw_limbs(const T & value, std::uint64_t out[4]) noexcept
|
|||
/// @brief Low `dest_bits` of `value`, sign-extended when `value` is a narrower signed integer.
|
||||
/// @tparam T value type
|
||||
/// @param value the value to convert or store
|
||||
/// @param src_bits the `src_bits`
|
||||
/// @param is_signed the `is_signed`
|
||||
/// @param dest_bits the `dest_bits`
|
||||
/// @param src_bits width of `value` before extension
|
||||
/// @param is_signed whether `value` is a signed integer of `src_bits` bits
|
||||
/// @param dest_bits width of the destination word
|
||||
/// @param dest the destination
|
||||
/// @param nlimbs the `nlimbs`
|
||||
/// @param nlimbs number of 64-bit limbs kept in the product
|
||||
template <typename T>
|
||||
HEDLEY_NO_THROW
|
||||
constexpr void reduce_operand(const T & value, unsigned src_bits, bool is_signed,
|
||||
|
|
@ -239,7 +241,7 @@ constexpr void reduce_operand(const T & value, unsigned src_bits, bool is_signed
|
|||
/// @param out the output buffer
|
||||
/// @param lhs the left-hand operand
|
||||
/// @param rhs the right-hand operand
|
||||
/// @param nlimbs the `nlimbs`
|
||||
/// @param nlimbs number of 64-bit limbs kept in the product
|
||||
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
|
||||
|
|
@ -360,6 +362,11 @@ constexpr T limbs_to_integral(const std::uint64_t * limbs) noexcept
|
|||
/// @param lhs the left-hand operand
|
||||
/// @param rhs the right-hand operand
|
||||
/// @return the product at the requested width
|
||||
/// \complexity `mul_low_limbs` multiplies `L` 64-bit limbs, `L = fixed_mul_plan::limbs`, with loops `i < L` and `i + j < L`: `Θ(L²)` limb products.
|
||||
/// Reducing each operand and the align shift are `Θ(L)`. Scratch is a fixed limb buffer in this header (the 8-word arrays plus `fixed_mul_buf_limbs`).
|
||||
/// @see grotto::fixedpoint
|
||||
/// @note Plaintext. `grotto::eval_fixed_mul_beaver` is the same window on additive shares: one Beaver product in `Z/2^{multiply_bits}Z`, then the lifts and the aligning shift.
|
||||
/// @see grotto::eval_fixed_mul_beaver
|
||||
template <unsigned IntegerBits,
|
||||
unsigned FractionalBits,
|
||||
unsigned LhsFractionalBits,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ namespace grotto
|
|||
|
||||
namespace gadgets
|
||||
{
|
||||
/// \complexity Static constants only. Reading a field is `Θ(1)`.
|
||||
/// @see grotto::gadgets
|
||||
|
||||
template <typename T>
|
||||
struct gadget_hints
|
||||
|
|
@ -33,6 +35,8 @@ struct gadget_hints
|
|||
static constexpr double * canonical_bounds = nullptr;
|
||||
static constexpr std::array<double, degree+1> * canonical_polys = nullptr;
|
||||
};
|
||||
/// \complexity Two `nexttoward` calls. `Θ(1)`.
|
||||
/// @see grotto::gadgets
|
||||
|
||||
constexpr double ulp_of(double x)
|
||||
{
|
||||
|
|
@ -44,6 +48,8 @@ constexpr double ulp_of(double x)
|
|||
template <typename T, std::size_t N>
|
||||
struct gadget_domain
|
||||
{
|
||||
/// \complexity A constant number of `exp2` / casts. `Θ(1)`.
|
||||
/// @see grotto::gadget_hints
|
||||
static constexpr T min()
|
||||
{
|
||||
|
||||
|
|
|
|||
|
|
@ -21,9 +21,19 @@ namespace gadgets
|
|||
{
|
||||
|
||||
static constexpr double celu_default_alpha = 1;
|
||||
/// @brief Cleartext `celu`.
|
||||
/// @see grotto::eval_closed
|
||||
/// @tparam alpha functor parameter (a `double` bound or scale)
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
template <double const & alpha = celu_default_alpha>
|
||||
struct celu
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -25,8 +25,17 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `elish`.
|
||||
/// @see grotto::eval_window
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_window) elish
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -21,9 +21,19 @@ namespace gadgets
|
|||
{
|
||||
|
||||
static constexpr double elu_default_alpha = 1;
|
||||
/// @brief Cleartext `elu`.
|
||||
/// @see grotto::eval_closed
|
||||
/// @tparam alpha functor parameter (a `double` bound or scale)
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
template <double const & alpha = elu_default_alpha>
|
||||
struct elu
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -24,8 +24,17 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `gelu`.
|
||||
/// @see grotto::eval_window
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_window) gelu
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,8 +20,17 @@ namespace grotto
|
|||
namespace gadgets
|
||||
{
|
||||
|
||||
/// @brief Cleartext `hardelish`.
|
||||
/// @see grotto::eval_window with `window::hardelish`
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct hardelish
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -25,9 +25,19 @@ HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
|||
|
||||
|
||||
static constexpr double hardshrink_default_lambda = 0.5;
|
||||
/// @brief Cleartext `hardshrink`.
|
||||
/// @see grotto::make_hardshrink_lut
|
||||
/// @tparam lambda functor parameter (a `double` bound or scale)
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
template <double const & lambda = hardshrink_default_lambda>
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::make_hardshrink_lut) hardshrink
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -24,8 +24,17 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `hardsigmoid`.
|
||||
/// @see grotto::make_hardsigmoid_lut
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::make_hardsigmoid_lut) hardsigmoid
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -24,8 +24,17 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `hardswish`.
|
||||
/// @see grotto::make_hardswish_lut
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::make_hardswish_lut) hardswish
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -24,8 +24,17 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `hardtanh`.
|
||||
/// @see grotto::make_hardtanh_lut
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::make_hardtanh_lut) hardtanh
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -26,9 +26,21 @@ HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
|||
|
||||
static constexpr double leakyrelu_default_negative_slope = 0.01;
|
||||
static constexpr double leakyrelu_zero_negative_slope = 0.0;
|
||||
/// @brief Cleartext `leakyrelu`. The default slope `0.01` is
|
||||
/// `make_leaky_relu_hundredth_lut`. A power-of-two slope is
|
||||
/// `make_leaky_relu_lut`.
|
||||
/// @see grotto::make_leaky_relu_hundredth_lut
|
||||
/// @tparam negative_slope functor parameter (a `double` bound or scale)
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
template <double const & negative_slope = leakyrelu_default_negative_slope>
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::make_leaky_relu_lut) leakyrelu
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::make_leaky_relu_hundredth_lut) leakyrelu
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -21,8 +21,17 @@ namespace grotto
|
|||
namespace gadgets
|
||||
{
|
||||
|
||||
/// @brief Cleartext `lecun_tanh`.
|
||||
/// @see grotto::eval_window with `window::lecun_tanh`
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct lecun_tanh
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -25,8 +25,17 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `logsigmoid`.
|
||||
/// @see grotto::eval_window
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_window) logsigmoid
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x) { return std::log(sigmoid{}(x)); }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -25,8 +25,17 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `mish`.
|
||||
/// @see grotto::eval_window
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_window) mish
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -24,8 +24,17 @@ namespace gadgets
|
|||
HEDLEY_DIAGNOSTIC_PUSH
|
||||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
/// @brief Cleartext `one_minus_sigmoid`.
|
||||
/// @see grotto::eval_window with `window::one_minus_sigmoid`
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_window) one_minus_sigmoid
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ namespace gadgets
|
|||
|
||||
HEDLEY_DIAGNOSTIC_PUSH
|
||||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
/// @brief Alias of another cleartext functor, under the name `relu`.
|
||||
/// @see grotto::make_relu_lut
|
||||
/// \complexity Same as the functor it aliases: one straight-line `operator()`. `Θ(1)`.
|
||||
using relu HEDLEY_DEPRECATED_FOR(2026, grotto::make_relu_lut) = leakyrelu<leakyrelu_zero_negative_slope>;
|
||||
HEDLEY_DIAGNOSTIC_POP
|
||||
|
||||
|
|
|
|||
|
|
@ -25,9 +25,19 @@ HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
|||
|
||||
|
||||
static constexpr double relu6_default_clip = 6;
|
||||
/// @brief Cleartext `relu6`.
|
||||
/// @see grotto::make_relu6_lut
|
||||
/// @tparam clip functor parameter (a `double` bound or scale)
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
template <double const & clip = 6>
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::make_relu6_lut) relu6
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -22,10 +22,21 @@ namespace gadgets
|
|||
|
||||
static constexpr double selu_default_alpha = 1.6732632423543772848170429916717;
|
||||
static constexpr double selu_default_scale = 1.0507009873554804934193349852946;
|
||||
/// @brief Cleartext `selu`.
|
||||
/// @see grotto::eval_closed
|
||||
/// @tparam scale functor parameter (a `double` bound or scale)
|
||||
/// @tparam alpha functor parameter (a `double` bound or scale)
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
template <double const & alpha = selu_default_alpha,
|
||||
double const & scale = selu_default_scale>
|
||||
struct selu
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -25,8 +25,17 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `serf`.
|
||||
/// @see grotto::eval_window
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_window) serf
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -24,8 +24,17 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `sigmoid`.
|
||||
/// @see grotto::eval_window
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_window) sigmoid
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x) { return 1/(1+std::exp(-x)); }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -24,8 +24,17 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `silu`.
|
||||
/// @see grotto::eval_window
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_window) silu
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x) { return x*sigmoid{}(x); }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -25,9 +25,19 @@ HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
|||
|
||||
|
||||
static constexpr double smoothstep_default_gamma = 1;
|
||||
/// @brief Cleartext `smoothstep`.
|
||||
/// @see grotto::eval_window
|
||||
/// @tparam gamma functor parameter (a `double` bound or scale)
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
template <double const & gamma = smoothstep_default_gamma>
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_window) smoothstep
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -26,9 +26,19 @@ HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
|||
|
||||
|
||||
static constexpr double softminus_default_beta = 1;
|
||||
/// @brief Cleartext `softminus`.
|
||||
/// @see grotto::eval_window
|
||||
/// @tparam beta functor parameter (a `double` bound or scale)
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
template <double const & beta = softminus_default_beta>
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_window) softminus
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -25,9 +25,19 @@ HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
|||
|
||||
|
||||
static constexpr double softplus_default_beta = 1;
|
||||
/// @brief Cleartext `softplus`.
|
||||
/// @see grotto::eval_window
|
||||
/// @tparam beta functor parameter (a `double` bound or scale)
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
template <double const & beta = softplus_default_beta>
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_window) softplus
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -25,9 +25,19 @@ HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
|||
|
||||
|
||||
static constexpr double softshrink_default_lambda = 0.5;
|
||||
/// @brief Cleartext `softshrink`.
|
||||
/// @see grotto::make_softshrink_lut
|
||||
/// @tparam lambda functor parameter (a `double` bound or scale)
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
template <double const & lambda = softshrink_default_lambda>
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::make_softshrink_lut) softshrink
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,8 +20,17 @@ namespace grotto
|
|||
namespace gadgets
|
||||
{
|
||||
|
||||
/// @brief Cleartext `hardsign`.
|
||||
/// @see grotto::eval_closed
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct hardsign
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x) { return x/(1+std::abs(x)); }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -24,8 +24,17 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `squared_relu`.
|
||||
/// @see grotto::make_squared_relu_lut
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::make_squared_relu_lut) squared_relu
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -22,10 +22,21 @@ namespace gadgets
|
|||
|
||||
static constexpr double starrelu_default_scale = 0.8944;
|
||||
static constexpr double starrelu_default_shift = -0.4472;
|
||||
/// @brief Cleartext `starrelu`.
|
||||
/// @see grotto::make_relu_lut
|
||||
/// @tparam shift functor parameter (a `double` bound or scale)
|
||||
/// @tparam scale functor parameter (a `double` bound or scale)
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
template<double const & scale = starrelu_default_scale,
|
||||
double const & shift = starrelu_default_shift>
|
||||
struct starrelu
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -24,8 +24,17 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `tanhexp`.
|
||||
/// @see grotto::eval_window
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_window) tanhexp
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x) { return x*std::tanh(std::exp(x)); }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -20,8 +20,17 @@ namespace grotto
|
|||
namespace gadgets
|
||||
{
|
||||
|
||||
/// @brief Cleartext `tanhshrink`.
|
||||
/// @see grotto::eval_closed
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct tanhshrink
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x) { return x -std::tanh(x); }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ namespace gadgets
|
|||
|
||||
|
||||
|
||||
/// @note This header declares no callable.
|
||||
/// Complexity is stated on the sibling that evaluates the map, when one exists.
|
||||
/// @see grotto::make_msb_lut
|
||||
} // namespace gadgets
|
||||
|
||||
} // namespace grotto
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ namespace gadgets
|
|||
|
||||
|
||||
|
||||
/// @note This header declares no callable.
|
||||
/// Complexity is stated on the sibling that evaluates the map, when one exists.
|
||||
/// @see grotto::eval_bit_ceil
|
||||
} // namespace gadgets
|
||||
|
||||
} // namespace grotto
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ namespace gadgets
|
|||
|
||||
|
||||
|
||||
/// @note This header declares no callable.
|
||||
/// Complexity is stated on the sibling that evaluates the map, when one exists.
|
||||
/// @see grotto::eval_bit_floor
|
||||
} // namespace gadgets
|
||||
|
||||
} // namespace grotto
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ namespace gadgets
|
|||
|
||||
|
||||
|
||||
/// @note This header declares no callable.
|
||||
/// Complexity is stated on the sibling that evaluates the map, when one exists.
|
||||
/// @see grotto::eval_bit_width
|
||||
} // namespace gadgets
|
||||
|
||||
} // namespace grotto
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ namespace gadgets
|
|||
|
||||
|
||||
|
||||
/// @note This header declares no callable.
|
||||
/// Complexity is stated on the sibling that evaluates the map, when one exists.
|
||||
/// @see grotto::exact_constant
|
||||
} // namespace gadgets
|
||||
|
||||
} // namespace grotto
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ namespace gadgets
|
|||
|
||||
|
||||
|
||||
/// @note This header declares no callable.
|
||||
/// Complexity is stated on the sibling that evaluates the map, when one exists.
|
||||
/// @see grotto::eval_countl_one
|
||||
} // namespace gadgets
|
||||
|
||||
} // namespace grotto
|
||||
|
|
|
|||
|
|
@ -24,8 +24,17 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `countl_zero`.
|
||||
/// @see grotto::exact_constant
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::exact_constant) countl_zero
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,8 +20,17 @@ namespace grotto
|
|||
namespace gadgets
|
||||
{
|
||||
|
||||
/// @brief Cleartext `has_single_bit`.
|
||||
/// @see grotto::eval_has_single_bit
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct has_single_bit
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ namespace gadgets
|
|||
|
||||
|
||||
|
||||
/// @note This header declares no callable.
|
||||
/// Complexity is stated on the sibling that evaluates the map, when one exists.
|
||||
/// @see grotto::prefix_parities
|
||||
} // namespace gadgets
|
||||
|
||||
} // namespace grotto
|
||||
|
|
|
|||
|
|
@ -24,8 +24,17 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `sgn`.
|
||||
/// @see grotto::exact_constant
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::exact_constant) sgn
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ namespace gadgets
|
|||
|
||||
|
||||
|
||||
/// @note This header declares no callable.
|
||||
/// Complexity is stated on the sibling that evaluates the map, when one exists.
|
||||
/// @see grotto::eval_dec_ceil
|
||||
} // namespace gadgets
|
||||
|
||||
} // namespace grotto
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ namespace gadgets
|
|||
|
||||
|
||||
|
||||
/// @note This header declares no callable.
|
||||
/// Complexity is stated on the sibling that evaluates the map, when one exists.
|
||||
/// @see grotto::eval_dec_floor
|
||||
} // namespace gadgets
|
||||
|
||||
} // namespace grotto
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ namespace gadgets
|
|||
|
||||
|
||||
|
||||
/// @note This header declares no callable.
|
||||
/// Complexity is stated on the sibling that evaluates the map, when one exists.
|
||||
/// @see grotto::eval_dec_width
|
||||
} // namespace gadgets
|
||||
|
||||
} // namespace grotto
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ namespace gadgets
|
|||
|
||||
|
||||
|
||||
/// @note This header declares no callable.
|
||||
/// Complexity is stated on the sibling that evaluates the map, when one exists.
|
||||
/// @see grotto::eval_has_single_digit
|
||||
} // namespace gadgets
|
||||
|
||||
} // namespace grotto
|
||||
|
|
|
|||
|
|
@ -24,8 +24,17 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `abs`.
|
||||
/// @see grotto::make_abs_lut
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::make_abs_lut) abs
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x) { return std::abs(static_cast<double>(x)); }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -23,10 +23,21 @@ namespace gadgets
|
|||
static constexpr double approx_default_target = 0;
|
||||
static constexpr unsigned approx_default_ULPs = 1;
|
||||
|
||||
/// @brief Cleartext `approx`.
|
||||
/// @see grotto::make_threshold_lut
|
||||
/// @tparam ULPs functor parameter (a `double` bound or scale)
|
||||
/// @tparam target functor parameter (a `double` bound or scale)
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
template <double const & target = approx_default_target,
|
||||
unsigned const & ULPs = approx_default_ULPs>
|
||||
struct approx
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x) { return std::abs(x - target) <= ULPs * ulp_of(target) ? 1 : 0; }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -22,12 +22,25 @@ namespace gadgets
|
|||
static constexpr double boxcar_default_outside = 0;
|
||||
static constexpr double boxcar_default_inside = 1;
|
||||
|
||||
/// @brief Cleartext `boxcar`.
|
||||
/// @see grotto::make_interval_lut
|
||||
/// @tparam inside functor parameter (a `double` bound or scale)
|
||||
/// @tparam outside functor parameter (a `double` bound or scale)
|
||||
/// @tparam to functor parameter (a `double` bound or scale)
|
||||
/// @tparam from functor parameter (a `double` bound or scale)
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
template <double const & from,
|
||||
double const & to,
|
||||
double const & outside = boxcar_default_outside,
|
||||
double const & inside = boxcar_default_inside>
|
||||
struct boxcar
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x) { return (x < from || x > to) ? outside : inside; }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -23,11 +23,22 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `clip`.
|
||||
/// @see grotto::make_clip_lut
|
||||
/// @tparam upper functor parameter (a `double` bound or scale)
|
||||
/// @tparam lower functor parameter (a `double` bound or scale)
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
template <double const & lower,
|
||||
double const & upper>
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::make_clip_lut) clip
|
||||
{
|
||||
static_assert(lower <= upper);
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x) { return std::max(std::min(x, upper), lower); }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -19,9 +19,19 @@ namespace grotto
|
|||
namespace gadgets
|
||||
{
|
||||
|
||||
/// @brief Cleartext `eq`.
|
||||
/// @see grotto::make_threshold_lut
|
||||
/// @tparam target functor parameter (a `double` bound or scale)
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
template <double const & target>
|
||||
struct eq
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x) { return x == target; }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -19,9 +19,19 @@ namespace grotto
|
|||
namespace gadgets
|
||||
{
|
||||
|
||||
/// @brief Cleartext `geq`.
|
||||
/// @see grotto::make_threshold_lut
|
||||
/// @tparam target functor parameter (a `double` bound or scale)
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
template <double const & target>
|
||||
struct geq
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x) { return x >= target; }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -19,9 +19,19 @@ namespace grotto
|
|||
namespace gadgets
|
||||
{
|
||||
|
||||
/// @brief Cleartext `gt`.
|
||||
/// @see grotto::make_threshold_lut
|
||||
/// @tparam target functor parameter (a `double` bound or scale)
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
template <double const & target>
|
||||
struct gt
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x) { return x > target; }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ namespace gadgets
|
|||
|
||||
|
||||
|
||||
/// @note This header declares no callable.
|
||||
/// Complexity is stated on the sibling that evaluates the map, when one exists.
|
||||
/// @see grotto::fixedpoint
|
||||
} // namespace gadgets
|
||||
|
||||
} // namespace grotto
|
||||
|
|
|
|||
|
|
@ -19,9 +19,19 @@ namespace grotto
|
|||
namespace gadgets
|
||||
{
|
||||
|
||||
/// @brief Cleartext `leq`.
|
||||
/// @see grotto::make_threshold_lut
|
||||
/// @tparam target functor parameter (a `double` bound or scale)
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
template <double const & target>
|
||||
struct leq
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x) { return x <= target; }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -19,9 +19,19 @@ namespace grotto
|
|||
namespace gadgets
|
||||
{
|
||||
|
||||
/// @brief Cleartext `lt`.
|
||||
/// @see grotto::make_threshold_lut
|
||||
/// @tparam target functor parameter (a `double` bound or scale)
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
template <double const & target>
|
||||
struct lt
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x) { return x < target; }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ namespace gadgets
|
|||
{
|
||||
|
||||
static constexpr double lt_target_zero = 0;
|
||||
/// @brief Alias of another cleartext functor, under the name `negative`.
|
||||
/// @see grotto::exact_constant
|
||||
/// \complexity Same as the functor it aliases: one straight-line `operator()`. `Θ(1)`.
|
||||
using negative HEDLEY_DEPRECATED_FOR(2026, grotto::exact_constant) = lt<lt_target_zero>;
|
||||
|
||||
} // namespace gadgets
|
||||
|
|
|
|||
|
|
@ -19,9 +19,19 @@ namespace grotto
|
|||
namespace gadgets
|
||||
{
|
||||
|
||||
/// @brief Cleartext `neq`.
|
||||
/// @see grotto::make_nonzero_lut
|
||||
/// @tparam target functor parameter (a `double` bound or scale)
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
template <double const & target>
|
||||
struct neq
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x) { return x != target; }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ namespace gadgets
|
|||
{
|
||||
|
||||
static constexpr double geq_target_zero = 0;
|
||||
/// @brief Alias of another cleartext functor, under the name `nonnegative`.
|
||||
/// @see grotto::exact_constant
|
||||
/// \complexity Same as the functor it aliases: one straight-line `operator()`. `Θ(1)`.
|
||||
using nonnegative HEDLEY_DEPRECATED_FOR(2026, grotto::exact_constant) = geq<geq_target_zero>;
|
||||
|
||||
} // namespace gadgets
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ namespace gadgets
|
|||
{
|
||||
|
||||
static constexpr double leq_target_zero = 0;
|
||||
/// @brief Alias of another cleartext functor, under the name `nonpositive`.
|
||||
/// @see grotto::exact_constant
|
||||
/// \complexity Same as the functor it aliases: one straight-line `operator()`. `Θ(1)`.
|
||||
using nonpositive HEDLEY_DEPRECATED_FOR(2026, grotto::exact_constant) = leq<leq_target_zero>;
|
||||
|
||||
} // namespace gadgets
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ namespace gadgets
|
|||
{
|
||||
|
||||
static constexpr double neq_target_zero = 0;
|
||||
/// @brief Alias of another cleartext functor, under the name `nonzero`.
|
||||
/// @see grotto::exact_constant
|
||||
/// \complexity Same as the functor it aliases: one straight-line `operator()`. `Θ(1)`.
|
||||
using nonzero HEDLEY_DEPRECATED_FOR(2026, grotto::exact_constant) = neq<neq_target_zero>;
|
||||
|
||||
} // namespace gadgets
|
||||
|
|
|
|||
|
|
@ -16,6 +16,10 @@ namespace gadgets
|
|||
|
||||
|
||||
|
||||
/// @note This header declares no callable.
|
||||
/// Complexity is stated on the sibling that evaluates the map, when one exists.
|
||||
/// @see grotto::gadget_hints
|
||||
/// @note No fixed-point table in `eval_closed`, `eval_window`, or `eval_reduced` is named after this file. Complexity is not stated here.
|
||||
} // namespace gadgets
|
||||
|
||||
} // namespace grotto
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ namespace gadgets
|
|||
{
|
||||
|
||||
static constexpr double gt_target_zero = 0;
|
||||
/// @brief Alias of another cleartext functor, under the name `positive`.
|
||||
/// @see grotto::exact_constant
|
||||
/// \complexity Same as the functor it aliases: one straight-line `operator()`. `Θ(1)`.
|
||||
using positive HEDLEY_DEPRECATED_FOR(2026, grotto::exact_constant) = gt<gt_target_zero>;
|
||||
|
||||
} // namespace gadgets
|
||||
|
|
|
|||
|
|
@ -19,8 +19,17 @@ namespace grotto
|
|||
namespace gadgets
|
||||
{
|
||||
|
||||
/// @brief Cleartext `rect`.
|
||||
/// @see grotto::make_zero_lut
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct rect
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x) { return x == 0; }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -22,11 +22,23 @@ namespace gadgets
|
|||
static constexpr double step_default_before = 0;
|
||||
static constexpr double step_default_after = 1;
|
||||
|
||||
/// @brief Cleartext `step`.
|
||||
/// @see grotto::make_exact_step_lut
|
||||
/// @tparam after functor parameter (a `double` bound or scale)
|
||||
/// @tparam before functor parameter (a `double` bound or scale)
|
||||
/// @tparam at functor parameter (a `double` bound or scale)
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
template <double const & at,
|
||||
double const & before = step_default_before,
|
||||
double const & after = step_default_after>
|
||||
struct step
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x) { return x < at ? before : after; }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,6 +16,10 @@ namespace gadgets
|
|||
|
||||
|
||||
|
||||
/// @note This header declares no callable.
|
||||
/// Complexity is stated on the sibling that evaluates the map, when one exists.
|
||||
/// @see grotto::gadget_hints
|
||||
/// @note No fixed-point table in `eval_closed`, `eval_window`, or `eval_reduced` is named after this file. Complexity is not stated here.
|
||||
} // namespace gadgets
|
||||
|
||||
} // namespace grotto
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ namespace gadgets
|
|||
{
|
||||
|
||||
static constexpr double eq_target_zero = 0;
|
||||
/// @brief Alias of another cleartext functor, under the name `zero`.
|
||||
/// @see grotto::exact_constant
|
||||
/// \complexity Same as the functor it aliases: one straight-line `operator()`. `Θ(1)`.
|
||||
using zero HEDLEY_DEPRECATED_FOR(2026, grotto::exact_constant) = eq<eq_target_zero>;
|
||||
|
||||
} // namespace gadgets
|
||||
|
|
|
|||
|
|
@ -24,8 +24,17 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `exp`.
|
||||
/// @see grotto::eval_reduced
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) exp
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x) { return std::min(std::exp(static_cast<double>(x)), std::numeric_limits<T>::max()); }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -24,8 +24,17 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `exp10`.
|
||||
/// @see grotto::eval_reduced
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) exp10
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x) { return std::min(std::pow(10, static_cast<double>(x)), std::numeric_limits<T>::max()); }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -24,8 +24,17 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `exp2`.
|
||||
/// @see grotto::eval_reduced
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) exp2
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x) { return std::min(std::exp2(static_cast<double>(x)), std::numeric_limits<T>::max()); }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -22,8 +22,17 @@ namespace gadgets
|
|||
|
||||
// Returns the inverse hyperbolic cosine of a number.
|
||||
// The number must be greater than or equal to 1.
|
||||
/// @brief Cleartext `acosh`.
|
||||
/// @see grotto::eval_closed
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct acosh
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x) {
|
||||
if (x < 1) return -1; // TODO
|
||||
|
|
|
|||
|
|
@ -20,8 +20,17 @@ namespace grotto
|
|||
namespace gadgets
|
||||
{
|
||||
|
||||
/// @brief Cleartext `acoth`.
|
||||
/// @see grotto::eval_closed
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct acoth
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x) {
|
||||
if (std::abs(x) <= 1) return 0; // TODO
|
||||
|
|
|
|||
|
|
@ -20,8 +20,17 @@ namespace grotto
|
|||
namespace gadgets
|
||||
{
|
||||
|
||||
/// @brief Cleartext `acsch`.
|
||||
/// @see grotto::eval_closed
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct acsch
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,8 +20,17 @@ namespace grotto
|
|||
namespace gadgets
|
||||
{
|
||||
|
||||
/// @brief Cleartext `asech`.
|
||||
/// @see grotto::eval_closed
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct asech
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,8 +20,17 @@ namespace grotto
|
|||
namespace gadgets
|
||||
{
|
||||
|
||||
/// @brief Cleartext `asinh`.
|
||||
/// @see grotto::eval_closed
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct asinh
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x) { return std::asinh(static_cast<double>(x)); }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -20,8 +20,17 @@ namespace grotto
|
|||
namespace gadgets
|
||||
{
|
||||
|
||||
/// @brief Cleartext `atanh`.
|
||||
/// @see grotto::eval_closed
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct atanh
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -24,8 +24,17 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `cosh`.
|
||||
/// @see grotto::eval_reduced
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) cosh
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x) { return std::min(std::cosh(static_cast<double>(x)), std::numeric_limits<T>::max()); }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -24,8 +24,17 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `coth`.
|
||||
/// @see grotto::eval_reduced
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) coth
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -24,8 +24,17 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `csch`.
|
||||
/// @see grotto::eval_reduced
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) csch
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -24,8 +24,17 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `sech`.
|
||||
/// @see grotto::eval_reduced
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) sech
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x) { return 1/std::cosh(static_cast<double>(x)); }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -24,8 +24,17 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `sinh`.
|
||||
/// @see grotto::eval_reduced
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) sinh
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -24,8 +24,17 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `tanh`.
|
||||
/// @see grotto::eval_reduced
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) tanh
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x) { return std::tanh(static_cast<double>(x)); }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -24,8 +24,17 @@ namespace gadgets
|
|||
HEDLEY_DIAGNOSTIC_PUSH
|
||||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
/// @brief Cleartext `ilog10`.
|
||||
/// @see grotto::exact_constant
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::exact_constant) ilog10
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,8 +20,17 @@ namespace grotto
|
|||
namespace gadgets
|
||||
{
|
||||
|
||||
/// @brief Cleartext `ilog16`.
|
||||
/// @see grotto::eval_ilog16
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct ilog16
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,8 +20,17 @@ namespace grotto
|
|||
namespace gadgets
|
||||
{
|
||||
|
||||
/// @brief Cleartext `ilog256`.
|
||||
/// @see grotto::eval_ilog256
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct ilog256
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -24,8 +24,17 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `ilogb`.
|
||||
/// @see grotto::exact_constant
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::exact_constant) ilogb
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -24,8 +24,17 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `lg`.
|
||||
/// @see grotto::eval_reduced
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) lg
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -24,8 +24,17 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `ln`.
|
||||
/// @see grotto::eval_reduced
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) ln
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -24,8 +24,17 @@ HEDLEY_DIAGNOSTIC_PUSH
|
|||
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
|
||||
|
||||
|
||||
/// @brief Cleartext `log10`.
|
||||
/// @see grotto::eval_reduced
|
||||
/// @note Straight-line cleartext. Fixed-point evaluation is the symbol in `@see` when that symbol is a LUT or an `eval_*` function.
|
||||
/// \complexity One evaluation of `operator()` as written in this header. There is no domain scan and no loop. `Θ(1)` time and extra space relative to that expression.
|
||||
struct HEDLEY_DEPRECATED_FOR(2026, grotto::eval_reduced) log10
|
||||
{
|
||||
/// @brief Evaluate the cleartext map.
|
||||
/// @tparam T argument and result type
|
||||
/// @param x the input
|
||||
/// @return the cleartext image of `x`
|
||||
/// \complexity The body is straight-line. `Θ(1)` time and extra space.
|
||||
template <typename T>
|
||||
T operator()(T x)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -16,6 +16,10 @@ namespace gadgets
|
|||
|
||||
|
||||
|
||||
/// @note This header declares no callable.
|
||||
/// Complexity is stated on the sibling that evaluates the map, when one exists.
|
||||
/// @see grotto::gadget_hints
|
||||
/// @note No fixed-point table in `eval_closed`, `eval_window`, or `eval_reduced` is named after this file. Complexity is not stated here.
|
||||
} // namespace gadgets
|
||||
|
||||
} // namespace grotto
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ namespace gadgets
|
|||
|
||||
|
||||
|
||||
/// @note This header declares no callable.
|
||||
/// Complexity is stated on the sibling that evaluates the map, when one exists.
|
||||
/// @see grotto::eval_logstar
|
||||
} // namespace gadgets
|
||||
|
||||
} // namespace grotto
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue