Device Functions#
The following are __device__ member functions of a descriptor’s bigint type
(see Types). Its double-width counterpart, bigint_wide, carries
almost none of these directly—it exposes only construction, access, div_rem, and
reduce_montgomery—and otherwise carries the lo/hi result of mul_wide,
operator*, and square, which Barrett reduction, Montgomery reduction, and its own
div_rem then consume.
For a warp-executed instance (TPI > 1), every cooperating lane must call these functions
together, in lockstep, with identical arguments, including the same address for any pointer
argument. Some operations are available only for thread execution; see
Warp Execution Coverage.
Unless an entry says otherwise, both operands of a binary operation must be the same type—same
bit width, threads per instance, and error policy—with no implicit conversion between widths.
div_rem is the sole exception; see
Operand Widths.
Construction and Access#
The semantics below are written in terms of limb[i], the i-th 32-bit limb, so that the
value is limb[0] + limb[1] * 2^32 + ... + limb[num_limbs-1] * 2^(32 * (num_limbs-1)).
Each assignment holds for every i from 0 to num_limbs - 1.
-
__device__ bigint::bigint()#
Default-constructs a zero-initialized value. Useful for declaring an output such as a quotient, remainder, or struct member before it is assigned by another operation (for example
bigint qv, rv;before callingdiv_rem).limb[i] = 0
-
__device__ explicit bigint::bigint(uint32_t value)#
Constructs from a 32-bit scalar.
limb[0] = value limb[i] = 0 for i > 0
-
__device__ explicit bigint::bigint(uint64_t value)#
Constructs from a 64-bit scalar. Deleted when
num_limbs == 1(a single 32-bit limb cannot hold a 64-bit value).limb[0] = value & 0xffffffff limb[1] = value >> 32 limb[i] = 0 for i > 1
A value can also be loaded from memory on construction and written back with store.
Under warp execution every lane passes the same pointer, and each lane transfers only the
limbs it owns.
-
__device__ explicit bigint::bigint(const uint32_t *ptr)#
Loads
num_limbslimbs starting atptr.limb[i] = ptr[i]
-
__device__ bigint::bigint(const uint32_t *ptr, unsigned int index)#
Loads instance
indexof a batched array atptr, which storesnum_limbslimbs per instance.indexaddresses a big-integer-sized element, not a limb.limb[i] = ptr[index * num_limbs + i]
-
__device__ void bigint::store(uint32_t *ptr) const#
Stores
num_limbslimbs starting atptr.ptr[i] = limb[i]
-
__device__ void bigint::store(uint32_t *ptr, unsigned int index) const#
Stores to instance
indexof a batched array atptr, addressed as in the indexed constructor.ptr[index * num_limbs + i] = limb[i]
-
__device__ uint32_t bigint::to_uint32() const#
Returns the low 32 bits of the value. Under warp execution the limb is broadcast, so every lane of the group receives the same result.
result = limb[0]
-
__device__ uint64_t bigint::to_uint64() const#
Returns the low 64 bits of the value, broadcast to every lane in the same way.
result = limb[0] + limb[1] * 2^32 if num_limbs > 1 result = limb[0] if num_limbs == 1
- __device__ const uint32_t &bigint::operator[](
- unsigned int index,
Direct access to limb
index, least significant first. The non-const form returns a mutable reference, so a limb can be written in place. Hereindexcounts limbs, unlike theindexparameter of the batched constructor andstore, which counts whole big integers.result = limb[index]
Available only for
Thread()execution (tpi == 1). UnderWarp()no single lane holds the whole instance, so this overload does not exist and a call fails to compile.
bigint_wide provides the same construction and access surface over 2 * num_limbs limbs,
where limb[i] spans both halves: lo holds limb[0] through limb[num_limbs - 1]
and hi holds the rest. It has no scalar constructors and no to_uint32 or to_uint64.
-
__device__ bigint_wide::bigint_wide()#
-
__device__ explicit bigint_wide::bigint_wide(const uint32_t *ptr)#
- __device__ explicit bigint_wide::bigint_wide(
- const uint32_t *ptr,
- unsigned int index,
-
__device__ void bigint_wide::store(uint32_t *ptr) const#
- __device__ void bigint_wide::store(
- uint32_t *ptr,
- unsigned int index,
Zero-initialization, pointer and batched loads, and the matching stores, each behaving as the
bigintform of the same name with2 * num_limbslimbs per instance.
-
__device__ uint32_t &bigint_wide::operator[](unsigned int index)#
- __device__ const uint32_t &bigint_wide::operator[](
- unsigned int index,
Direct access to limb
indexof the combined value, so one index reaches intoloorhiwithout naming either. Restricted totpi == 1like thebigintform.result = lo[index] if index < num_limbs result = hi[index - num_limbs] if index >= num_limbs
Basic Arithmetic#
Unsigned arithmetic on the full bit_width value. The three groups below differ mainly in what
happens when a result does not fit: addition, subtraction, and scalar multiplication wrap modulo
2^bit_width; mul_wide, operator*, and square widen instead, returning the exact
product as a bigint_wide; division cannot overflow but can fail, so it reports a
bigint_error status.
Addition and Subtraction#
Fixed-width addition and subtraction, both bigint-bigint and bigint-scalar. Results wrap
modulo 2^bit_width, so the carry or borrow out of the most significant limb is discarded and
a smaller minus a larger value gives the wrapped result rather than a negative one.
add_scalar(limb) and this + limb are two spellings of one function, as are
sub_scalar(limb) and this - limb: each operator calls the named function. Both behave
as the bigint forms with other equal to limb.
-
__device__ bigint bigint::operator+(const bigint &other) const#
result = (this + other) mod 2^bit_width
Multiplication#
The product of two bit_width-bit values needs 2 * bit_width bits. mul_wide,
operator*, and square return all of it as a bigint_wide, whose lo and
hi halves are exactly what mul_low and mul_high return individually. Multiplication
by a uint32_t scalar is fixed-width instead, wrapping modulo 2^bit_width like addition.
-
__device__ bigint bigint::mul_low(const bigint &other) const#
The low
bit_widthbits of the product. Costs about half ofmul_wide.result = (this * other) mod 2^bit_width
-
__device__ bigint bigint::mul_high(const bigint &other) const#
The high
bit_widthbits of the product. This is not faster than a fullmul_wide; if the low half is also required, usemul_wideinstead.result = (this * other) / 2^bit_width
-
__device__ bigint_wide bigint::mul_wide(const bigint &other) const#
-
__device__ bigint_wide bigint::operator*(const bigint &other) const#
The full
2 * bit_width-bit product, the only bigint-bigint multiply that keeps every bit instead of wrapping.a * banda.mul_wide(b)are the same function.operator*does nothing but callmul_wide, so the two are interchangeable and cost the same.The wide result is the usual input to a reduction step: pass it to
reduce_montgomery,reduce_barrett, or the double-widthdiv_remto bring it back to a single-width value modulo your modulus.result.lo = (this * other) mod 2^bit_width result.hi = (this * other) / 2^bit_width
-
__device__ bigint_wide bigint::square() const#
The full
2 * bit_width-bit square.squareis faster thanmul_wide(this)only forTPI == 1; for warp-executed instances (TPI > 1) the two cost the same.result.lo = (this * this) mod 2^bit_width result.hi = (this * this) / 2^bit_width
Division#
Division of unsigned values, producing a quotient and a remainder together. On success the two
outputs satisfy the division identity below, which is what pins them down; unless
bigint_error::success is returned, treat q and r as unspecified.
this = q * divisor + r with 0 <= r < divisor
- __device__ bigint_error bigint::div_rem( ) const#
Returns
bigint_error::success,bigint_error::divide_by_zero, orbigint_error::quotient_overflow.q = this / divisor r = this mod divisor
-
template<unsigned int DIVISOR_LIMBS, unsigned int QUOTIENT_LIMBS>
__device__ bigint_error bigint::div_rem( - const bigint_impl<DIVISOR_LIMBS, TPI, ErrorPolicy> &divisor,
- bigint_impl<QUOTIENT_LIMBS, TPI, ErrorPolicy> &q,
- bigint_impl<DIVISOR_LIMBS, TPI, ErrorPolicy> &r,
Division by a divisor narrower than the dividend: the remainder width follows the divisor, and the quotient width is chosen through the type of
q. Returns the same status codes as the same-width overload, withbigint_error::quotient_overflowreported when the true quotient needs more thanQUOTIENT_LIMBSlimbs.Precondition
QUOTIENT_LIMBSmust benum_limbsor half of it, enforced by astatic_assert.DIVISOR_LIMBSmust be half ofnum_limbs; that one is enforced only by which specializations the library provides, so an unsupported divisor width compiles and then fails to link. See Operand Widths.q = this / divisor q is QUOTIENT_LIMBS limbs wide r = this mod divisor r is DIVISOR_LIMBS limbs wide
-
__device__ bigint bigint::operator/(const bigint &divisor) const#
The quotient alone. Failures go through the configured on-error policy rather than coming back as a status (see Operators), and the returned value is zero.
result = this / divisor
-
template<unsigned int DIVISOR_LIMBS, unsigned int QUOTIENT_LIMBS>
__device__ bigint_error bigint_wide::div_rem( - const bigint_impl<DIVISOR_LIMBS, TPI, ErrorPolicy> &divisor,
- bigint_impl<QUOTIENT_LIMBS, TPI, ErrorPolicy> "ient,
- bigint_impl<DIVISOR_LIMBS, TPI, ErrorPolicy> &remainder,
Divides a double-width
bigint_widevalue bydivisor, so the dividend is the full2 * bit_width-bit value rather than a single-width one. This is the path that reduces amul_wideorsquareresult without Barrett or Montgomery setup.Precondition
DIVISOR_LIMBSandQUOTIENT_LIMBSmust both benum_limbs, that is, half the double-width dividend. Thestatic_assertalso admits a full2 * num_limbsquotient, but that specialization is provided only fornum_limbs == 8. See Operand Widths.quotient = this / divisor remainder = this mod divisor
Comparison#
Three-way comparison: returns a value less than, equal to, or greater than zero when
this is respectively less than, equal to, or greater than other. Values are compared as
unsigned magnitudes. Under warp execution every lane of the group receives the same result,
so it is safe to branch the whole group on it.
-
__device__ int bigint::compare(uint64_t other) const#
Semantics
result < 0 if this < other result == 0 if this == other result > 0 if this > other
Relational and equality operators built on compare(). Scalar operands must be uint32_t
or uint64_t, so write an unsigned literal (base == 0u) rather than a plain 0.
Each of the six also takes a uint32_t or uint64_t right-hand operand as a member
overload, with the same semantics against a scalar. The mirrored spellings with the scalar on
the left, such as 2u < x, are provided by free functions that swap the operands and call
the member back, so the sense of the comparison is preserved.
Bitwise and Shift#
Bitwise AND, OR, XOR, and complement over the full bit_width value, applied limb by limb.
The compound forms assign the result into this.
-
__device__ bigint bigint::operator&(const bigint &other) const#
result.limb[i] = this->limb[i] & other.limb[i]
-
__device__ bigint bigint::operator|(const bigint &other) const#
result.limb[i] = this->limb[i] | other.limb[i]
-
__device__ bigint bigint::operator^(const bigint &other) const#
result.limb[i] = this->limb[i] ^ other.limb[i]
-
__device__ bigint &bigint::operator&=(const bigint &other)#
this->limb[i] = this->limb[i] & other.limb[i]
-
__device__ bigint &bigint::operator|=(const bigint &other)#
this->limb[i] = this->limb[i] | other.limb[i]
-
__device__ bigint &bigint::operator^=(const bigint &other)#
this->limb[i] = this->limb[i] ^ other.limb[i]
Logical left and right shift by shift bits. Bits shifted out are discarded and no bits are
shifted back in at the opposite end. A shift of bit_width or more is well defined and yields
zero, rather than being undefined as it is for C++ built-in integers.
Modular Arithmetic#
Three routes to arithmetic modulo a modulus, differing in where the cost of reduction falls rather than in the results they produce. Direct needs no setup at all: one call in, a reduced result out. Barrett precomputes a reciprocal once per modulus, making repeated reductions against that modulus cheaper. Montgomery changes how values are represented so that multiplication needs no division at all, which pays off once several multiplications share a modulus. For choosing between them, see Choosing Modular Reduction.
Direct#
Single-call modular operations, each returning a result already reduced to the range
[0, modulus).
The two groups differ in what they expect of their inputs. add_mod and sub_mod require
both operands to be reduced already, which is what lets them bring the result back into range
with at most one correction by the modulus rather than a division. mod, mul_mod,
pow_mod, and inv_mod reduce as part of the operation and are correspondingly more
expensive.
-
__device__ bigint bigint::operator%(const bigint &modulus) const#
The remainder of the same division performed by
div_rem, with the quotient discarded.a.mod(b)anda % bare the same operation. A zero modulus yields zero and is reported through the configured on-error policy.result = this mod modulus
- __device__ bigint bigint::add_mod( ) const#
Precondition
this < modulusandother < modulus. This is not checked: with unreduced operands the result may fall outside[0, modulus)or be incorrect.result = (this + other) mod modulus
- __device__ bigint bigint::sub_mod( ) const#
Precondition
this < modulusandother < modulus. This is not checked: with unreduced operands the result may fall outside[0, modulus)or be incorrect.result = (this - other) mod modulus
- __device__ bigint bigint::mul_mod( ) const#
Computed as a
mul_widefollowed by the double-widthdiv_rem, keeping the remainder and discarding the quotient — the same reductionmodperforms, but on the2 * bit_width-bit product rather than a single-width value. This is not Montgomery multiplication; when multiplying repeatedly against one modulus, prefer the Montgomery path.Unlike
add_modandsub_mod, the operands need not be reduced beforehand. A zero modulus yields zero, reported through the configured on-error policy.result = (this * other) mod modulus
- __device__ bigint bigint::pow_mod( ) const#
Modular exponentiation by the right-to-left binary method. The base is reduced first, so it need not be reduced by the caller. A zero exponent yields
1 mod modulus, and a zero modulus yields zero, reported through the configured on-error policy.Warning
The loop bound depends on the value of the exponent, so
pow_modis not a constant-time implementation.result = this^exponent mod modulus
- __device__ bigint_error bigint::inv_mod( ) const#
The modular inverse. Supports both odd and even moduli, and both single-thread and warp-cooperative instances. Returns
bigint_error::success,bigint_error::inv_mod_not_invertible,bigint_error::inv_mod_invalid_modulus, orbigint_error::inv_mod_zero_input.Precondition
modulus > 1, andthis < moduluswhenmodulusis valid. Unlike the other preconditions in this section these are checked, and a violation is reported through the returned status rather than producing a silently wrong result.out = this^-1 mod modulus so that (this * out) mod modulus == 1
Barrett#
Reduction of a double-width value by a fixed modulus, split into a one-time setup_barrett
per modulus and a reduce_barrett per value.
- __device__ static void bigint::reduce_barrett(
- bigint &rem_out,
- const bigint_wide &num,
- const bigint &den,
- const bigint &approx,
- int den_clz,
Barrett-reduction helpers:
setup_barrettprecomputes a reciprocal approximation and leading-zero count for a fixed denominatorden, andreduce_barrettuses them to computenum mod denfor a double-widthnum, avoiding recomputation of the approximation across repeated reductions by the same denominator.Precondition
setup_barrettrequires a non-zeroden.reduce_barrettrequiresnum.hi < den, andden,approx, andden_clzmust all come from the samesetup_barrettcall.setup_barrett: approx_out, den_clz_out = reusable state derived from den reduce_barrett: rem_out = num mod den
approxandden_clzare an internal representation of the reciprocal, not a value to compute or interpret yourself; treat them as opaque state that is set up once per denominator and passed unchanged to every reduction against it.Both functions return
void, so unlikediv_remandinv_modthere is no status to inspect at the call site; errors are only reported through the configured on-error policy (see Operators):setup_barrettreportsbigint_error::barrett_divide_by_zero, leavingapprox_outandden_clz_outunspecified.reduce_barrettreportsbigint_error::barrett_input_invariant_violated, leavingrem_outunspecified.
Under the default
OnErrorNonea failure is therefore entirely silent, leaving a wrong value inapprox_outorrem_out; useOnErrorTraporOnErrorPrintTrapwhen it needs to be detectable.
Montgomery#
The Montgomery domain represents a value x as (x * R) mod modulus, where
R = 2^(num_limbs * 32). Multiplication in this representation needs no division, so the
cost of entering and leaving the domain is worth paying once several multiplications share a
modulus:
a_mont = a.to_montgomery(modulus)
b_mont = b.to_montgomery(modulus)
c_mont = a_mont.mul_montgomery(b_mont, modulus)
c = c_mont.from_montgomery(modulus) c == (a * b) mod modulus
Precondition
Every function in this section requires an odd modulus; see
montgomery_modulus for why. This is not
checked, and an even modulus yields incorrect results.
- __device__ bigint bigint::to_montgomery(
- const montgomery_modulus &modulus,
Converts into the Montgomery domain.
Precondition
this < modulus. This is not checked, and an unreduced input yields an incorrect result.result = (this * R) mod modulus
- __device__ bigint bigint::from_montgomery(
- const montgomery_modulus &modulus,
Converts out of the Montgomery domain (Montgomery reduction / REDC).
result = (this * R^-1) mod modulus
- __device__ bigint bigint::mul_montgomery(
- const bigint &other,
- const montgomery_modulus &modulus,
Montgomery multiplication. The result is in the Montgomery domain.
Precondition
Both
thisandothermust already be in the Montgomery domain, as produced byto_montgomeryor a previousmul_montgomery. This is not checked, and mixing domains yields an incorrect result.result = (this * other * R^-1) mod modulus
- __device__ bigint bigint_wide::reduce_montgomery(
- const montgomery_modulus &modulus,
Montgomery-reduces an already double-width value (for example the output of
mul_wideorsquare) directly againstmodulus, equivalent tofrom_montgomeryapplied to a double-width input without a preceding multiply.result = (this * R^-1) mod modulus this is 2 * bit_width bits wide