# Jet and exact ring switch {#jet_and_ring}
One opened offset `eta = x - r` drives two cheap corrections. The binomial
jet returns shares of \f$\binom{x}{0},\ldots,\binom{x}{d}\f$ after a public
Chu–Vandermonde shift. The ring switch returns shares of `x` in any residue
group whose comparison payload is the destination modulus.
The same offset also drives [offset Horner](@ref offset_horner),
[offset polynomials](@ref offset_poly), and [carry](@ref carry).
[Prefix parity](@ref prefix_parity) reads a key's path.
[Cleartext maps](@ref grotto_luts) evaluate fixed-point functions with no tree.
## Binomial jet {#offset_jet}
\htmlonly
ELI5. The dealer keys the binomial coefficients of (center + eta) up to a chosen degree. After eta is public, a dot with those shares is the monomial or the polynomial, with no further tree walk.
\endhtmlonly
`make_offset_jet_keys(center, degree)` keys one incremental `gt` whose
payload is the vector of \f$\binom{\mathrm{center}}{k}\f$ in
\f$\mathbb{Z}/2^{64}\f$. After `eta` opens,
the same knot shift and carry cut as offset poly refine the pieces. On the
piece with carry `kappa`,
\f[
\binom{c+\kappa}{k}
=\sum_j\binom{c}{j}\binom{\kappa}{k-j}.
\f]
`make_offset_jet_keys` writes one incremental comparison for degree `d`
(`d ≤ 16`). The seed spine is `Θ(n λ)` bits, with `n` the center's bit
length and `λ` the seed width. Value words grow with the `d+1` binomial
lanes. After `η` is public, `offset_jet_shares` evaluates that one key
on the `K` knots, the same order as one `eval_sequence` on the knots.
The Chu–Vandermonde
update after those walks is `Θ(P · d²)` arithmetic, where `P` is the
number of refined pieces (the knots, plus the domain minimum, plus the
carry cut when the input width is at most 62). `offset_jet_dot` is
`Θ(d)`. No further round when the coefficients are public.
`offset_jet_shares` returns that shifted jet. Public dots are free:
- value of \f$\sum a_k\binom{x}{k}\f$ via `offset_jet_dot`;
- forward difference via `offset_jet_difference_coeff` (Pascal);
- hockey-stick prefix via `offset_jet_prefix_coeff`.
The prefix needs \f$\binom{x}{k+1}\f$, so the key degree must be one larger
than the polynomial degree. Degree 16 therefore prefix-sums polynomials
through degree 15.
Binomials modulo \f$2^{64}\f$ use a falling factorial modulo
\f$2^{64+v_2(k!)}\f$ (\f$v_2(16!)=15\f$), then multiply by the inverse of the
odd part of \f$k!\f$. Dividing by \f$k!\f$ inside \f$\mathbb{Z}/2^{64}\f$ alone
is not exact.
A Padé pair or one Newton correction is two dots against the same jet and
one reciprocal after the shares are opened. Those are not separate APIs.
**Code samples**\n
ELI5. An n-bit limb is rewritten into another modulus, a field, or a P-256 scalar by an exact map on the opened residue. The value does not go through floating point, and the map does not expand another key.
\endhtmlonly
For an unsigned \f$n\f$-bit limb (\f$n\le 64\f$) with representatives in
\f$[0,2^n)\f$,
\f[
\eta + r = x + w\cdot 2^n,\qquad
w=\mathbf{1}[r+\eta\ge 2^n].
\f]
In any modulus \f$M\f$,
\f[
x \equiv \eta + (r\bmod M) - w\cdot(2^n\bmod M)\pmod M.
\f]
A `uint64` comparison share is not a share mod \f$M\f$. The payload of the
wrap comparison is the destination element \f$2^n\bmod M\f$. The dealer keys
`lt(2^n \bmod M)` at the secret `r` and stores an additive split of `r` in
the residue group. After `eta` opens, each party evaluates at the public
query \f$2^n-1-\eta\f$. That indicator is hot exactly on wrap, including the
`eta = 0` case. Party 0 adds public `eta`.
Destination groups:
- `grotto::zn64` and `grotto::zn128` ([residue.hpp](@ref grotto/residue.hpp));
- `dpf::field128`;
- `dpf::p256_scalar` (NIST P-256 order, not the point group).
`ring_switch_factor` reduces a share when `Factor` divides the
modulus. One switch into an lcm yields every factor by local reduction.
The dealer material is one `lt` key on that limb, `Θ(n λ)` bits for
limb width `n ≤ 64` and seed width `λ`, plus two residue shares of `r`. After `η` is
public, each party does one point evaluation (`Θ(n)` expands) and a
constant amount of arithmetic in the destination group.
`ring_switch_factor` is local.
This is the exact neighbour of truncated Barrett `nmod`.
`grotto::nmod(x_raw, x_bits, recip_raw, recip_bits, residue_bits)` splits
`x / M` when `recip_raw / 2^recip_bits` is a positive approximation of `1/M`.
The result is an `nmod_result`: `quotient` is `floor(x/M)`, and `residue`
is the fractional part truncated onto `residue_bits`.
`nmod_pow2(x_raw, x_bits, exp, residue_bits)` is the same split when the
modulus is a power of two. Both are one product by a reciprocal of at
most 128 bits, so time and extra memory are constant in the word size.
\code{cpp}
auto split = grotto::nmod_pow2(raw, 16, 0, 16);
\endcode
**Defined in**\n
@ref grotto/nmod.hpp
See also [representation shift and twisted jets](@ref repr_and_twist).
## Offset Horner {#offset_horner}
\htmlonly
ELI5. Powers of a public center are already shared. Shifting them by the opened eta, the binomial way, evaluates the polynomial at the secret. The degree here is fixed in the template.
\endhtmlonly
`make_offset_horner_keys(center)` keys one `gt` whose
payload is `center^m` for `m = 0 .. Degree`. `Degree` is at most 3
(`offset_horner_max_degree`). Pass `dpf::verifiable{}` for proof tokens.
After `eta` opens, `offset_horner_eval` returns that party's
share of the cubic at the wrapped point. Coefficients are one
`std::array` per knot, low degree first.
\code{cpp}
const std::uint8_t center = 12;
auto mat = grotto::make_offset_horner_keys(center);
std::vector knots{0};
std::vector> coeff{{4, 2, 1}};
auto s0 = grotto::offset_horner_eval<0, 2>(mat, knots, coeff, eta);
\endcode
`geneval_offset_horner` runs the same cubic from Jack Doerner and abhi shelat shares of
`x` and of the center, on a `dpf::ds_randomness` tape.
Degree is at most 3. The seed spine is one comparison, `Θ(n λ)` bits,
and the value words hold the four powers. Evaluation after `η` opens is
one sequence-shaped walk on the knots plus `O(1)` arithmetic. The
geneval form generates that same comparison once, opens one correction
word per level, as in [geneval](@ref tour_ds), and does not store a
reusable key.
**Defined in**\n
@ref grotto/offset_horner.hpp
## Offset polynomial {#offset_poly}
\htmlonly
ELI5. Same shift as offset Horner, but the degree is an argument, so the number of powered shares is chosen when the keys are built.
\endhtmlonly
`make_offset_poly_keys(center, degree)` is offset Horner at a runtime
degree, at most 16 (`offset_poly_max_degree`). One incremental `gt`
whose payload is the vector of powers. `offset_poly_eval` dots the shifted powers.
`offset_poly_clear` is the same polynomial in the clear.
`offset_poly_kappas` is the public carry of each piece.
Shared coefficients use `offset_poly_shift_share` (the binomial map is
linear) and `offset_poly_beaver_share` for the dot.
Degree `d` is at most 16: one key, `Θ(n λ)` bits of seed spine plus
value words that grow with `d`. The clear and public-coefficient evals
are one sequence-shaped walk on the `K` knots, then `O(d^2)` arithmetic. A shared-coefficient dot is one Beaver
inner product: one opening round of the masked vectors, communication
linear in the flattened length (pieces times `d+1` coefficients), and
one product share per coefficient in preprocessing. The shift of each
party's coefficient share is local.
\code{cpp}
auto mat = grotto::make_offset_poly_keys(std::uint8_t{12}, 4);
std::vector knots{0};
std::vector> coeff{{4, 2, 1, 0, 0}};
auto s0 = grotto::offset_poly_eval<0>(mat, knots, coeff, eta);
auto opened = s0 + grotto::offset_poly_eval<1>(mat, knots, coeff, eta);
\endcode
**Defined in**\n
@ref grotto/offset_poly.hpp
## Carry {#carry}
\htmlonly
ELI5. A carry across a shift is a short list of comparisons and bit corrections, not a generic circuit. The request names the source width, the shift, and the width of what comes out. Truncate, arithmetic shift, and sign-extend are the same plan with different output widths.
\endhtmlonly
A `carry_request` names the source width `n`, the shift `s`, the output
width `out_n`, a `carry_mode` (`truncate_reduce`, `same_ring`, `extend`,
`window`), and a `sign_knowledge` (`unknown`, `nonnegative`, `negative`).
`plan_carry` returns a `carry_recipe` whose flags are the steps that are
still live. `plan_carry_in(n, s)`, `plan_carry_out(n, s, sign)`, and
`plan_carry_fused(n, s, out_n, sign)` fill the common requests.
`make_carry_keys(recipe)` (and `make_carry_in_keys`, `make_carry_out_keys`,
`make_carry_fused_keys`) builds the dealer keys. `finalize_carry_in_blinds`
adjusts a truncate-reduce split. Online, `eval_carry_in(keys, party, opened)`
returns a `carry_eval_share` whose `value` is that party's share.
`opened` is `(x0 + x1 + rin) mod 2^n`. The other online entry points are
`eval_carry_out_known`, `eval_carry_out_unknown`, `eval_carry_extend`,
`eval_carry_window`, and `eval_carry_fused`.
Cleartext twins, for tests and for a public limb, are `eval_carry_clear`,
`carry_in_clear`, `carry_out_clear`, `carry_asr`, and `carry_mask`.
`plan_carry` is a constant-time inspection of the request. Each live
comparison flag becomes one DPF key whose domain is the limb width `w`
of that comparison, `Θ(w λ)` bits, and the online step is one point
walk of that key. A share-MSB AND adds one Beaver bit triple in
preprocessing and one opening round of a bit.
\code{cpp}
auto keys = grotto::make_carry_in_keys(32, 8);
auto share = grotto::eval_carry_in(keys, /*party*/ 0, opened);
auto clear = grotto::eval_carry_clear(keys.recipe, x0, x1);
\endcode
**Defined in**\n
@ref grotto/carry_plan.hpp, @ref grotto/carry.hpp
## Prefix parity {#prefix_parity}
\htmlonly
ELI5. One walk of an existing key stops at the public endpoints and folds XOR or addition along that prefix. The fold is O(number of endpoints), not a new key per prefix.
\endhtmlonly
`prefix_parities(key, endpoints)` walks a key to the sorted endpoints and
returns XOR shares of the prefix parities, plus the index of the first
endpoint on the wrap. `segment_parities` turns those into one share per
segment. `all_segment_parities_from_prefix_parities` is the same conversion
when you already hold the prefix array.
`signed_prefix_parities(key, endpoints)` needs a comparison channel
(assigned, if the payload was a wildcard). It returns one additive
`uint64_t` share per endpoint: for `dpf::gt(1)` that share opens to 1 when
the secret point is below the endpoint. `signed_prefix_parities_into`
writes a runtime-length buffer.
The prefix walk follows Storrier, Vadapalli, Lyons, and Henry, ePrint
2023/108: one key's prefix parity in place of a comparison per piece.
On `m` endpoints the walk resumes one path memoizer (`Θ(n)` nodes, `n`
the key depth). Expands are the nodes on those paths, `O(m n)` in the
worst case, and less when endpoints share a prefix or the zero-suffix
stop hits. `signed_prefix_parities` adds an `O(n)` sum of
value-correction words on each endpoint. Both calls are local.
`segment_parities` is the prefix walk plus an `O(m)` XOR of those bits.
\code{cpp}
std::array ends{10, 40};
auto [bits, first] = grotto::prefix_parities(k0, ends);
auto segs = grotto::segment_parities(k0, ends);
auto signs = grotto::signed_prefix_parities(cmp0, ends);
\endcode
**Defined in**\n
@ref grotto/prefix_parity.hpp
## Several LUTs, one comparison {#lut_union}
\htmlonly
ELI5. Stack the breakpoints of every table into one sorted list. One comparison and one prefix walk label the pieces of that list. Each table then sums the labels that fall inside its own intervals.
\endhtmlonly
`make_lut_union_plan(luts, eta)` shifts every piecewise LUT by the public
`eta`, inserts the same domain-minimum and carry cuts as
[offset polynomial](@ref offset_poly), and sorts the union. A piece of one
LUT is a span of those union knots: `[begin, end)`, or
`[begin, end-of-union) ∪ [0, end)` when the piece wraps. The span stores
that piece's binomial shift by its public `kappa`. Spans of one LUT
partition the union.
The interactive plan is one comparison, whatever the number of LUTs and
whatever the number of union knots:
- `plan.comparisons` and `plan.prefix_walks` are 1.
- `plan.depth` and `plan.geneval_rounds()` are the bitlength of the input.
- `plan.degree` is the widest polynomial. The payload is
`1, center, …, center^degree`.
- `schedule_lut_union` records one `fss_cmp` of that depth. The slot is
`lut_union_slot_bytes`: one AES block, or `lanes * 8` when the power
vector is wider. Prefix parity of the union is local after that
comparison.
`lut_union_eval` reads one `make_offset_poly_keys` key of degree
at least `plan.degree` and returns a share per LUT. `geneval_lut_union`
opens that comparison from XOR shares of the center, as in
`geneval_offset_horner`. Pass `dpf::arith_input` when the shares add to
the center in the input group.
`piecewise_from_easy` and `piecewise_from_constant` adapt the cleartext
tables. An `easy_lut` denominator other than 1 is a rounding division, so
`piecewise_from_easy` rejects it. Powers that are zero on every piece are
dropped, and the shared payload stays only as wide as the widest remaining
degree.
\code{cpp}
grotto::piecewise_lut low{{0, 10}, {{1, 0}, {0, 2}}};
grotto::piecewise_lut high{{0, 4, 12}, {{3, 0}, {1, 1}, {9, 4}}};
const std::uint8_t center = 12;
const std::uint8_t eta = 3;
auto plan = grotto::make_lut_union_plan({low, high}, eta);
auto mat = grotto::make_offset_poly_keys(center, plan.degree);
auto s0 = grotto::lut_union_eval<0>(mat, plan);
auto s1 = grotto::lut_union_eval<1>(mat, plan);
dpf::protocol::composer composer(0);
grotto::schedule_lut_union(composer, plan); // rounds == plan.depth
\endcode
**Code samples**\n
**Defined in**\n
@ref grotto/lut_union.hpp
## Cleartext maps {#grotto_luts}
These functions take a raw fixed-point word (`n << fractional_bits`) and
return a raw word. They do not build a DPF. The type
`grotto::fixedpoint` itself is a domain and an output; see
[Input types](@ref input_types) and [Output types](@ref output_types).
## Fixed-point product {#fixedpoint_mul}
\htmlonly
ELI5. The product lives in a ring wide enough for both fixed-point operands. One Beaver triple in that ring is the product; the binary point is placed by a public shift afterward.
\endhtmlonly
`fixed_mul(lhs, rhs)` multiplies two
`fixedpoint` values and keeps that many integer bits (including the sign)
and fraction bits. Bits below the fraction are floored. The product type
is the `result_type` of `fixed_mul_plan`. The plan uses at most 8
limbs and refuses a wider window, so the product is a constant amount
of 64-bit arithmetic and `O(1)` extra memory.
\code{cpp}
using q16 = grotto::fixedpoint<16, std::int32_t>;
auto prod = grotto::fixed_mul<16, 16>(q16{1.5}, q16{2.0});
\endcode
**Defined in**\n
@ref grotto/fixedpoint_mul.hpp
## Lookup tables {#lookup_tables}
\htmlonly
ELI5. A cleartext approximation is replaced by a table addressed with the secret. Constant, easy, range, window, and principal tables differ in how many bits of the input they consume and how the correction is added.
\endhtmlonly
Constant, easy, principal, range, and window tables are included from
`grotto.hpp`. The dyadic table comes in through `exact_steps.hpp`, which
`grotto.hpp` also includes.
- **Constant.** `make_exact_constant_lut(exact_constant::signum, fractional_bits)`
and the other `exact_constant` names (`positive`, `negative`, `nonneg`,
`nonpos`, `zero`, `nonzero`, `ilogb`, `ceil_ilogb`, `ilog10`, `clz`,
`clrsb`). `make_threshold_lut`, `make_interval_lut`, and
`make_clipped_quotient_lut` build a `constant_lut` you call as
`table(raw)`.
- **Easy.** Few-piece polynomials with integer knots:
`make_abs_lut`, `make_relu_lut`, `make_clip_lut`, `make_hardsigmoid_lut`,
`make_hardswish_lut`, `make_leaky_relu_hundredth_lut`, and the other
`make_*_lut` factories in [easy_lut.hpp](@ref grotto/easy_lut.hpp).
The result is an `easy_lut`. `make_leaky_relu_lut(shift)` is the
dyadic slope `1/2^shift`. The Appendix D leaky ReLU is slope `1/100`.
- **Dyadic.** Exact steps on powers of two: `make_signum_lut`,
`make_msb_lut(index)`, `make_ilogb_lut`, `make_ilog10_lut`, `make_clz_lut`,
`make_clrsb_lut`, and the sign predicates `make_positive_lut` through
`make_nonzero_lut`. `ilog_of_zero` is the sentinel for a zero argument.
`msb_bit_limit` is 8.
- **Range.** `eval_reduced(reduced::ln, fractional_bits, raw)` and the
other `reduced` names (`lg`, `log10`, `exp`, `exp2`, `exp10`, `sin`,
`cos`, `tan`, `cot`, `sec`, `csc`, the hyperbolics, `sqrt`, `inv`,
`rsqrt`, `invsq`, `expm1`, `log1p`). `split_positive` is the dyadic
mantissa split those reductions use.
- **Window.** `eval_window(window::gelu, fractional_bits, raw)`. The
`window` names cover `smoothstep`, `sigmoid`, `tanh`, `erf`, `erfc`,
`softplus`, `gelu`, `silu`, `asin`, `acos`, `probit`, `hardelish`,
`lecun_tanh`, `one_minus_sigmoid`, and the rest of the enum in
[window_lut.hpp](@ref grotto/window_lut.hpp).
- **Principal.** `eval_principal(principal::sin, fractional_bits, raw)` on
the closed principal interval. Precisions are 8, 12, …, 32
(`principal_precision`). Names: `ln`, `exp`, `sin`, `tanf`, `tang`,
`sinh`, `cosh`, `sqrt`, `coth`, `sec`, `gsec`, `csch`, `inv`, `rsqrt`,
`invsq`.
- **Wavelet.** Haar and bior(5,3) compressed tables:
[Wavelet lookup tables](@ref dwt_luts).
\code{cpp}
auto sign = grotto::make_exact_constant_lut(
grotto::exact_constant::signum, 0);
auto s = sign(std::int32_t{-3});
auto relu = grotto::make_relu_lut(8);
auto ln = grotto::eval_reduced(grotto::reduced::ln, 16, raw);
auto gelu = grotto::eval_window(grotto::window::gelu, 16, raw);
auto sine = grotto::eval_principal(grotto::principal::sin, 16, raw);
\endcode
The degree-0 exact tables follow Storrier, Vadapalli, Lyons, and Henry,
[ePrint 2023/108](@ref bib_grotto), Appendix D.
Sign predicates are a constant number of cuts, `Θ(1)`. `clz` and
`ilogb` cut once per bit of the raw width, `Θ(w)`. `ilog10` binary-searches
the raw domain once per decimal exponent, `Θ(w²)` probes. `make_msb_lut`
emits `Θ(2^index)` cuts and rejects `index` at or above 8.
`make_clipped_quotient_lut` is linear in `(high-low)/modulus`, capped at
`2^16` pieces. Calling a constant or easy table binary-searches its `P`
pieces, `O(log P)`. `eval_principal` and `eval_window` binary-search the
static knots and then run one cubic. `eval_reduced` adds a short series
on the small interval (`expm1` 24 terms, `log1p` 80). No DPF and no
communication.
## Appendix D maps that were still cleartext {#appendix_d_gaps}
Appendix D of [ePrint 2023/108](@ref bib_grotto) lists HardELiSH, LeCun tanh, and leaky
ReLU with slope `1/100`. Those three now have fixed-point evaluators.
`one_minus_sigmoid` is the sigmoid table complemented, which rounds out
the logistic pair.
The cubics were built on mocha2. Sollya chose the longest pieces whose
absolute error stays within half an ulp. Mathematica (Remez), Maple
(`numapprox[minimax]`), and MATLAB/Chebfun (`minimax`) fitted each
piece, and the shipped polynomial is the one with the lowest error
after the coefficients are rounded to `k+16` fraction bits. Of the 443
cubics, Sollya won 229, Maple 83, MATLAB 74, and Mathematica 57.
Half an ulp at 16 fraction bits is `2^{-17} ≈ 7.63e-6`. Appendix D's
own columns are tighter (`4.2e-8`) and therefore use more pieces
(HardELiSH 38, LeCun tanh 89). The counts below are this library's
half-ulp partitions.
| map | degree | pieces at k = 8, 12, 16, 20, 24, 28, 32 | evaluation |
| --- | --- | --- | --- |
| `window::hardelish` | 3 on `(-1, 0)`; exact quadratic on `[0, 1]` | 1, 2, 3, 6, 12, 23, 45 | `Θ(log P)` knot search and one cubic on `(-1, 0)`. Elsewhere `Θ(1)`: `0`, `round(x(x+1)/2)`, or `x` |
| `window::lecun_tanh` | 3 | 3, 6, 11, 22, 44, 88, 177 | `Θ(log P)` on the positive knots of the absolute value, then a sign. Past the last knot the value is the constant `±round(1.7159 · 2^k)` |
| `window::one_minus_sigmoid` | 3 | same as `sigmoid`: 8, 16, 64, 128, 256, 1024, 2048 | one sigmoid evaluation and one subtraction. `Θ(log P)` |
| `make_leaky_relu_hundredth_lut` | 1 | 2 | `Θ(1)`. Identity on the right, `round(x/100)` on the left. Error at most half a unit in the last place |
`make_leaky_relu_lut(shift)` is still the dyadic slope `1/2^shift`.
The hundredth factory is the Appendix D slope and does not depend on
the fractional width.
`grotto::polynomials::eval_horner` evaluates a `poly_constant`,
`poly_linear`, `poly_quadratic`, or `poly_cubic` (a `std::array` of
coefficients, constant term first). `piecewise_eval(polys, bounds, x)`
picks the piece and calls that Horner step.
**Defined in**\n
@ref grotto/constant_lut.hpp, @ref grotto/easy_lut.hpp,
@ref grotto/dyadic_lut.hpp, @ref grotto/range_lut.hpp,
@ref grotto/window_lut.hpp, @ref grotto/principal_lut.hpp,
@ref grotto/piecewise.hpp
## Wavelet lookup tables {#dwt_luts}
\htmlonly
ELI5. A wavelet step is a fixed linear combination. The LUT stores that combination so the signal stays in shares and never enters a floating-point routine. Haar and biorthogonal 5/3 are the two filters.
\endhtmlonly
`make_haar_dwt_lut` and `make_bior53_dwt_lut` compress a real signal of
length \f$2^n\f$ and evaluate it as a fixed-point word. The construction
is the cleartext Haar and bior(5,3) lookup of Reis, Ugurbil, Wagh, Henry,
and de Vega, [ePrint 2025/013](@ref bib_wave), Equations (7) and (8).
`sample_dwt_signal(domain_bits, fractional_bits, f)` writes the grid
\f$i \cdot 2^{-f}\f$ for \f$i \in [0, 2^n)\f$.
Both builders run the depth-\f$j\f$ low-pass with the smooth edge
extension used for that paper's accuracy tables. PyWavelets calls these
filters `haar` and `bior2.2`; bior(5,3) is the same pair, named there by
vanishing moments. Building either table is \f$\Theta(N)\f$ arithmetic
and extra memory, \f$N = 2^n\f$.
Haar then multiplies the approximation coefficients by \f$2^{-j/2}\f$
and rounds down to \f$f\f$ fraction bits. On this grid that coefficient
is the mean of each block of \f$2^j\f$ samples. Evaluation reads
`coeff[raw >> j]`, one indexing step, \f$\Theta(1)\f$.
bior(5,3) multiplies by \f$2^{j/2}\f$ and rounds down the same way.
Smooth extension prepends two coefficients, so the bin `msb = raw >> j`
lives at index `msb + 2`, and the next tap at `msb + 3`, wrapping in the
stored vector. With `lsb = raw mod 2^j`,
\f[
y = \bigl\lfloor\bigl(c_{\mathrm{msb}+2}\,(2^j - \mathrm{lsb})
+ c_{\mathrm{msb}+3}\,\mathrm{lsb}\bigr) / 2^{2j}\bigr\rfloor.
\f]
That is Equation (8): the Lemma 6 weights \f$(2^j - \mathrm{lsb}_j)\f$
and \f$\mathrm{lsb}_j\f$, in integer arithmetic. Two multiplications and
a shift, \f$\Theta(1)\f$.
The paper's online protocols look these tables up under a DPF. Haar is
paired there with a deterministic Pika truncation; bior(5,3) is paired
with segment parity. Those protocols are not a key type here. The value
they open is `table(raw)`.
\code{cpp}
auto samples = grotto::sample_dwt_signal(6, 4, [](double x) {
return 1.0 / (1.0 + std::exp(-(x - 2.0)));
});
auto haar = grotto::make_haar_dwt_lut(samples, 4, 2);
auto bior = grotto::make_bior53_dwt_lut(samples, 4, 2);
auto h = haar(std::uint64_t{32});
auto b = bior(std::uint64_t{33});
\endcode
**Code samples**\n
- dwt_lut.cpp \include{cpp} grotto/dwt_lut.cpp
**Defined in**\n
@ref grotto/dwt_lut.hpp
## Closed form {#closed_form}
`eval_closed(closed::atanh, fractional_bits, raw)` composes
`eval_reduced` and `eval_window`. The `closed` names are the inverse
hyperbolics and inverse trig functions, `selu`, `elu`, `celu`,
`softsign`, `tanhshrink`, the `logistic` / `exponential` / `laplace` /
`cauchy` quantiles, `sinc`, and the extra powers `cbrt`, `qtrt`,
`icbrt`, `iqtrt`, `pow_m01`, `pow_p15`, `pow_m3`. Precision is one of
8, 12, …, 32. Each call runs a constant number of `eval_reduced` or
`eval_window` evaluations.
\code{cpp}
auto y = grotto::eval_closed(grotto::closed::atan, 16, raw);
\endcode
**Defined in**\n
@ref grotto/closed_form.hpp
## Exact steps {#exact_steps}
Counts and booleans come back as fixed-point integers,
`n << fractional_bits`.
\code{cpp}
auto floor_x = grotto::eval_dec_floor(raw, 16);
auto digits = grotto::eval_dec_width(raw, 16);
auto bits = grotto::eval_bit_width(raw, 16);
\endcode
Decimal digit counts divide in a loop, so the time follows the number
of digits. `eval_bit_width`, `eval_bit_floor`, `eval_bit_ceil`,
`eval_countl_one`, and `eval_has_single_bit` scan the raw width,
`Θ(width)` bit operations and at most 64 shifts. `eval_logstar` is five
magnitude comparisons, `Θ(1)`. `eval_deg2rad` and `eval_rad2deg` are one
scale each.
Also `eval_dec_ceil`, `eval_oct_width`, `eval_b64_width`,
`eval_value_length` (base 8, 10, or 64), `eval_has_single_digit`,
`eval_bit_floor`, `eval_bit_ceil`, `eval_countl_one`, `eval_has_single_bit`,
`eval_deg2rad`, and `eval_rad2deg`. `make_exact_step_lut` builds the
matching `easy_lut`.
**Defined in**\n
@ref grotto/exact_steps.hpp
## Gadget functors {#grotto_gadgets}
`grotto/gadgets.hpp` still includes the decimal and exponential reference
headers. The functors that those headers used to provide are deprecated:
call `eval_reduced`, `eval_window`, `make_*_lut`, or `exact_constant`
instead. `gadget_hints` holds the old domain, degree, and pole notes
for a functor type.
**Defined in**\n
@ref grotto/gadgets.hpp, @ref grotto/gadget_hints.hpp
\htmlonly
TL;DR. Open eta = x − r once. Jets and Horner turn that public distance into polynomial powers. Ring switch and carry move the integer. Prefix parity folds a key you already hold. Lookup tables, including the wavelet tables, replace cleartext math. A fixed-point product is one Beaver triple.