From d81764883373121c95bb25616da6a9e89dee72e8 Mon Sep 17 00:00:00 2001 From: Griffin Berlstein Date: Mon, 9 Sep 2024 14:16:39 -0400 Subject: [PATCH] [Cider] Replace `ibig` with `num-bigint` (#2279) A relatively minor set of documentation changes and some changes to the arbitrary precision numbers. Deceptively frustrating to implement but should be more compatible with future changes to `Value`. --- Cargo.lock | 25 +-- interp/Cargo.toml | 4 +- interp/src/flatten/flat_ir/base.rs | 27 ++- interp/src/flatten/flat_ir/identifier.rs | 10 +- .../src/flatten/primitives/stateful/math.rs | 18 +- interp/src/flatten/primitives/utils.rs | 28 +-- interp/src/flatten/setup.rs | 48 +++-- .../src/flatten/structures/environment/env.rs | 6 +- interp/src/flatten/structures/index_trait.rs | 3 +- interp/src/serialization/formatting.rs | 8 +- interp/src/structures/values.rs | 196 +++++++----------- interp/src/tests/values.rs | 68 ++++-- 12 files changed, 210 insertions(+), 231 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cdffa89544..af5521b32f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -230,12 +230,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - [[package]] name = "base64" version = "0.21.7" @@ -1516,19 +1510,6 @@ dependencies = [ "cc", ] -[[package]] -name = "ibig" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1fcc7f316b2c079dde77564a1360639c1a956a23fa96122732e416cb10717bb" -dependencies = [ - "cfg-if", - "num-traits", - "rand 0.8.5", - "serde", - "static_assertions", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -1614,7 +1595,6 @@ version = "0.1.1" dependencies = [ "ahash 0.8.10", "argh", - "base64 0.13.1", "bitvec", "btor2i", "calyx-frontend", @@ -1623,9 +1603,10 @@ dependencies = [ "calyx-utils", "ciborium", "fraction", - "ibig", "itertools 0.11.0", "lazy_static", + "num-bigint", + "num-traits", "once_cell", "owo-colors", "pest", @@ -2645,7 +2626,7 @@ version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15d167997bd841ec232f5b2b8e0e26606df2e7caa4c31b95ea9ca52b200bd270" dependencies = [ - "base64 0.21.7", + "base64", "chrono", "hex", "indexmap 1.9.3", diff --git a/interp/Cargo.toml b/interp/Cargo.toml index e76c9dc3b8..da6bddfabf 100644 --- a/interp/Cargo.toml +++ b/interp/Cargo.toml @@ -24,16 +24,16 @@ pest_consume.workspace = true argh.workspace = true owo-colors = "^3.5" bitvec = "1.0" -base64 = "0.13.0" serde_json = "1.0" rustyline = "=10.0.0" fraction = { version = "0.11.0", features = ["with-serde-support"] } thiserror = "1.0.26" -ibig = { version = "0.3.4", features = ["serde"] } slog = "2.7.0" slog-term = "2.8.0" slog-async = "2.7.0" ahash = "0.8.3" +num-bigint = "0.4.6" +num-traits = "0.2.19" once_cell = "1.9.0" petgraph = "0.6.3" diff --git a/interp/src/flatten/flat_ir/base.rs b/interp/src/flatten/flat_ir/base.rs index 08ae8969db..eda7f66191 100644 --- a/interp/src/flatten/flat_ir/base.rs +++ b/interp/src/flatten/flat_ir/base.rs @@ -85,18 +85,21 @@ impl_index!(GlobalRefPortIdx); // Offset indices -/// A local port offset for a component. These are used in the definition of -/// assignments and can only be understood in the context of the component they -/// are defined under. Combined with a base index from a component instance this -/// can be resolved to a [`GlobalPortIdx`]. +/// A local port offset for a component. +/// +/// These are used in the definition of assignments and can only be understood +/// in the context of the component they are defined under. Combined with a base +/// index from a component instance this can be resolved to a [`GlobalPortIdx`]. #[derive(Debug, Eq, Copy, Clone, PartialEq, Hash, PartialOrd, Ord)] pub struct LocalPortOffset(u32); impl_index!(LocalPortOffset); -/// A local ref port offset for a component. These are used in the definition of -/// assignments and can only be understood in the context of the component they -/// are defined under. Combined with a base index from a component instance this -/// can be resolved to a [`GlobalRefPortIdx`]. +/// A local ref port offset for a component. +/// +/// These are used in the definition of assignments and can only be understood +/// in the context of the component they are defined under. Combined with a base +/// index from a component instance this can be resolved to a +/// [`GlobalRefPortIdx`]. #[derive(Debug, Eq, Copy, Clone, PartialEq, Hash, PartialOrd, Ord)] pub struct LocalRefPortOffset(u32); impl_index!(LocalRefPortOffset); @@ -380,9 +383,11 @@ impl From for CellDefinitionRef { pub struct AssignmentIdx(u32); impl_index!(AssignmentIdx); -/// An enum representing the "winner" of an assignment. This tells us how the -/// value was assigned to the port. For standard assignments, this is also used -/// to detect conflicts where there are multiple driving assignments. +/// An enum representing the "winner" of an assignment. +/// +/// This tells us how the value was assigned to the port. For standard +/// assignments, this is also used to detect conflicts where there are multiple +/// driving assignments. #[derive(Debug, Clone, PartialEq, Eq)] pub enum AssignmentWinner { /// Indicates that the "winning" assignment for this port was produced by a diff --git a/interp/src/flatten/flat_ir/identifier.rs b/interp/src/flatten/flat_ir/identifier.rs index c20d15a1ae..ac5764cb3b 100644 --- a/interp/src/flatten/flat_ir/identifier.rs +++ b/interp/src/flatten/flat_ir/identifier.rs @@ -3,10 +3,12 @@ use std::hash::Hash; use crate::flatten::structures::index_trait::{impl_index, IndexRef}; -/// An index type corresponding to a string. Similar to [calyx_ir::Id] but -/// cannot be turned into a string directly. Strings are stored in the -/// interpretation context [crate::flatten::structures::context::Context] and -/// can be looked up via [crate::flatten::structures::context::Context::lookup_name] +/// An index type corresponding to a string. +/// +/// Similar to [calyx_ir::Id] but cannot be turned into a string directly. +/// Strings are stored in the interpretation context +/// [crate::flatten::structures::context::Context] and can be looked up via +/// [crate::flatten::structures::context::Context::lookup_name] #[derive(Debug, Eq, Copy, Clone, PartialEq, Hash, PartialOrd, Ord)] pub struct Identifier(u32); diff --git a/interp/src/flatten/primitives/stateful/math.rs b/interp/src/flatten/primitives/stateful/math.rs index 1b9b40ae87..d3daecf8fa 100644 --- a/interp/src/flatten/primitives/stateful/math.rs +++ b/interp/src/flatten/primitives/stateful/math.rs @@ -1,22 +1,16 @@ -use ibig::ops::RemEuclid; - use crate::{ flatten::{ flat_ir::prelude::*, primitives::{ - declare_ports, + declare_ports, ports, + prim_trait::*, utils::{floored_division, int_sqrt, ShiftBuffer}, }, - }, - values::InputNumber, -}; -use crate::{ - flatten::{ - primitives::{ports, prim_trait::*}, structures::environment::PortMap, }, - values::Value, + values::{InputNumber, Value}, }; +use num_traits::Euclid; pub struct StdMultPipe { base_port: GlobalPortIdx, @@ -199,7 +193,7 @@ impl Primitive (left .val() .as_unsigned() - .rem_euclid(right.val().as_unsigned())) + .rem_euclid(&right.val().as_unsigned())) .into() } else { (left.val().as_signed() @@ -530,7 +524,7 @@ impl Primitive (left .val() .as_unsigned() - .rem_euclid(right.val().as_unsigned())) + .rem_euclid(&right.val().as_unsigned())) .into() } else { (left.val().as_signed() diff --git a/interp/src/flatten/primitives/utils.rs b/interp/src/flatten/primitives/utils.rs index dce9c9da8d..41a5769375 100644 --- a/interp/src/flatten/primitives/utils.rs +++ b/interp/src/flatten/primitives/utils.rs @@ -1,14 +1,13 @@ +use num_bigint::{BigInt, BigUint, Sign}; use std::collections::VecDeque; -use ibig::{ibig, UBig}; -use ibig::{ubig, IBig}; - -pub(crate) fn floored_division(left: &IBig, right: &IBig) -> IBig { +/// There's probably a better way to do this but I'm not fixing it right now +pub(crate) fn floored_division(left: &BigInt, right: &BigInt) -> BigInt { let div = left / right; - if left.signum() != ibig!(-1) && right.signum() != ibig!(-1) { + if left.sign() != Sign::Minus && right.sign() != Sign::Minus { div - } else if (div.signum() == (-1).into() || div.signum() == 0.into()) + } else if (div.sign() == Sign::Minus || div.sign() == Sign::NoSign) && (left != &(&div * right)) { div - 1_i32 @@ -18,14 +17,17 @@ pub(crate) fn floored_division(left: &IBig, right: &IBig) -> IBig { } /// Implementation of integer square root via a basic binary search algorithm -/// based on wikipedia psuedocode -pub(crate) fn int_sqrt(i: &UBig) -> UBig { - let mut lower: UBig = ubig!(0); - let mut upper: UBig = i + ubig!(1); - let mut temp: UBig; +/// based on wikipedia pseudocode. +/// +/// TODO griffin: See if this can be replaced with a `sqrt` function in the +/// `num-bigint` crate +pub(crate) fn int_sqrt(i: &BigUint) -> BigUint { + let mut lower: BigUint = BigUint::from(0_u32); + let mut upper: BigUint = i + BigUint::from(1_u32); + let mut temp: BigUint; - while lower != (&upper - ubig!(1)) { - temp = (&lower + &upper) / ubig!(2); + while lower != (&upper - BigUint::from(1_u32)) { + temp = (&lower + &upper) / BigUint::from(2_u32); if &(&temp * &temp) <= i { lower = temp } else { diff --git a/interp/src/flatten/setup.rs b/interp/src/flatten/setup.rs index 08b8e6638d..72b99e0ee1 100644 --- a/interp/src/flatten/setup.rs +++ b/interp/src/flatten/setup.rs @@ -14,6 +14,7 @@ fn do_setup( file: &Option, lib_path: &Path, skip_verification: bool, + gen_metadata: bool, ) -> InterpreterResult<(Context, InterpreterResult)> { // Construct IR let ws = frontend::Workspace::construct(file, lib_path)?; @@ -23,42 +24,53 @@ fn do_setup( if !skip_verification { pm.execute_plan(&mut ctx, &["validate".to_string()], &[], &[], false)?; } - pm.execute_plan( - &mut ctx, - &["metadata-table-generation".to_string()], - &[], - &[], - false, - )?; - let mapping = ctx - .metadata - .as_ref() - .map(|metadata| { - crate::debugger::source::new_parser::parse_metadata(metadata) - }) - .unwrap_or_else(|| { - Err(crate::errors::InterpreterError::MissingMetaData.into()) - }); + let mapping = if gen_metadata { + pm.execute_plan( + &mut ctx, + &["metadata-table-generation".to_string()], + &[], + &[], + false, + )?; + ctx.metadata + .as_ref() + .map(|metadata| { + crate::debugger::source::new_parser::parse_metadata(metadata) + }) + .unwrap_or_else(|| { + Err(crate::errors::InterpreterError::MissingMetaData.into()) + }) + } else { + Err(crate::errors::InterpreterError::MissingMetaData.into()) + }; // general setup Ok((crate::flatten::flat_ir::translate(&ctx), mapping)) } +/// This function sets up the simulation context for the given program. This is +/// meant to be used in contexts where calyx metadata is not required. For other +/// cases, use [setup_simulation_with_metadata] pub fn setup_simulation( file: &Option, lib_path: &Path, skip_verification: bool, ) -> InterpreterResult { - let (ctx, _) = do_setup(file, lib_path, skip_verification)?; + let (ctx, _) = do_setup(file, lib_path, skip_verification, false)?; Ok(ctx) } +/// Constructs the simulation context for the given program. Additionally +/// attempts to construct the metadata table for the program. +/// +/// For cases where the metadata is not required, use [setup_simulation], which +/// has less of a performance impact. pub fn setup_simulation_with_metadata( file: &Option, lib_path: &Path, skip_verification: bool, ) -> InterpreterResult<(Context, NewSourceMap)> { - let (ctx, mapping) = do_setup(file, lib_path, skip_verification)?; + let (ctx, mapping) = do_setup(file, lib_path, skip_verification, true)?; Ok((ctx, mapping?)) } diff --git a/interp/src/flatten/structures/environment/env.rs b/interp/src/flatten/structures/environment/env.rs index 98e3c96bea..a02470e9b6 100644 --- a/interp/src/flatten/structures/environment/env.rs +++ b/interp/src/flatten/structures/environment/env.rs @@ -1188,8 +1188,10 @@ impl + Clone> Environment { } /// A wrapper struct for the environment that provides the functions used to -/// simulate the actual program. This is just to keep the simulation logic under -/// a different namespace than the environment to avoid confusion +/// simulate the actual program. +/// +/// This is just to keep the simulation logic under a different namespace than +/// the environment to avoid confusion pub struct Simulator + Clone> { env: Environment, } diff --git a/interp/src/flatten/structures/index_trait.rs b/interp/src/flatten/structures/index_trait.rs index b816bad239..09f2588538 100644 --- a/interp/src/flatten/structures/index_trait.rs +++ b/interp/src/flatten/structures/index_trait.rs @@ -242,7 +242,8 @@ where (size, Some(size)) } } - +/// An iterator over a range of indices but without +/// /// Because I really played myself by making the [IndexRangeIterator] have a /// lifetime attached to it. This one doesn't do that. As with it's sibling, the /// range is half open, meaning that the start is inclusive, but the end is diff --git a/interp/src/serialization/formatting.rs b/interp/src/serialization/formatting.rs index 345afd7701..b529b8b678 100644 --- a/interp/src/serialization/formatting.rs +++ b/interp/src/serialization/formatting.rs @@ -96,9 +96,11 @@ impl From<&MemoryDimensions> for Shape { } } -/// A wrapper enum used during serialization. It represents either an unsigned integer, -/// or a signed integer and is serialized as the underlying integer. This also allows -/// mixed serialization of signed and unsigned values +/// A wrapper enum used during serialization. +/// +/// It represents either an unsigned integer, or a signed integer and is +/// serialized as the underlying integer. This also allows mixed serialization +/// of signed and unsigned values #[derive(Serialize, Clone)] #[serde(untagged)] pub enum Entry { diff --git a/interp/src/structures/values.rs b/interp/src/structures/values.rs index 1f77ef3fe4..a370b50242 100644 --- a/interp/src/structures/values.rs +++ b/interp/src/structures/values.rs @@ -1,11 +1,10 @@ -use std::ops::Not; +use std::iter::once; use std::{fmt::Write, ops::Index}; use bitvec::prelude::*; use fraction::Fraction; -use ibig::{ibig, ops::UnsignedAbs, IBig, UBig}; use itertools::Itertools; -use serde::de::{self, Deserialize, Visitor}; +use num_bigint::{BigInt, BigUint}; use serde::Serialize; pub type BitString = BitVec; @@ -59,14 +58,14 @@ pub enum InputNumber { U32(u32), U64(u64), U128(u128), - U(UBig), + U(BigUint), // signed I8(i8), I16(i16), I32(i32), I64(i64), I128(i128), - I(IBig), + I(BigInt), // usize Usize(usize), } @@ -127,14 +126,14 @@ impl From for InputNumber { } } -impl From for InputNumber { - fn from(u: UBig) -> Self { +impl From for InputNumber { + fn from(u: BigUint) -> Self { Self::U(u) } } -impl From for InputNumber { - fn from(i: IBig) -> Self { +impl From for InputNumber { + fn from(i: BigInt) -> Self { Self::I(i) } } @@ -193,62 +192,51 @@ impl InputNumber { InputNumber::Usize(i) => BitVec::from_element(*i), InputNumber::U(u) => { let bytes_64: Vec<_> = u - .to_le_bytes() - .into_iter() - .chunks(8) - .into_iter() + .iter_u64_digits() .map(|x| { - let mut acc: usize = 0; - for (byte_number, u) in x.enumerate() { - acc |= (u as usize) << (byte_number * 8) - } - acc + x.try_into().expect("u64 to usize conversion failed") }) .collect(); BitString::from_slice(&bytes_64) } - InputNumber::I(i) => { - if i.signum() == ibig!(-1) { - let mut carry = true; - // manually do the twos complement conversion - let fun: Vec<_> = i - .unsigned_abs() - .to_le_bytes() - .into_iter() - .chunks(8) - .into_iter() - .map(|x| { - let mut acc: usize = 0; - for (byte_number, u) in x.enumerate() { - acc |= (u as usize) << (byte_number * 8) - } - acc = acc.not(); - - if carry { - let (new_acc, new_carry) = - acc.overflowing_add(1); - carry = new_carry; - acc = new_acc; - } - acc + InputNumber::I(i) => match i.sign() { + num_bigint::Sign::Minus => { + let signed_bytes_le = i.to_signed_bytes_le(); + let chunks = signed_bytes_le.chunks_exact(8); + let rem = chunks.remainder(); + let mut bv: BitString = chunks + .map(|bytes| { + let mut array = [0_u8; 8]; + array.copy_from_slice(bytes); + usize::from_le_bytes(array) }) + .chain(once({ + // in the case of even division this will add an + // extra + let mut array = [0_u8; 8]; + array[..rem.len()].copy_from_slice(rem); + usize::from_le_bytes(array) + })) .collect(); - - let mut bv = BitVec::from_slice(&fun); - - if carry { - bv.push(true) - } - - bv.truncate(bv.last_one().unwrap() + 1); + // this truncation is _critically_ important for getting the + // correct result + bv.truncate(bv.last_one().map(|x| x + 1).unwrap_or(0)); bv - } else { - let unsigned: InputNumber = i.unsigned_abs().into(); - unsigned.as_bit_vec() } - } + num_bigint::Sign::NoSign | num_bigint::Sign::Plus => i + .magnitude() + .to_u64_digits() + .into_iter() + .map(|x| { + let x: usize = x + .try_into() + .expect("u64 to usize conversion failed"); + x + }) + .collect(), + }, } } } @@ -586,37 +574,46 @@ impl Value { }) } - pub fn as_signed(&self) -> IBig { - let mut acc: IBig = 0.into(); + fn as_bytes_le(&self) -> Vec { + self.vec + .iter() + .chunks(8) + .into_iter() + .map(|bits| { + let mut out = 0_u8; + for (idx, bit) in bits.enumerate() { + out |= (*bit as u8) << idx; + } + out + }) + .collect_vec() + } - // skip the msb for the moment - for bit in self.vec.iter().take(self.vec.len() - 1).rev() { - acc <<= 1; - let bit: IBig = (*bit).into(); - acc |= bit - } + pub fn as_signed(&self) -> BigInt { + let mut bytes = self.as_bytes_le(); + let width_bits = self.vec.len(); - if let Some(bit) = self.vec.last() { - if *bit { - let neg: IBig = (-1).into(); - let two: IBig = (2).into(); + let rem = width_bits % 8; + let msb = 1u8 << if rem != 0 { rem - 1 } else { 7 }; - acc += neg * two.pow(self.vec.len() - 1) + if (bytes.last().unwrap() & msb) != 0 { + // The value is negative + if msb != 0b1000_0000 { + let mask = 0b1111_1111u8 << if rem != 0 { rem - 1 } else { 7 }; + *(bytes.last_mut().unwrap()) |= mask; } + } else { + // The value is positive. No additional work needed } - acc + BigInt::from_signed_bytes_le(&bytes) } - pub fn as_unsigned(&self) -> UBig { - let mut acc: UBig = 0_u32.into(); - for bit in self.vec.iter().rev() { - acc <<= 1; - let bit: UBig = (*bit).into(); - acc |= bit; - } + pub fn as_unsigned(&self) -> BigUint { + let vec = self.as_bytes_le(); + // don't need to sign extend here - acc + BigUint::from_bytes_le(&vec) } /// Interprets a 1bit value as a bool, will not panic for non-1-bit values @@ -699,17 +696,7 @@ impl Value { } pub fn to_bytes(&self) -> Vec { - // there has got to be a better way to do this - self.vec - .chunks(8) - .map(|bits| { - let mut byte = 0_u8; - for (i, bit) in bits.iter().enumerate() { - byte |= (*bit as u8) << i; - } - byte - }) - .collect() + self.as_bytes_le() } } @@ -778,39 +765,8 @@ impl Serialize for Value { where S: serde::Serializer, { - base64::encode(self.as_unsigned().to_le_bytes()).serialize(serializer) - } -} - -impl<'de> Deserialize<'de> for Value { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct BitVecVisitor; - - impl<'de> Visitor<'de> for BitVecVisitor { - type Value = (UBig, usize); - - fn expecting( - &self, - formatter: &mut std::fmt::Formatter, - ) -> std::fmt::Result { - formatter.write_str("Expected bitstring") - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - let s = base64::decode(value) - .expect("Couldn't convert from base64"); - Ok((UBig::from_le_bytes(&s), s.len())) - } - } - - let (val, bytes) = deserializer.deserialize_str(BitVecVisitor)?; - Ok(Value::from(val, bytes * 8)) + // this is stupid + self.to_bytes().serialize(serializer) } } diff --git a/interp/src/tests/values.rs b/interp/src/tests/values.rs index c7e715e301..16afd5c4bf 100644 --- a/interp/src/tests/values.rs +++ b/interp/src/tests/values.rs @@ -239,89 +239,111 @@ mod signed_fixed_point_tests { #[cfg(test)] mod property_tests { use crate::values::Value; - use ibig::{ops::UnsignedAbs, IBig, UBig}; + use num_bigint::{BigInt, BigUint}; use proptest::prelude::*; proptest! { + #![proptest_config(ProptestConfig::with_cases(1000))] + + #[test] fn u8_round_trip(input: u8) { - assert_eq!(input as u64, Value::from(input, 8).as_u64()) + prop_assert_eq!(input as u64, Value::from(input, 8).as_u64()) } #[test] fn u16_round_trip(input: u16) { - assert_eq!(input as u64, Value::from(input, 16).as_u64()) + prop_assert_eq!(input as u64, Value::from(input, 16).as_u64()) } #[test] fn u32_round_trip(input: u32) { - assert_eq!(input as u64, Value::from(input, 32).as_u64()) + prop_assert_eq!(input as u64, Value::from(input, 32).as_u64()) } #[test] fn u64_round_trip(input: u64) { - assert_eq!(input, Value::from(input, 64).as_u64()) + prop_assert_eq!(input, Value::from(input, 64).as_u64()) } #[test] fn u128_round_trip(input: u128) { - assert_eq!(input, Value::from(input, 128).as_u128()) + prop_assert_eq!(input, Value::from(input, 128).as_u128()) } #[test] fn i8_round_trip(input: i8) { - assert_eq!(input as i64, Value::from(input, 8).as_i64()) + prop_assert_eq!(input as i64, Value::from(input, 8).as_i64()) } #[test] fn i16_round_trip(input: i16) { - assert_eq!(input as i64, Value::from(input, 16).as_i64()) + prop_assert_eq!(input as i64, Value::from(input, 16).as_i64()) } #[test] fn i32_round_trip(input: i32) { - assert_eq!(input as i64, Value::from(input, 32).as_i64()) + prop_assert_eq!(input as i64, Value::from(input, 32).as_i64()) } #[test] fn i64_round_trip(input: i64) { - assert_eq!(input, Value::from(input, 64).as_i64()) + prop_assert_eq!(input, Value::from(input, 64).as_i64()) } #[test] fn i128_round_trip(input: i128) { - assert_eq!(input, Value::from(input, 128).as_i128()) + prop_assert_eq!(input, Value::from(input, 128).as_i128()) } #[test] fn i128_to_ibig(input: i128) { let val = Value::from(input, 128); - assert_eq!(val.as_signed(), input.into()) + prop_assert_eq!(val.as_signed(), input.into()) } #[test] fn u128_to_ubig(input: u128) { let val = Value::from(input, 128); - assert_eq!(val.as_unsigned(), input.into()) + prop_assert_eq!(val.as_unsigned(), input.into()) + } + + + #[test] + fn u32_to_ubig(input: u32) { + let val = Value::from(input, 32); + prop_assert_eq!(val.as_unsigned(), input.into()) + } + + #[test] + fn i32_to_ibig(input: i32) { + let val = Value::from(input, 32); + prop_assert_eq!(val.as_signed(), input.into()) + } + #[test] + fn ibig_to_ibig_neg(input in -350_i32..350_i32) { + dbg!(input); + let val = Value::from(BigInt::from(input), 32); + prop_assert_eq!(val.as_signed(), BigInt::from(input)) } #[test] fn ubig_roundtrip(input: u128, mul: u128) { - let in_big: UBig = input.into(); - let mul_big: UBig = mul.into(); - let target: UBig = in_big * mul_big; - let val = Value::from(target.clone(), target.bit_len()); - assert_eq!(val.as_unsigned(), target) + let in_big: BigUint = input.into(); + let mul_big: BigUint = mul.into(); + let target: BigUint = in_big * mul_big; + let val = Value::from(target.clone(), target.bits()); + prop_assert_eq!(val.as_unsigned(), target) } #[test] fn ibig_roundtrip(input: i128, mul: i128) { - let in_big: IBig = input.into(); - let mul_big: IBig = mul.into(); - let target: IBig = in_big * mul_big; - let val = Value::from(target.clone(), (&target).unsigned_abs().bit_len()+1); + let in_big: BigInt = input.into(); + let mul_big: BigInt = mul.into(); + let target: BigInt = in_big * mul_big; + let val = Value::from(target.clone(), target.magnitude().bits() + 1); println!("{}", val); - assert_eq!(val.as_signed(), target) + prop_assert_eq!(val.as_signed(), target) } } }