use std::mem::transmute;
use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Rem, Shl, Shr, Sub};
use std::simd::cmp::SimdPartialEq;
use std::simd::num::{SimdInt, SimdUint};
use std::simd::Simd;
use bytemuck::Zeroable;
use itertools::Itertools;
use stwo::core::fields::FieldExpOps;
use stwo::prover::backend::simd::conversion::{Pack, Unpack};
use stwo::prover::backend::simd::m31::{PackedM31, N_LANES};
use super::cpu::{
BigUInt, CasmState, Felt252, Felt252Width27, UInt16, UInt32, UInt64, FELT252WIDTH27_N_WORDS,
FELT252_N_WORDS, PRIME,
};
pub const P_BROADCAST: Simd<u32, N_LANES> = Simd::from_array([PRIME; N_LANES]);
pub const SIMD_ENUMERATION_0: Simd<u32, N_LANES> =
Simd::from_array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
pub trait PackedM31Type {
fn as_m31(&self) -> PackedM31;
}
#[derive(Clone, Copy, Debug)]
pub struct PackedBool {
pub(crate) value: Simd<i32, N_LANES>,
}
impl PackedBool {
pub fn from_m31(value: PackedM31) -> Self {
Self {
value: value.into_simd().cast().bitand(Simd::splat(1)),
}
}
}
impl PackedM31Type for PackedBool {
fn as_m31(&self) -> PackedM31 {
unsafe { PackedM31::from_simd_unchecked(self.value.cast()) }
}
}
impl BitAnd for PackedBool {
type Output = Self;
fn bitand(self, rhs: Self) -> Self::Output {
Self {
value: self.value & rhs.value,
}
}
}
#[derive(Copy, Clone, Debug, Default)]
pub struct PackedUInt16 {
value: Simd<u16, N_LANES>,
}
impl PackedUInt16 {
pub fn broadcast(value: UInt16) -> Self {
Self {
value: Simd::splat(value.value),
}
}
pub fn from_array(arr: [UInt16; N_LANES]) -> Self {
unsafe {
Self {
value: Simd::from_array(transmute::<[UInt16; 16], [u16; 16]>(arr)),
}
}
}
pub fn as_array(&self) -> [UInt16; N_LANES] {
unsafe { transmute(self.value.to_array()) }
}
pub fn from_m31(val: PackedM31) -> Self {
Self {
value: val.into_simd().cast(),
}
}
}
impl PackedM31Type for PackedUInt16 {
fn as_m31(&self) -> PackedM31 {
unsafe { PackedM31::from_simd_unchecked(self.value.cast()) }
}
}
impl Add for PackedUInt16 {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
value: self.value + rhs.value,
}
}
}
impl Rem for PackedUInt16 {
type Output = Self;
fn rem(self, rhs: Self) -> Self::Output {
Self {
value: self.value % rhs.value,
}
}
}
impl Shl for PackedUInt16 {
type Output = Self;
fn shl(self, rhs: Self) -> Self::Output {
Self {
value: self.value << rhs.value,
}
}
}
impl Shr for PackedUInt16 {
type Output = Self;
fn shr(self, rhs: Self) -> Self::Output {
Self {
value: self.value >> rhs.value,
}
}
}
impl BitAnd for PackedUInt16 {
type Output = Self;
fn bitand(self, rhs: Self) -> Self::Output {
Self {
value: self.value & rhs.value,
}
}
}
impl BitOr for PackedUInt16 {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
Self {
value: self.value | rhs.value,
}
}
}
impl BitXor for PackedUInt16 {
type Output = Self;
fn bitxor(self, rhs: Self) -> Self::Output {
Self {
value: self.value ^ rhs.value,
}
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct PackedUInt32 {
pub simd: Simd<u32, N_LANES>,
}
impl PackedUInt32 {
pub fn broadcast(value: UInt32) -> Self {
Self {
simd: Simd::splat(value.value),
}
}
pub fn from_array(arr: [UInt32; N_LANES]) -> Self {
unsafe {
Self {
simd: Simd::from_array(transmute::<[UInt32; 16], [u32; 16]>(arr)),
}
}
}
pub fn from_simd(value: Simd<u32, N_LANES>) -> Self {
Self { simd: value }
}
pub fn as_array(&self) -> [UInt32; N_LANES] {
unsafe { transmute(self.simd.to_array()) }
}
pub fn from_m31(val: PackedM31) -> Self {
Self {
simd: val.into_simd(),
}
}
pub fn low(&self) -> PackedUInt16 {
PackedUInt16 {
value: (self.simd & Simd::splat(0xFFFF)).cast(),
}
}
pub fn high(&self) -> PackedUInt16 {
PackedUInt16 {
value: (self.simd >> 16).cast(),
}
}
pub fn from_limbs([low, high]: [PackedM31; 2]) -> Self {
let [low, high] = [low, high].map(PackedM31::into_simd);
Self {
simd: low + (high << 16),
}
}
}
impl Rem for PackedUInt32 {
type Output = Self;
fn rem(self, rhs: Self) -> Self::Output {
Self {
simd: self.simd % rhs.simd,
}
}
}
impl Shl for PackedUInt32 {
type Output = Self;
fn shl(self, rhs: Self) -> Self::Output {
Self {
simd: self.simd << rhs.simd,
}
}
}
impl Shr for PackedUInt32 {
type Output = Self;
fn shr(self, rhs: Self) -> Self::Output {
Self {
simd: self.simd >> rhs.simd,
}
}
}
impl BitAnd for PackedUInt32 {
type Output = Self;
fn bitand(self, rhs: Self) -> Self::Output {
Self {
simd: self.simd & rhs.simd,
}
}
}
impl BitOr for PackedUInt32 {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
Self {
simd: self.simd | rhs.simd,
}
}
}
impl BitXor for PackedUInt32 {
type Output = Self;
fn bitxor(self, rhs: Self) -> Self::Output {
Self {
simd: self.simd ^ rhs.simd,
}
}
}
impl Add for PackedUInt32 {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
simd: self.simd + rhs.simd,
}
}
}
unsafe impl Zeroable for PackedUInt32 {
fn zeroed() -> Self {
Self {
simd: unsafe { core::mem::zeroed() },
}
}
}
impl Pack for UInt32 {
type SimdType = PackedUInt32;
fn pack(inputs: [UInt32; N_LANES]) -> Self::SimdType {
PackedUInt32::from_array(inputs)
}
}
impl Unpack for PackedUInt32 {
type CpuType = UInt32;
fn unpack(self) -> [Self::CpuType; N_LANES] {
self.as_array()
}
}
#[derive(Copy, Clone, Debug, Default)]
pub struct PackedUInt64 {
pub(crate) value: Simd<u64, N_LANES>,
}
impl PackedUInt64 {
pub fn broadcast(value: UInt64) -> Self {
Self {
value: Simd::splat(value.value),
}
}
pub fn from_array(arr: [UInt64; N_LANES]) -> Self {
unsafe {
Self {
value: Simd::from_array(transmute::<[UInt64; 16], [u64; 16]>(arr)),
}
}
}
pub fn as_array(&self) -> [UInt64; N_LANES] {
unsafe { transmute(self.value.to_array()) }
}
}
impl Add for PackedUInt64 {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
value: self.value + rhs.value,
}
}
}
impl Rem for PackedUInt64 {
type Output = Self;
fn rem(self, rhs: Self) -> Self::Output {
Self {
value: self.value % rhs.value,
}
}
}
impl Shl for PackedUInt64 {
type Output = Self;
fn shl(self, rhs: Self) -> Self::Output {
Self {
value: self.value << rhs.value,
}
}
}
impl Shr for PackedUInt64 {
type Output = Self;
fn shr(self, rhs: Self) -> Self::Output {
Self {
value: self.value >> rhs.value,
}
}
}
impl BitAnd for PackedUInt64 {
type Output = Self;
fn bitand(self, rhs: Self) -> Self::Output {
Self {
value: self.value & rhs.value,
}
}
}
impl BitOr for PackedUInt64 {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
Self {
value: self.value | rhs.value,
}
}
}
impl BitXor for PackedUInt64 {
type Output = Self;
fn bitxor(self, rhs: Self) -> Self::Output {
Self {
value: self.value ^ rhs.value,
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct PackedFelt252 {
pub value: [PackedM31; FELT252_N_WORDS],
}
impl PackedFelt252 {
pub fn get_m31(&self, index: usize) -> PackedM31 {
self.value[index]
}
pub fn from_limbs(limbs: [PackedM31; FELT252_N_WORDS]) -> Self {
Self { value: limbs }
}
pub fn from_m31(val: PackedM31) -> Self {
Self::from_array(&val.to_array().map(Felt252::from_m31))
}
pub fn to_array(&self) -> [Felt252; N_LANES] {
self.value
.unpack()
.each_ref()
.map(|v| Felt252::from_limbs(v))
}
pub fn broadcast(value: Felt252) -> Self {
Self::from_array(&[value; N_LANES])
}
pub fn from_array(arr: &[Felt252; N_LANES]) -> Self {
let limbs = arr.map(|felt| std::array::from_fn(|i| felt.get_m31(i)));
Self::from_limbs(<_ as Pack>::pack(limbs))
}
pub fn from_packed_felt252width27(packed27: PackedFelt252Width27) -> Self {
Self::from_array(&std::array::from_fn(|i| Felt252::from(packed27.value[i])))
}
}
impl Add for PackedFelt252 {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
let lhs = self.to_array();
let rhs = rhs.to_array();
let result = std::array::from_fn(|i| lhs[i] + rhs[i]);
Self::from_array(&result)
}
}
impl Sub for PackedFelt252 {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
let lhs = self.to_array();
let rhs = rhs.to_array();
let result = std::array::from_fn(|i| lhs[i] - rhs[i]);
Self::from_array(&result)
}
}
impl Mul for PackedFelt252 {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
let lhs = self.to_array();
let rhs = rhs.to_array();
let result = std::array::from_fn(|i| lhs[i] * rhs[i]);
Self::from_array(&result)
}
}
impl Div for PackedFelt252 {
type Output = Self;
fn div(self, rhs: Self) -> Self::Output {
let lhs = self.to_array();
let rhs = rhs.to_array();
let result = std::array::from_fn(|i| lhs[i] / rhs[i]);
Self::from_array(&result)
}
}
impl PartialEq for PackedFelt252 {
fn eq(&self, other: &Self) -> bool {
self.to_array() == other.to_array()
}
}
impl AsRef<[PackedM31; FELT252_N_WORDS]> for PackedFelt252 {
fn as_ref(&self) -> &[PackedM31; FELT252_N_WORDS] {
&self.value
}
}
pub trait EqExtend {
fn eq(&self, other: Self) -> PackedBool;
}
impl EqExtend for PackedM31 {
fn eq(&self, other: Self) -> PackedBool {
PackedBool {
value: self
.into_simd()
.simd_eq(other.into_simd())
.to_int()
.bitand(Simd::splat(1))
.cast(),
}
}
}
pub trait DivExtend {
fn div(&self, other: Self) -> Self;
}
impl DivExtend for PackedM31 {
fn div(&self, rhs: Self) -> Self {
*self * rhs.inverse()
}
}
#[derive(Clone, Copy)]
pub struct PackedFelt252Width27 {
value: [Felt252Width27; N_LANES],
}
impl PackedFelt252Width27 {
pub fn to_array(&self) -> [Felt252Width27; N_LANES] {
self.value
}
pub fn from_array(arr: [Felt252Width27; N_LANES]) -> Self {
Self { value: arr }
}
pub fn from_limbs(limbs: [PackedM31; FELT252WIDTH27_N_WORDS]) -> Self {
let limbs = limbs.unpack();
Self {
value: std::array::from_fn(|i| Felt252Width27::from_limbs(&limbs[i])),
}
}
pub fn from_packed_felt252width27(other: PackedFelt252Width27) -> Self {
Self { value: other.value }
}
pub fn from_packed_felt252(other: PackedFelt252) -> Self {
Self {
value: other.to_array().map(Felt252Width27::from),
}
}
pub fn get_m31(&self, index: usize) -> PackedM31 {
PackedM31::from_array(std::array::from_fn(|i| self.value[i].get_m31(index)))
}
}
impl Pack for Felt252Width27 {
type SimdType = PackedFelt252Width27;
fn pack(inputs: [Self; N_LANES]) -> Self::SimdType {
PackedFelt252Width27 { value: inputs }
}
}
impl Unpack for PackedFelt252Width27 {
type CpuType = Felt252Width27;
fn unpack(self) -> [Self::CpuType; N_LANES] {
self.value
}
}
#[derive(Copy, Clone, Debug)]
pub struct PackedBigUInt<const B: usize, const L: usize, const F: usize> {
value: [BigUInt<B, L, F>; N_LANES],
}
impl<const B: usize, const L: usize, const F: usize> PackedBigUInt<B, L, F> {
pub fn to_array(&self) -> [BigUInt<B, L, F>; N_LANES] {
self.value
}
pub fn broadcast(value: BigUInt<B, L, F>) -> Self {
Self {
value: [value; N_LANES],
}
}
pub fn from_packed_felt252_array(felts: &[PackedFelt252]) -> Self {
let felts = felts.iter().map(|felt| felt.to_array()).collect_vec();
let value = std::array::from_fn(|i| {
BigUInt::from_felt252_array(felts.iter().map(|felt| felt[i]).collect_vec())
});
Self { value }
}
pub fn from_packed_biguint<const OB: usize, const OL: usize, const OF: usize>(
other: PackedBigUInt<OB, OL, OF>,
) -> Self {
let result: [BigUInt<B, L, F>; N_LANES] =
std::array::from_fn(|i| BigUInt::<B, L, F>::from_biguint(other.value[i]));
Self { value: result }
}
pub fn widening_mul<const DB: usize, const DL: usize, const DF: usize>(
self,
rhs: PackedBigUInt<B, L, F>,
) -> PackedBigUInt<DB, DL, DF> {
let result = std::array::from_fn(|i| self.value[i].widening_mul(rhs.value[i]));
PackedBigUInt::<DB, DL, DF> { value: result }
}
pub fn get_m31(&self, index: usize) -> PackedM31 {
PackedM31::from_array(std::array::from_fn(|i| self.value[i].get_m31(index)))
}
}
impl<const B: usize, const L: usize, const F: usize> Add for PackedBigUInt<B, L, F> {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
let lhs = self.value;
let rhs = rhs.value;
let result = std::array::from_fn(|i| lhs[i] + rhs[i]);
Self { value: result }
}
}
impl<const B: usize, const L: usize, const F: usize> Sub for PackedBigUInt<B, L, F> {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
let lhs = self.value;
let rhs = rhs.value;
let result = std::array::from_fn(|i| lhs[i] - rhs[i]);
Self { value: result }
}
}
impl<const B: usize, const L: usize, const F: usize> Mul for PackedBigUInt<B, L, F> {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
let lhs = self.value;
let rhs = rhs.value;
let result = std::array::from_fn(|i| lhs[i] * rhs[i]);
Self { value: result }
}
}
impl<const B: usize, const L: usize, const F: usize> Div for PackedBigUInt<B, L, F> {
type Output = Self;
fn div(self, rhs: Self) -> Self {
let lhs = self.value;
let rhs = rhs.value;
let result = std::array::from_fn(|i| lhs[i].div(rhs[i]));
Self { value: result }
}
}
impl<const B: usize, const L: usize, const F: usize> EqExtend for PackedBigUInt<B, L, F> {
fn eq(&self, other: Self) -> PackedBool {
let lhs = self.value;
let rhs = other.value;
PackedBool {
value: Simd::from_array(std::array::from_fn(|i| lhs[i].eq(&rhs[i]) as i32)),
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct PackedCasmState {
pub pc: PackedM31,
pub ap: PackedM31,
pub fp: PackedM31,
}
impl Pack for CasmState {
type SimdType = PackedCasmState;
fn pack(inputs: [Self; N_LANES]) -> Self::SimdType {
PackedCasmState {
pc: PackedM31::from_array(std::array::from_fn(|i| inputs[i].pc)),
ap: PackedM31::from_array(std::array::from_fn(|i| inputs[i].ap)),
fp: PackedM31::from_array(std::array::from_fn(|i| inputs[i].fp)),
}
}
}
impl Unpack for PackedCasmState {
type CpuType = CasmState;
fn unpack(self) -> [Self::CpuType; N_LANES] {
let (pc, ap, fp) = (self.pc.to_array(), self.ap.to_array(), self.fp.to_array());
std::array::from_fn(|i| CasmState {
pc: pc[i],
ap: ap[i],
fp: fp[i],
})
}
}
impl Pack for Felt252 {
type SimdType = PackedFelt252;
fn pack(inputs: [Self; N_LANES]) -> Self::SimdType {
PackedFelt252::from_array(&inputs)
}
}
impl Unpack for PackedFelt252 {
type CpuType = Felt252;
fn unpack(self) -> [Self::CpuType; N_LANES] {
self.to_array()
}
}
#[cfg(test)]
mod tests {
use rand::rngs::SmallRng;
use rand::{Rng, SeedableRng};
use stwo::core::fields::m31::{M31, P};
type BigUInt384 = BigUInt<384, 6, 32>;
type PackedBigUInt384 = PackedBigUInt<384, 6, 32>;
type BigUInt768 = BigUInt<768, 12, 64>;
type PackedBigUInt768 = PackedBigUInt<768, 12, 64>;
use super::*;
fn rand_f252(rng: &mut SmallRng) -> Felt252 {
Felt252 {
limbs: [rng.gen(), rng.gen(), rng.gen(), 0],
}
}
fn rand_packed_f252(rng: &mut SmallRng) -> PackedFelt252 {
PackedFelt252::from_array(&std::array::from_fn(|_| rand_f252(rng)))
}
#[test]
fn test_f252_to_array() {
let mut rng = SmallRng::seed_from_u64(0u64);
let arr = std::array::from_fn(|_| rand_f252(&mut rng));
let a = PackedFelt252::from_array(&arr);
let unpacked_a = a.to_array();
for i in 0..N_LANES {
assert_eq!(arr[i], unpacked_a[i]);
}
}
#[test]
fn test_packed_f252_from_m31() {
let mut rng = SmallRng::seed_from_u64(0u64);
let input = std::array::from_fn(|_| M31(rng.gen::<u32>() % P));
let expected = input.map(Felt252::from_m31);
let packed_from = PackedFelt252::from_m31(PackedM31::from_array(input));
assert_eq!(packed_from.to_array(), expected);
}
#[test]
fn test_div_extend() {
let mut rng = SmallRng::seed_from_u64(0u64);
let a = std::array::from_fn(|_| M31(rng.gen::<u32>() % P));
let b = std::array::from_fn(|_| M31(rng.gen::<u32>() % P));
let expected_div = std::array::from_fn(|i| a[i] / b[i]);
let div_result = PackedM31::from_array(a).div(PackedM31::from_array(b));
assert_eq!(div_result.to_array(), expected_div);
}
#[test]
fn test_packed_f252_ops() {
let mut rng = SmallRng::seed_from_u64(0u64);
let a = rand_packed_f252(&mut rng);
let b = rand_packed_f252(&mut rng);
let unpacked_a = a.to_array();
let unpacked_b = b.to_array();
let expected_add = std::array::from_fn(|i| unpacked_a[i] + unpacked_b[i]);
let expected_sub = std::array::from_fn(|i| unpacked_a[i] - unpacked_b[i]);
let expected_mul = std::array::from_fn(|i| unpacked_a[i] * unpacked_b[i]);
let add = a + b;
let sub = a - b;
let mul = a * b;
assert_eq!(add.to_array(), expected_add);
assert_eq!(sub.to_array(), expected_sub);
assert_eq!(mul.to_array(), expected_mul);
}
#[test]
fn test_packed_biguint_from_packed_felt252_array() {
let mut rng = SmallRng::seed_from_u64(0u64);
let felts: Vec<Felt252> = (0..4).map(|_| rand_f252(&mut rng)).collect();
let big_uint = BigUInt384::from_felt252_array(felts.clone());
let packed_felts: Vec<PackedFelt252> = felts
.into_iter()
.map(|felt| PackedFelt252::from_array(&[felt; N_LANES]))
.collect_vec();
let packed_big_uint = PackedBigUInt384::from_packed_felt252_array(&packed_felts);
for i in 0..32 {
assert_eq!(packed_big_uint.to_array(), [big_uint; N_LANES], "i = {i}");
}
}
#[test]
fn test_packed_biguint_from_packed_biguint() {
let big_uint = BigUInt384::from_felt252(Felt252::from_limbs(&[
M31::from_u32_unchecked(32425),
M31::from_u32_unchecked(1429),
M31::from_u32_unchecked(243987),
M31::from_u32_unchecked(63),
M31::from_u32_unchecked(94753),
]));
let expected_packed_big_uint = PackedBigUInt384::broadcast(big_uint);
let packed_big_uint = PackedBigUInt384::from_packed_biguint(
PackedBigUInt768::from_packed_biguint(expected_packed_big_uint),
);
let packed_big_uint_2 = PackedBigUInt384::from_packed_biguint(PackedBigUInt768::broadcast(
BigUInt768::from_biguint(big_uint),
));
assert_eq!(
packed_big_uint.to_array(),
expected_packed_big_uint.to_array()
);
assert_eq!(
packed_big_uint_2.to_array(),
expected_packed_big_uint.to_array()
);
}
#[test]
fn test_packed_biguint_widening_mul() {
let big_uint = BigUInt384::from_felt252(Felt252::from_limbs(&[
M31::from_u32_unchecked(32425),
M31::from_u32_unchecked(1429),
M31::from_u32_unchecked(243987),
M31::from_u32_unchecked(63),
M31::from_u32_unchecked(94753),
]));
let packed_big_uint = PackedBigUInt384::broadcast(big_uint);
let result = packed_big_uint.widening_mul::<768, 12, 64>(packed_big_uint);
let expected_result = [big_uint.widening_mul::<768, 12, 64>(big_uint); N_LANES];
assert_eq!(result.to_array(), expected_result);
}
#[test]
fn test_packed_biguint_get_m31() {
let big_uint = BigUInt384::from_felt252(Felt252::from_limbs(&[
M31::from_u32_unchecked(32425),
M31::from_u32_unchecked(1429),
M31::from_u32_unchecked(243987),
M31::from_u32_unchecked(63),
M31::from_u32_unchecked(94753),
]));
let packed_big_uint = PackedBigUInt384::broadcast(big_uint);
for i in 0..32 {
assert_eq!(
packed_big_uint.get_m31(i).to_array(),
[big_uint.get_m31(i); N_LANES],
"i = {i}"
);
}
}
#[test]
fn test_packed_biguint_ops() {
let unpacked_a = BigUInt384::from_felt252(Felt252::from_limbs(&[
M31::from_u32_unchecked(32425),
M31::from_u32_unchecked(1429),
M31::from_u32_unchecked(243987),
M31::from_u32_unchecked(63),
M31::from_u32_unchecked(94753),
]));
let unpacked_b = BigUInt384::from_felt252(Felt252::from_limbs(&[
M31::from_u32_unchecked(34),
M31::from_u32_unchecked(2876),
M31::from_u32_unchecked(943),
M31::from_u32_unchecked(354832),
M31::from_u32_unchecked(86760),
]));
let a = PackedBigUInt384::broadcast(unpacked_a);
let b = PackedBigUInt384::broadcast(unpacked_b);
let expected_add = [unpacked_a + unpacked_b; N_LANES];
let expected_sub = [unpacked_a - unpacked_b; N_LANES];
let expected_mul = [unpacked_a * unpacked_b; N_LANES];
let expected_div = [unpacked_a.div(unpacked_b); N_LANES];
let add = a + b;
let sub = a - b;
let mul = a * b;
let div = a / b;
assert_eq!(add.to_array(), expected_add);
assert_eq!(sub.to_array(), expected_sub);
assert_eq!(mul.to_array(), expected_mul);
assert_eq!(div.to_array(), expected_div);
assert!(a.eq(b).value.to_array().iter().all(|&x| x == 0));
assert!(a.eq(a).value.to_array().iter().all(|&x| x == 1));
}
}