use crate::{
core::{
actually_used_field::ActuallyUsedField,
bounds::FieldBounds,
circuits::{
boolean::{
boolean_value::BooleanValue,
utils::{decoder_circuit, shift_right},
},
traits::arithmetic_circuit::ArithmeticCircuit,
},
expressions::expr::EvalFailure,
global_value::value::FieldValue,
},
traits::{FromLeBits, GetBit, Select},
types::DOUBLE_PRECISION_MANTISSA,
utils::{number::Number, used_field::UsedField},
};
use num_traits::ToPrimitive;
const LOG2_E: u64 = 6497320848556798;
const LN_2: u64 = 3121657384082679;
const PRECISION_MARGIN: usize = 4;
const CHEBYSHEV_COEFFS: [usize; 12] = [
104987905506995824,
35850444948458576,
3090774450775715,
178085167335115,
7703397532702,
266712640298,
7697462682,
190450582,
4123602,
79370,
1375,
22,
];
#[derive(Clone, Debug)]
pub struct Exp2 {
precision_in: usize,
precision_out: usize,
}
impl Exp2 {
#[allow(unused)]
pub const fn new(precision_in: usize) -> Self {
if precision_in > DOUBLE_PRECISION_MANTISSA {
panic!("precision_in must be at most 52");
}
Exp2 {
precision_in,
precision_out: DOUBLE_PRECISION_MANTISSA,
}
}
fn init_exp2<F: ActuallyUsedField>(&self, x: FieldValue<F>) -> (FieldValue<F>, FieldValue<F>) {
let bounds = x.bounds();
let bin_size = bounds.signed_bin_size();
let bits = (0..(self.precision_in + 1).max(bin_size))
.map(|i| x.get_bit(i, true))
.collect::<Vec<BooleanValue>>();
let frac_x = FieldValue::<F>::from_le_bits(
bits.iter()
.copied()
.take(self.precision_in)
.collect::<Vec<BooleanValue>>(),
false,
);
let (min, max) = bounds.to_signed_number_pair();
let min_int = min >> self.precision_in;
let max_int = max >> self.precision_in;
let two_pow_int_x = if min_int == max_int {
FieldValue::<F>::from(1)
} else {
let mut l = min_int.bits().max(max_int.bits());
if min_int == (-1 << (l - 1)) && max_int < (1 << (l - 1)) {
l -= 1;
}
let mut int_x = bits[self.precision_in..self.precision_in + l].to_vec();
let gap = (max_int.to_i32().unwrap() - min_int.to_i32().unwrap() + 1) as usize;
if min_int >= 0 {
let two_pow_int_x = decoder_circuit(int_x);
let start = min_int.to_usize().unwrap();
FieldValue::<F>::from_le_bits(two_pow_int_x[start..(start + gap)].to_vec(), false)
} else if max_int < 0 {
let two_pow_int_x = decoder_circuit(int_x);
let start = (Number::power_of_two(l) + min_int).to_usize().unwrap();
FieldValue::<F>::from_le_bits(two_pow_int_x[start..(start + gap)].to_vec(), false)
} else {
let sign = bits[self.precision_in + l];
int_x.push(!sign);
let two_pow_int_x = decoder_circuit(int_x);
let start = (Number::power_of_two(l) + min_int).to_usize().unwrap();
FieldValue::<F>::from_le_bits(two_pow_int_x[start..(start + gap)].to_vec(), false)
}
};
(two_pow_int_x, frac_x)
}
fn exp2_approx<F: ActuallyUsedField>(&self, frac_x: FieldValue<F>) -> FieldValue<F> {
let one =
FieldValue::<F>::from(Number::power_of_two(self.precision_out + PRECISION_MARGIN));
let z = FieldValue::from(F::power_of_two(
1 + (self.precision_out - self.precision_in) + PRECISION_MARGIN,
)) * frac_x
- one;
let mut chebyshev_polynomials = vec![one, z];
for i in 2..12 {
let last = chebyshev_polynomials[i - 1];
let second_last = chebyshev_polynomials[i - 2];
chebyshev_polynomials.push(
2 * shift_right(z * last, self.precision_out + PRECISION_MARGIN, true)
- second_last,
);
}
CHEBYSHEV_COEFFS
.into_iter()
.zip(chebyshev_polynomials)
.fold(FieldValue::<F>::from(0), |acc, (c, p)| {
acc + FieldValue::from(F::from(Number::from(c))) * p
})
>> (self.precision_out + 2 * PRECISION_MARGIN)
}
pub fn exp2<F: ActuallyUsedField>(&self, x: FieldValue<F>) -> FieldValue<F> {
let bounds = x.bounds();
let negative_limit =
F::from(self.precision_out as u64) * F::negative_power_of_two(self.precision_in);
let x_clamped_left = x
.signed_lt(FieldValue::from(negative_limit))
.select(FieldValue::from(negative_limit), x);
let positive_limit = self.exp2_positive_limit();
let x_clamped = x_clamped_left
.signed_gt(FieldValue::from(positive_limit))
.select(FieldValue::from(positive_limit), x_clamped_left)
.with_bounds(FieldBounds::new(negative_limit, positive_limit));
let (two_pow_int_x, frac_x) = self.init_exp2(x_clamped);
let approx_frac = self.exp2_approx(frac_x);
let min = x_clamped.bounds().signed_min().to_signed_number();
let min_int = min >> self.precision_in;
let mut res = two_pow_int_x * approx_frac;
if min_int > 0 {
res *= F::power_of_two(min_int.to_usize().unwrap())
} else {
res = res >> (-min_int).to_usize().unwrap()
}
res.with_bounds(self.exp2_bounds(bounds))
}
fn exp2_public<F: UsedField>(&self, x: F) -> F {
let x_float = f64::from(x.to_signed_number()) * 2f64.powi(-(self.precision_in as i32));
F::from(x_float.exp2())
}
fn exp2_positive_limit_number<F: UsedField>(&self) -> Number {
Number::from((F::NUM_BITS as usize - 2 * self.precision_out - 1) as u64)
* Number::power_of_two(self.precision_in)
}
fn exp2_positive_limit<F: UsedField>(&self) -> F {
F::from(self.exp2_positive_limit_number::<F>())
}
fn exp2_bounds<F: UsedField>(&self, bounds: FieldBounds<F>) -> FieldBounds<F> {
let (min, max) = bounds.min_and_max(true);
let negative_limit =
F::from(self.precision_out as u64) * F::negative_power_of_two(self.precision_in);
let clamped_min = (min - negative_limit)
.is_lt_zero()
.select(negative_limit, min);
let positive_limit: F = self.exp2_positive_limit();
let clamped_max = (max - positive_limit)
.is_gt_zero()
.select(positive_limit, max);
FieldBounds::new(
self.exp2_public(clamped_min) - self.eval_gap(&[clamped_min]),
self.exp2_public(clamped_max) + self.eval_gap(&[clamped_max]),
)
}
}
impl<F: UsedField> ArithmeticCircuit<F> for Exp2 {
fn eval(&self, x: Vec<F>) -> Result<Vec<F>, EvalFailure> {
if x.len() != 1 {
panic!("Exp2 requires one input")
}
let x = x[0];
if x.is_ge_zero() && x > self.exp2_positive_limit() {
return EvalFailure::err_ub("number too large for exp2");
}
Ok(vec![self.exp2_public(x)])
}
fn eval_gap(&self, x: &[F]) -> F {
let x = x[0];
if x.is_ge_zero() {
self.exp2_public(x) >> 46
} else {
F::from(3)
}
}
fn bounds(&self, bounds: Vec<FieldBounds<F>>) -> Vec<FieldBounds<F>> {
vec![self.exp2_bounds(bounds[0])]
}
fn run(&self, vals: Vec<FieldValue<F>>) -> Vec<FieldValue<F>>
where
F: ActuallyUsedField,
{
if vals.len() != 1 {
panic!("Exp2 requires one input")
}
vec![self.exp2(vals[0])]
}
}
#[derive(Clone, Debug)]
pub struct Exp {
precision_in: usize,
precision_out: usize,
}
impl Exp {
#[allow(unused)]
pub const fn new(precision_in: usize) -> Self {
if precision_in > DOUBLE_PRECISION_MANTISSA {
panic!("precision_in must be at most 52");
}
Exp {
precision_in,
precision_out: DOUBLE_PRECISION_MANTISSA,
}
}
pub fn exp<F: ActuallyUsedField>(&self, x: FieldValue<F>) -> FieldValue<F> {
let bounds = x.bounds();
let log2_e_x = shift_right(
FieldValue::from(F::from(LOG2_E)) * x,
self.precision_in,
true,
);
Exp2::new(self.precision_out)
.exp2(log2_e_x)
.with_bounds(self.exp_bounds(bounds))
}
fn exp_public<F: UsedField>(&self, x: F) -> F {
let x_float = f64::from(x.to_signed_number()) * 2f64.powi(-(self.precision_in as i32));
F::from(x_float.exp())
}
fn exp_positive_limit<F: UsedField>(&self) -> F {
F::from(
(Number::from(LN_2) * Exp2::new(self.precision_in).exp2_positive_limit_number::<F>())
>> self.precision_out,
)
}
fn exp_bounds<F: UsedField>(&self, bounds: FieldBounds<F>) -> FieldBounds<F> {
let (min, max) = bounds.min_and_max(true);
let negative_limit: F =
F::from(self.precision_out as u64) * F::negative_power_of_two(self.precision_in);
let positive_limit: F = self.exp_positive_limit();
let (clamped_min, clamped_max) =
if min.signed_gt(positive_limit) || max.signed_lt(negative_limit) {
(negative_limit, positive_limit)
} else {
(min.max(negative_limit, true), max.min(positive_limit, true))
};
FieldBounds::new(
self.exp_public(clamped_min) - self.eval_gap(&[clamped_min]),
self.exp_public(clamped_max) + self.eval_gap(&[clamped_max]),
)
}
}
impl<F: UsedField> ArithmeticCircuit<F> for Exp {
fn eval(&self, x: Vec<F>) -> Result<Vec<F>, EvalFailure> {
if x.len() != 1 {
panic!("Exp requires one input")
}
let x = x[0];
if x.is_ge_zero() && x > self.exp_positive_limit() {
return EvalFailure::err_ub("number too large for exp");
}
if x.is_lt_zero()
&& x <= F::negative_power_of_two(F::NUM_BITS as usize - self.precision_out - 3)
{
return EvalFailure::err_ub("number too small for exp");
}
Ok(vec![self.exp_public(x)])
}
fn eval_gap(&self, x: &[F]) -> F {
let x = x[0];
if x.is_ge_zero() {
self.exp_public(x) >> 44
} else {
F::from(3)
}
}
fn bounds(&self, bounds: Vec<FieldBounds<F>>) -> Vec<FieldBounds<F>> {
vec![self.exp_bounds(bounds[0])]
}
fn run(&self, vals: Vec<FieldValue<F>>) -> Vec<FieldValue<F>>
where
F: ActuallyUsedField,
{
if vals.len() != 1 {
panic!("Exp requires one input")
}
vec![self.exp(vals[0])]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
core::circuits::traits::arithmetic_circuit::tests::TestedArithmeticCircuit,
utils::field::ScalarField,
};
use rand::Rng;
impl TestedArithmeticCircuit<ScalarField> for Exp2 {
fn gen_desc<R: Rng + ?Sized>(rng: &mut R) -> Self {
let mut precision = 52;
while rng.gen_bool(0.5) {
precision -= 1;
}
Self::new(precision as usize)
}
fn gen_n_inputs<R: Rng + ?Sized>(&self, _rng: &mut R) -> usize {
1
}
fn input_precisions(&self) -> Vec<usize> {
vec![self.precision_in]
}
}
impl TestedArithmeticCircuit<ScalarField> for Exp {
fn gen_desc<R: Rng + ?Sized>(rng: &mut R) -> Self {
let mut precision = 52;
while rng.gen_bool(0.5) {
precision -= 1;
}
Self::new(precision.max(45) as usize)
}
fn gen_n_inputs<R: Rng + ?Sized>(&self, _rng: &mut R) -> usize {
1
}
fn input_precisions(&self) -> Vec<usize> {
vec![self.precision_in]
}
}
#[test]
fn tested_exp2() {
Exp2::test(16, 4)
}
#[test]
fn tested_exp() {
Exp::test(16, 4)
}
}