2 *****************************************************************************
4 Implementation of interfaces for multi-exponentiation routines.
8 *****************************************************************************
9 * @author This file is part of libff, developed by SCIPR Lab
10 * and contributors (see AUTHORS).
11 * @copyright MIT license (see LICENSE file)
12 *****************************************************************************/
19 #include <libff/algebra/curves/curve_serialization.hpp>
20 #include <libff/algebra/fields/bigint.hpp>
21 #include <libff/algebra/fields/fp_aux.tcc>
22 #include <libff/algebra/scalar_multiplication/multiexp.hpp>
23 #include <libff/algebra/scalar_multiplication/wnaf.hpp>
24 #include <libff/common/concurrent_fifo.hpp>
25 #include <libff/common/profiling.hpp>
26 #include <libff/common/utils.hpp>
27 #include <type_traits>
35 inline size_t pippenger_optimal_c(const size_t num_elements)
37 // empirically, this seems to be a decent estimate of the optimal value of c
38 const size_t log2_num_elements = log2(num_elements);
39 return log2_num_elements - (log2_num_elements / 3 - 2);
42 /// Add/subtract base_element to/from the correct bucket, based on a signed
43 /// digit, using and updating the bucket_hit flags. Supports regular / mixed
44 /// addition, based on base element form.
45 template<typename GroupT, multi_exp_base_form BaseForm>
46 void multi_exp_add_element_to_bucket_with_signed_digit(
47 std::vector<GroupT> &buckets,
48 std::vector<bool> &bucket_hit,
49 const GroupT &base_element,
53 const size_t bucket_idx = (-digit) - 1;
54 assert(bucket_idx < buckets.size());
55 if (bucket_hit[bucket_idx]) {
56 if (BaseForm == multi_exp_base_form_special) {
58 buckets[bucket_idx].mixed_add(-base_element);
60 buckets[bucket_idx] = buckets[bucket_idx].add(-base_element);
63 buckets[bucket_idx] = -base_element;
64 bucket_hit[bucket_idx] = true;
66 } else if (digit > 0) {
67 const size_t bucket_idx = digit - 1;
68 assert(bucket_idx < buckets.size());
69 if (bucket_hit[bucket_idx]) {
70 if (BaseForm == multi_exp_base_form_special) {
72 buckets[bucket_idx].mixed_add(base_element);
74 buckets[bucket_idx] = buckets[bucket_idx].add(base_element);
77 buckets[bucket_idx] = base_element;
78 bucket_hit[bucket_idx] = true;
84 /// \sum_{i=1}^{num_buckets} [i] B_i
86 /// B_i is the i-th bucket value (stored in buckets[i - 1])
88 /// Caller must ensure that at least one bucket is not empty (i.e.
89 /// bucket_hit[i] == true for at least one i).
90 template<typename GroupT, multi_exp_base_form Form>
91 GroupT multiexp_accumulate_buckets(
92 std::vector<GroupT> &buckets,
93 std::vector<bool> &bucket_hit,
94 const size_t num_buckets)
96 // Find first set bucket and initialize the accumulator. For each remaining
97 // buckets (set or unset), add the bucket value to the accumulator (if
98 // set), and add the accumulator to the sum. In this way, the i-th bucket
99 // will be added to sum exactly i times.
101 size_t i = num_buckets - 1;
102 while (!bucket_hit[i]) {
105 // Ensure that at least one bucket is initialized.
106 assert(i < num_buckets);
111 sum = accumulator = buckets[i];
115 if (Form == multi_exp_base_form_special) {
116 accumulator = accumulator.mixed_add(buckets[i]);
118 accumulator = accumulator + buckets[i];
121 sum = sum + accumulator;
127 template<mp_size_t n> class ordered_exponent
129 // to use std::push_heap and friends later
134 ordered_exponent(const size_t idx, const bigint<n> &r) : idx(idx), r(r){};
136 bool operator<(const ordered_exponent<n> &other) const
138 #if defined(__x86_64__) && defined(USE_ASM)
141 __asm__( // Preserve alignment
142 "// check for overflow \n\t" //
143 "mov $0, %[res] \n\t" //
148 "subtract%=: \n\t" //
149 "mov $1, %[res] \n\t" //
152 : [A] "r"(other.r.data), [mod] "r"(this->r.data)
157 __asm__( // Preserve alignment
158 "// check for overflow \n\t" //
159 "mov $0, %[res] \n\t" //
165 "subtract%=: \n\t" //
166 "mov $1, %[res] \n\t" //
169 : [A] "r"(other.r.data), [mod] "r"(this->r.data)
174 __asm__( // Preserve alignment
175 "// check for overflow \n\t" //
176 "mov $0, %[res] \n\t" //
183 "subtract%=: \n\t" //
184 "mov $1, %[res] \n\t" //
187 : [A] "r"(other.r.data), [mod] "r"(this->r.data)
193 return (mpn_cmp(this->r.data, other.r.data, n) < 0);
198 // Class holding the specialized multi exp implementations. Must implement a
199 // public static method of the form:
200 // static GroupT multi_exp_inner(
201 // typename std::vector<GroupT>::const_iterator bases,
202 // typename std::vector<GroupT>::const_iterator bases_end,
203 // typename std::vector<FieldT>::const_iterator exponents,
204 // typename std::vector<FieldT>::const_iterator exponents_end);
208 multi_exp_method Method,
209 multi_exp_base_form BaseForm>
210 class multi_exp_implementation
214 /// Naive multi_exp_implementation
215 template<typename GroupT, typename FieldT, multi_exp_base_form BaseForm>
216 class multi_exp_implementation<GroupT, FieldT, multi_exp_method_naive, BaseForm>
219 static GroupT multi_exp_inner(
220 typename std::vector<GroupT>::const_iterator vec_start,
221 typename std::vector<GroupT>::const_iterator vec_end,
222 typename std::vector<FieldT>::const_iterator scalar_start,
223 typename std::vector<FieldT>::const_iterator scalar_end)
225 GroupT result(GroupT::zero());
227 typename std::vector<GroupT>::const_iterator vec_it;
228 typename std::vector<FieldT>::const_iterator scalar_it;
230 for (vec_it = vec_start, scalar_it = scalar_start; vec_it != vec_end;
231 ++vec_it, ++scalar_it) {
232 bigint<FieldT::num_limbs> scalar_bigint = scalar_it->as_bigint();
234 result + opt_window_wnaf_exp(
235 *vec_it, scalar_bigint, scalar_bigint.num_bits());
237 assert(scalar_it == scalar_end);
244 // Naive plain multi_exp_implementation
245 template<typename GroupT, typename FieldT, multi_exp_base_form BaseForm>
246 class multi_exp_implementation<
249 multi_exp_method_naive_plain,
253 static GroupT multi_exp_inner(
254 typename std::vector<GroupT>::const_iterator vec_start,
255 typename std::vector<GroupT>::const_iterator vec_end,
256 typename std::vector<FieldT>::const_iterator scalar_start,
257 typename std::vector<FieldT>::const_iterator scalar_end)
259 GroupT result(GroupT::zero());
261 typename std::vector<GroupT>::const_iterator vec_it;
262 typename std::vector<FieldT>::const_iterator scalar_it;
264 for (vec_it = vec_start, scalar_it = scalar_start; vec_it != vec_end;
265 ++vec_it, ++scalar_it) {
266 result = result + (*scalar_it) * (*vec_it);
268 assert(scalar_it == scalar_end);
275 // multi_exp_implementation for BDLO12
276 template<typename GroupT, typename FieldT, multi_exp_base_form BaseForm>
277 class multi_exp_implementation<
280 multi_exp_method_BDLO12,
284 static GroupT multi_exp_inner(
285 typename std::vector<GroupT>::const_iterator bases,
286 typename std::vector<GroupT>::const_iterator bases_end,
287 typename std::vector<FieldT>::const_iterator exponents,
288 typename std::vector<FieldT>::const_iterator exponents_end)
290 UNUSED(exponents_end);
291 const size_t length = bases_end - bases;
292 const size_t c = internal::pippenger_optimal_c(length);
294 const mp_size_t exp_num_limbs =
295 std::remove_reference<decltype(*exponents)>::type::num_limbs;
296 std::vector<bigint<exp_num_limbs>> bi_exponents(length);
299 for (size_t i = 0; i < length; i++) {
300 bi_exponents[i] = exponents[i].as_bigint();
301 num_bits = std::max(num_bits, bi_exponents[i].num_bits());
304 const size_t num_groups = (num_bits + c - 1) / c;
307 bool result_nonzero = false;
309 for (size_t k = num_groups - 1; k <= num_groups; k--) {
310 if (result_nonzero) {
311 for (size_t i = 0; i < c; i++) {
312 result = result.dbl();
316 std::vector<GroupT> buckets(1 << c);
317 std::vector<bool> bucket_nonzero(1 << c);
319 for (size_t i = 0; i < length; i++) {
320 // id = k-th "digit" of bi_exponents[i], radix 2^c
321 // = (bi_exponents[i] >> (c*k)) & (2^c - 1)
323 for (size_t j = 0; j < c; j++) {
324 if (bi_exponents[i].test_bit(k * c + j)) {
334 // Add (or write) the group element into the appropriate bucket.
335 if (bucket_nonzero[id]) {
336 if (BaseForm == multi_exp_base_form_special) {
337 buckets[id] = buckets[id].mixed_add(bases[i]);
339 buckets[id] = buckets[id] + bases[i];
342 buckets[id] = bases[i];
343 bucket_nonzero[id] = true;
347 #ifdef USE_MIXED_ADDITION
348 batch_to_special(buckets);
352 bool running_sum_nonzero = false;
354 for (size_t i = (1u << c) - 1; i > 0; i--) {
355 if (bucket_nonzero[i]) {
356 if (running_sum_nonzero) {
357 #ifdef USE_MIXED_ADDITION
358 running_sum = running_sum.mixed_add(buckets[i]);
360 running_sum = running_sum + buckets[i];
363 running_sum = buckets[i];
364 running_sum_nonzero = true;
368 if (running_sum_nonzero) {
369 if (result_nonzero) {
370 result = result + running_sum;
372 result = running_sum;
373 result_nonzero = true;
383 template<typename GroupT, typename FieldT, multi_exp_base_form BaseForm>
384 class multi_exp_implementation<
387 multi_exp_method_bos_coster,
391 static GroupT multi_exp_inner(
392 typename std::vector<GroupT>::const_iterator vec_start,
393 typename std::vector<GroupT>::const_iterator vec_end,
394 typename std::vector<FieldT>::const_iterator scalar_start,
395 typename std::vector<FieldT>::const_iterator scalar_end)
398 std::remove_reference<decltype(*scalar_start)>::type::num_limbs;
400 if (vec_start == vec_end) {
401 return GroupT::zero();
404 if (vec_start + 1 == vec_end) {
405 return (*scalar_start) * (*vec_start);
408 std::vector<ordered_exponent<n>> opt_q;
409 const size_t vec_len = scalar_end - scalar_start;
410 const size_t odd_vec_len = (vec_len % 2 == 1 ? vec_len : vec_len + 1);
411 opt_q.reserve(odd_vec_len);
412 std::vector<GroupT> g;
413 g.reserve(odd_vec_len);
415 typename std::vector<GroupT>::const_iterator vec_it;
416 typename std::vector<FieldT>::const_iterator scalar_it;
418 for (i = 0, vec_it = vec_start, scalar_it = scalar_start;
420 ++vec_it, ++scalar_it, ++i) {
421 g.emplace_back(*vec_it);
423 opt_q.emplace_back(ordered_exponent<n>(i, scalar_it->as_bigint()));
425 std::make_heap(opt_q.begin(), opt_q.end());
426 assert(scalar_it == scalar_end);
428 if (vec_len != odd_vec_len) {
429 g.emplace_back(GroupT::zero());
431 ordered_exponent<n>(odd_vec_len - 1, bigint<n>(0ul)));
433 assert(g.size() % 2 == 1);
434 assert(opt_q.size() == g.size());
436 GroupT opt_result = GroupT::zero();
439 ordered_exponent<n> &a = opt_q[0];
440 ordered_exponent<n> &b =
441 (opt_q[1] < opt_q[2] ? opt_q[2] : opt_q[1]);
443 const size_t abits = a.r.num_bits();
446 // opt_result = opt_result + (a.r * g[a.idx]);
448 opt_result + opt_window_wnaf_exp(g[a.idx], a.r, abits);
452 const size_t bbits = b.r.num_bits();
453 const size_t limit = (abits - bbits >= 20 ? 20 : abits - bbits);
455 if (bbits < 1ul << limit) {
457 In this case, exponentiating to the power of a is cheaper than
458 subtracting b from a multiple times, so let's do it directly
460 // opt_result = opt_result + (a.r * g[a.idx]);
462 opt_result + opt_window_wnaf_exp(g[a.idx], a.r, abits);
465 "Skipping the following pair (%zu bit number vs %zu "
474 // x A + y B => (x-y) A + y (B+A)
475 mpn_sub_n(a.r.data, a.r.data, b.r.data, n);
476 g[b.idx] = g[b.idx] + g[a.idx];
479 // regardless of whether a was cleared or subtracted from we push it
480 // down, then take back up
484 while (2 * a_pos + 2 < odd_vec_len) {
485 // this is a max-heap so to maintain a heap property we swap
486 // with the largest of the two
487 if (opt_q[2 * a_pos + 1] < opt_q[2 * a_pos + 2]) {
488 std::swap(opt_q[a_pos], opt_q[2 * a_pos + 2]);
489 a_pos = 2 * a_pos + 2;
491 std::swap(opt_q[a_pos], opt_q[2 * a_pos + 1]);
492 a_pos = 2 * a_pos + 1;
496 /* now heapify A up appropriate amount of times */
497 while (a_pos > 0 && opt_q[(a_pos - 1) / 2] < opt_q[a_pos]) {
498 std::swap(opt_q[a_pos], opt_q[(a_pos - 1) / 2]);
499 a_pos = (a_pos - 1) / 2;
507 template<typename GroupT, typename FieldT, multi_exp_base_form BaseForm>
508 class multi_exp_implementation<
511 multi_exp_method_BDLO12_signed,
516 typename std::decay<decltype(((FieldT *)nullptr)->mont_repr)>::type;
518 /// buckets and bucket_hit should have at least 2^{c-1} entries.
519 static GroupT signed_digits_round(
520 typename std::vector<GroupT>::const_iterator bases,
521 typename std::vector<GroupT>::const_iterator bases_end,
522 typename std::vector<BigInt>::const_iterator exponents,
523 std::vector<GroupT> &buckets,
524 std::vector<bool> &bucket_hit,
525 const size_t num_entries,
526 const size_t num_buckets,
528 const size_t digit_idx)
532 assert(buckets.size() >= num_buckets);
533 assert(bucket_hit.size() >= num_buckets);
535 // Zero bucket_hit array.
536 bucket_hit.assign(num_buckets, false);
538 // For each scalar, element pair ...
540 for (size_t i = 0; i < num_entries; ++i) {
541 const ssize_t digit =
542 field_get_signed_digit(exponents[i], c, digit_idx);
547 multi_exp_add_element_to_bucket_with_signed_digit<GroupT, BaseForm>(
548 buckets, bucket_hit, bases[i], digit);
552 // Check up-front for the edge-case where no buckets have been touched.
554 return GroupT::zero();
557 // TODO: consider converting buckets to special form
559 return multiexp_accumulate_buckets<GroupT, multi_exp_base_form_normal>(
560 buckets, bucket_hit, num_buckets);
563 static GroupT multi_exp_inner(
564 typename std::vector<GroupT>::const_iterator bases,
565 typename std::vector<GroupT>::const_iterator bases_end,
566 typename std::vector<FieldT>::const_iterator exponents,
567 typename std::vector<FieldT>::const_iterator exponents_end)
569 UNUSED(exponents_end);
571 const size_t num_entries = bases_end - bases;
572 assert(exponents_end - exponents == (ssize_t)num_entries);
573 const size_t c = bdlo12_signed_optimal_c(num_entries);
576 // Pre-compute the bigint values
578 std::vector<BigInt> bi_exponents(num_entries);
579 for (size_t i = 0; i < num_entries; ++i) {
580 bi_exponents[i] = exponents[i].as_bigint();
581 num_bits = std::max(num_bits, bi_exponents[i].num_bits());
584 // Allow sufficient rounds for num_bits + 2, to accomodate overflow +
585 // negative final digit.
586 const size_t num_rounds = (num_bits + 2 + c - 1) / c;
588 // Digits have values between -(2^{c-1}) and 2^{c-1} - 1. Negative
589 // values are negated (to make them positive since we have cheap
590 // inversion of base elements), and 0 values are ignored. Hence we
591 // require up to 2^{c-1} buckets
592 const size_t num_buckets = 1 << (c - 1);
594 // Allocate the round state once, and reuse it.
595 std::vector<GroupT> buckets(num_buckets);
596 std::vector<bool> bucket_hit(num_buckets);
597 assert(buckets.size() == num_buckets);
598 assert(bucket_hit.size() == num_buckets);
600 // Compute from highest-order to lowest-order digits, accumulating at
602 GroupT result = signed_digits_round(
605 bi_exponents.begin(),
612 for (size_t round_idx = 1; round_idx < num_rounds; ++round_idx) {
613 const size_t digit_idx = num_rounds - 1 - round_idx;
614 for (size_t i = 0; i < c; ++i) {
615 result = result.dbl();
618 const GroupT round_result = signed_digits_round(
621 bi_exponents.begin(),
628 result = result + round_result;
635 } // namespace internal
637 static inline size_t bdlo12_signed_optimal_c(size_t num_entries)
639 // For now, this seems like a good estimate in most cases.
640 return internal::pippenger_optimal_c(num_entries) + 1;
646 multi_exp_method Method,
647 multi_exp_base_form BaseForm>
649 typename std::vector<GroupT>::const_iterator vec_start,
650 typename std::vector<GroupT>::const_iterator vec_end,
651 typename std::vector<FieldT>::const_iterator scalar_start,
652 typename std::vector<FieldT>::const_iterator scalar_end,
655 const size_t total = vec_end - vec_start;
656 if ((total < chunks) || (chunks == 1)) {
657 // no need to split into "chunks", can call implementation directly
659 multi_exp_implementation<GroupT, FieldT, Method, BaseForm>::
660 multi_exp_inner(vec_start, vec_end, scalar_start, scalar_end);
663 const size_t one = total / chunks;
665 std::vector<GroupT> partial(chunks, GroupT::zero());
668 #pragma omp parallel for
670 for (size_t i = 0; i < chunks; ++i) {
671 partial[i] = internal::
672 multi_exp_implementation<GroupT, FieldT, Method, BaseForm>::
675 (i == chunks - 1) ? vec_end : (vec_start + (i + 1) * one),
676 scalar_start + i * one,
677 (i == chunks - 1) ? scalar_end
678 : (scalar_start + (i + 1) * one));
681 GroupT final = GroupT::zero();
683 for (size_t i = 0; i < chunks; ++i) {
684 final = final + partial[i];
693 multi_exp_method Method,
694 multi_exp_base_form BaseForm>
695 GroupT multi_exp_filter_one_zero(
696 typename std::vector<GroupT>::const_iterator vec_start,
697 typename std::vector<GroupT>::const_iterator vec_end,
698 typename std::vector<FieldT>::const_iterator scalar_start,
699 typename std::vector<FieldT>::const_iterator scalar_end,
703 std::distance(vec_start, vec_end) ==
704 std::distance(scalar_start, scalar_end));
706 enter_block("Process scalar vector");
707 auto value_it = vec_start;
708 auto scalar_it = scalar_start;
710 std::vector<FieldT> p;
711 std::vector<GroupT> g;
713 GroupT acc = GroupT::zero();
717 size_t num_other = 0;
719 for (; scalar_it != scalar_end; ++scalar_it, ++value_it) {
720 if (scalar_it->is_zero()) {
723 } else if (*scalar_it == FieldT::one()) {
724 if (BaseForm == multi_exp_base_form_special) {
725 acc = acc.mixed_add(*value_it);
727 acc = acc + (*value_it);
731 p.emplace_back(*scalar_it);
732 g.emplace_back(*value_it);
739 "* Elements of w skipped: %zu (%0.2f%%)\n",
741 100. * num_skip / (num_skip + num_add + num_other));
744 "* Elements of w processed with special addition: %zu (%0.2f%%)\n",
746 100. * num_add / (num_skip + num_add + num_other));
749 "* Elements of w remaining: %zu (%0.2f%%)\n",
751 100. * num_other / (num_skip + num_add + num_other));
753 leave_block("Process scalar vector");
755 return acc + multi_exp<GroupT, FieldT, Method, BaseForm>(
756 g.begin(), g.end(), p.begin(), p.end(), chunks);
761 typename std::vector<T>::const_iterator a_start,
762 typename std::vector<T>::const_iterator a_end,
763 typename std::vector<T>::const_iterator b_start,
764 typename std::vector<T>::const_iterator b_end)
766 return multi_exp<T, T, multi_exp_method_naive_plain>(
767 a_start, a_end, b_start, b_end, 1);
770 template<typename T> size_t get_exp_window_size(const size_t num_scalars)
772 if (T::fixed_base_exp_window_table.empty()) {
780 for (long i = T::fixed_base_exp_window_table.size() - 1; i >= 0; --i) {
782 if (!inhibit_profiling_info) {
787 T::fixed_base_exp_window_table[i]);
790 if (T::fixed_base_exp_window_table[i] != 0 &&
791 num_scalars >= T::fixed_base_exp_window_table[i]) {
797 if (!inhibit_profiling_info) {
800 "Choosing window size %zu for %zu elements\n", window, num_scalars);
804 window = std::min((size_t)14, window);
810 window_table<T> get_window_table(
811 const size_t scalar_size, const size_t window, const T &g)
813 const size_t in_window = 1ul << window;
814 const size_t outerc = (scalar_size + window - 1) / window;
815 const size_t last_in_window = 1ul << (scalar_size - (outerc - 1) * window);
817 if (!inhibit_profiling_info) {
820 "* scalar_size=%zu; window=%zu; in_window=%zu; outerc=%zu\n",
828 window_table<T> powers_of_g(outerc, std::vector<T>(in_window, T::zero()));
832 for (size_t outer = 0; outer < outerc; ++outer) {
833 T ginner = T::zero();
834 size_t cur_in_window = outer == outerc - 1 ? last_in_window : in_window;
835 for (size_t inner = 0; inner < cur_in_window; ++inner) {
836 powers_of_g[outer][inner] = ginner;
837 ginner = ginner + gouter;
840 for (size_t i = 0; i < window; ++i) {
841 gouter = gouter + gouter;
848 template<typename T, typename FieldT>
850 const size_t scalar_size,
852 const window_table<T> &powers_of_g,
855 const size_t outerc = (scalar_size + window - 1) / window;
856 const bigint<FieldT::num_limbs> pow_val = pow.as_bigint();
859 T res = powers_of_g[0][0];
861 for (size_t outer = 0; outer < outerc; ++outer) {
863 for (size_t i = 0; i < window; ++i) {
864 if (pow_val.test_bit(outer * window + i)) {
869 res = res + powers_of_g[outer][inner];
875 template<typename T, typename FieldT>
876 std::vector<T> batch_exp(
877 const size_t scalar_size,
879 const window_table<T> &table,
880 const std::vector<FieldT> &v)
882 return batch_exp(scalar_size, window, table, v, v.size());
885 template<typename T, typename FieldT>
886 std::vector<T> batch_exp(
887 const size_t scalar_size,
889 const window_table<T> &table,
890 const std::vector<FieldT> &v,
893 if (!inhibit_profiling_info) {
896 std::vector<T> res(num_entries, table[0][0]);
899 #pragma omp parallel for
901 for (size_t i = 0; i < num_entries; ++i) {
902 res[i] = windowed_exp(scalar_size, window, table, v[i]);
904 if (!inhibit_profiling_info && (i % 10000 == 0)) {
910 if (!inhibit_profiling_info) {
917 template<typename T, typename FieldT>
918 std::vector<T> batch_exp_with_coeff(
919 const size_t scalar_size,
921 const window_table<T> &table,
923 const std::vector<FieldT> &v)
925 if (!inhibit_profiling_info) {
928 std::vector<T> res(v.size(), table[0][0]);
931 #pragma omp parallel for
933 for (size_t i = 0; i < v.size(); ++i) {
934 res[i] = windowed_exp(scalar_size, window, table, coeff * v[i]);
936 if (!inhibit_profiling_info && (i % 10000 == 0)) {
942 if (!inhibit_profiling_info) {
949 template<typename T> void batch_to_special(std::vector<T> &vec)
951 enter_block("Batch-convert elements to special form");
953 std::vector<T> non_zero_vec;
954 for (size_t i = 0; i < vec.size(); ++i) {
955 if (!vec[i].is_zero()) {
956 non_zero_vec.emplace_back(vec[i]);
960 T::batch_to_special_all_non_zeros(non_zero_vec);
961 auto it = non_zero_vec.begin();
962 T zero_special = T::zero();
963 zero_special.to_special();
965 for (size_t i = 0; i < vec.size(); ++i) {
966 if (!vec[i].is_zero()) {
970 vec[i] = zero_special;
973 leave_block("Batch-convert elements to special form");
978 #endif // MULTIEXP_TCC_