From b485f4c9e4eec0b2b5c49d084da21e9058e2b3e4 Mon Sep 17 00:00:00 2001 From: arvidn Date: Fri, 29 Oct 2021 15:10:02 +0200 Subject: [PATCH] use libgmp for bignum instead of num-bigint --- Cargo.lock | 44 +--- Cargo.toml | 6 +- src/gen/conditions.rs | 4 +- src/more_ops.rs | 36 ++- src/number.rs | 545 ++++++++++++++++++++++++++++++++++++++---- src/test_ops.rs | 3 +- 6 files changed, 535 insertions(+), 103 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3aa5bf68..40aa86d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -83,11 +83,9 @@ name = "clvm_rs" version = "0.1.15" dependencies = [ "bls12_381", + "gmp-mpfr-sys", "hex", "lazy_static", - "num-bigint", - "num-integer", - "num-traits", "openssl", "pyo3", "sha2", @@ -165,6 +163,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "gmp-mpfr-sys" +version = "1.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a146a7357ce9573bdcc416fc4a99b960e166e72d8eaffa7c59966d51866b5bfb" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "group" version = "0.10.0" @@ -254,36 +262,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "num-bigint" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0d047c1062aa51e256408c560894e5251f08925980e53cf1aa5bd00eec6512" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" -dependencies = [ - "autocfg", -] - [[package]] name = "once_cell" version = "1.8.0" diff --git a/Cargo.toml b/Cargo.toml index 24d25a14..cb1b4cd0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,13 +23,13 @@ default = ["extension-module"] [dependencies] hex = "=0.4.3" lazy_static = "=1.4.0" -num-bigint = "=0.4.0" -num-traits = "=0.2.14" -num-integer = "=0.1.44" bls12_381 = "=0.5.0" sha2 = "=0.9.5" openssl = { version = "0.10.35", features = ["vendored"], optional = true } +# we just want the GMP bindings, so we disable default features +gmp-mpfr-sys = { version = "=1.4", default-features = false } + [target.'cfg(target_family="wasm")'.dependencies] wasm-bindgen = "=0.2.75" wasm-bindgen-test = "=0.3.25" diff --git a/src/gen/conditions.rs b/src/gen/conditions.rs index cc1ae6f8..370f064b 100644 --- a/src/gen/conditions.rs +++ b/src/gen/conditions.rs @@ -555,8 +555,6 @@ use crate::serialize::node_to_bytes; #[cfg(test)] use hex::FromHex; #[cfg(test)] -use num_traits::Num; -#[cfg(test)] use std::collections::HashMap; #[cfg(test)] @@ -689,7 +687,7 @@ fn parse_list_impl( (a.new_atom(&buf).unwrap(), v.len() + 1) } else if input.starts_with("-") || "0123456789".contains(input.get(0..1).unwrap()) { let v = input.split_once(" ").unwrap().0; - let num = Number::from_str_radix(v, 10).unwrap(); + let num = Number::from_str_radix(v, 10); (ptr_from_number(a, &num).unwrap(), v.len() + 1) } else { panic!("atom not supported \"{}\"", input); diff --git a/src/more_ops.rs b/src/more_ops.rs index 740664cd..7c556afd 100644 --- a/src/more_ops.rs +++ b/src/more_ops.rs @@ -1,7 +1,4 @@ use bls12_381::{G1Affine, G1Projective, Scalar}; -use num_bigint::{BigUint, Sign}; -use num_integer::Integer; -use std::convert::TryFrom; use std::ops::BitAndAssign; use std::ops::BitOrAssign; use std::ops::BitXorAssign; @@ -12,7 +9,7 @@ use crate::allocator::{Allocator, NodePtr, SExp}; use crate::cost::{check_cost, Cost}; use crate::err_utils::err; use crate::node::Node; -use crate::number::{number_from_u8, ptr_from_number, Number}; +use crate::number::{number_from_u8, ptr_from_number, Number, Sign}; use crate::op_utils::{ arg_count, atom, check_arg_count, i32_atom, int_atom, two_ints, u32_from_u8, }; @@ -354,7 +351,7 @@ pub fn op_sha256(a: &mut Allocator, input: NodePtr, max_cost: Cost) -> Response pub fn op_add(a: &mut Allocator, input: NodePtr, max_cost: Cost) -> Response { let mut cost = ARITH_BASE_COST; let mut byte_count: usize = 0; - let mut total: Number = 0.into(); + let mut total = Number::zero(); for arg in Node::new(a, input) { cost += ARITH_COST_PER_ARG; check_cost( @@ -365,7 +362,7 @@ pub fn op_add(a: &mut Allocator, input: NodePtr, max_cost: Cost) -> Response { let blob = int_atom(&arg, "+")?; let v: Number = number_from_u8(blob); byte_count += blob.len(); - total += v; + total += &v; } let total = ptr_from_number(a, &total)?; cost += byte_count as Cost * ARITH_COST_PER_BYTE; @@ -375,7 +372,7 @@ pub fn op_add(a: &mut Allocator, input: NodePtr, max_cost: Cost) -> Response { pub fn op_subtract(a: &mut Allocator, input: NodePtr, max_cost: Cost) -> Response { let mut cost = ARITH_BASE_COST; let mut byte_count: usize = 0; - let mut total: Number = 0.into(); + let mut total = Number::zero(); let mut is_first = true; for arg in Node::new(a, input) { cost += ARITH_COST_PER_ARG; @@ -384,9 +381,9 @@ pub fn op_subtract(a: &mut Allocator, input: NodePtr, max_cost: Cost) -> Respons let v: Number = number_from_u8(blob); byte_count += blob.len(); if is_first { - total += v; + total += &v; } else { - total -= v; + total -= &v; }; is_first = false; } @@ -434,7 +431,7 @@ pub fn op_div(a: &mut Allocator, input: NodePtr, _max_cost: Cost) -> Response { // this is to preserve a buggy behavior from the initial implementation // of this operator. - if q == (-1).into() && r != 0.into() { + if q == -1 && r != 0 { q += 1; } let q1 = ptr_from_number(a, &q)?; @@ -641,7 +638,7 @@ pub fn op_lsh(a: &mut Allocator, input: NodePtr, _max_cost: Cost) -> Response { check_arg_count(&args, 2, "lsh")?; let a0 = args.first()?; let b0 = int_atom(&a0, "lsh")?; - let i0 = BigUint::from_bytes_be(b0); + let i0 = Number::from_unsigned_bytes_be(b0); let l0 = b0.len(); let rest = args.rest()?; let a1 = i32_atom(&rest.first()?, "lsh")?; @@ -649,8 +646,6 @@ pub fn op_lsh(a: &mut Allocator, input: NodePtr, _max_cost: Cost) -> Response { return args.rest()?.first()?.err("shift too large"); } - let i0: Number = i0.into(); - let v: Number = if a1 > 0 { i0 << a1 } else { i0 >> -a1 }; let l1 = limbs_for_int(&v); @@ -739,7 +734,7 @@ fn logior_op(a: &mut Number, b: &Number) { } pub fn op_logior(a: &mut Allocator, input: NodePtr, max_cost: Cost) -> Response { - let v: Number = (0).into(); + let v = Number::zero(); binop_reduction("logior", a, v, input, max_cost, logior_op) } @@ -748,7 +743,7 @@ fn logxor_op(a: &mut Number, b: &Number) { } pub fn op_logxor(a: &mut Allocator, input: NodePtr, max_cost: Cost) -> Response { - let v: Number = (0).into(); + let v = Number::zero(); binop_reduction("logxor", a, v, input, max_cost, logxor_op) } @@ -804,10 +799,10 @@ pub fn op_softfork(a: &mut Allocator, input: NodePtr, max_cost: Cost) -> Respons Some((p1, _)) => { let n: Number = number_from_u8(int_atom(&p1, "softfork")?); if n.sign() == Sign::Plus { - if n > Number::from(max_cost) { + if n > max_cost { return err(a.null(), "cost exceeded"); } - let cost: Cost = TryFrom::try_from(&n).unwrap(); + let cost: Cost = n.into(); Ok(Reduction(cost, args.null().node)) } else { args.err("cost must be > 0") @@ -824,14 +819,13 @@ lazy_static! { 0xd8, 0x05, 0x53, 0xbd, 0xa4, 0x02, 0xff, 0xfe, 0x5b, 0xfe, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, ]; - let n = BigUint::from_bytes_be(order_as_bytes); - n.into() + Number::from_unsigned_bytes_be(order_as_bytes) }; } fn mod_group_order(n: Number) -> Number { - let order = GROUP_ORDER.clone(); - let mut remainder = n.mod_floor(&order); + let order: &Number = &GROUP_ORDER; + let mut remainder = n.mod_floor(order); if remainder.sign() == Sign::Minus { remainder += order; } diff --git a/src/number.rs b/src/number.rs index b66a5df1..de57f6ff 100644 --- a/src/number.rs +++ b/src/number.rs @@ -1,22 +1,440 @@ use crate::allocator::{Allocator, NodePtr}; use crate::node::Node; use crate::reduction::EvalErr; +use core::mem::MaybeUninit; +use gmp_mpfr_sys::gmp; +use std::cmp::PartialOrd; +use std::ffi::c_void; +use std::ops::Drop; +use std::ops::{ + AddAssign, BitAndAssign, BitOrAssign, BitXorAssign, MulAssign, Not, Shl, Shr, SubAssign, +}; + +use std::cmp::Ordering; + +#[allow(clippy::enum_variant_names)] +#[derive(PartialEq)] +pub enum Sign { + Minus, + NoSign, + Plus, +} -use num_bigint::BigInt; -pub type Number = BigInt; +pub struct Number { + v: gmp::mpz_t, +} -pub fn ptr_from_number(allocator: &mut Allocator, item: &Number) -> Result { - let bytes: Vec = item.to_signed_bytes_be(); - let mut slice = bytes.as_slice(); +impl Number { + pub fn zero() -> Number { + let mut v = MaybeUninit::::uninit(); + unsafe { + gmp::mpz_init(v.as_mut_ptr()); + } + Number { + v: unsafe { v.assume_init() }, + } + } + + pub fn from_signed_bytes_be(v: &[u8]) -> Number { + let mut ret = Number::zero(); + if v.is_empty() { + return ret; + } + // mpz_import() only reads unsigned values + let negative = (v[0] & 0x80) != 0; + + if negative { + // since the bytes we read are two's complement + // if the most significant bit was set, we need to + // convert the value to a negative one. We do this by flipping + // all bits, adding one and then negating it. + let mut v = v.to_vec(); + for digit in &mut v { + *digit = !*digit; + } + unsafe { + gmp::mpz_import(&mut ret.v, v.len(), 1, 1, 0, 0, v.as_ptr() as *const c_void); + gmp::mpz_add_ui(&mut ret.v, &ret.v, 1); + gmp::mpz_neg(&mut ret.v, &ret.v); + } + } else { + unsafe { + gmp::mpz_import(&mut ret.v, v.len(), 1, 1, 0, 0, v.as_ptr() as *const c_void); + } + } + ret + } + + pub fn from_unsigned_bytes_be(v: &[u8]) -> Number { + let mut ret = Number::zero(); + if !v.is_empty() { + unsafe { + gmp::mpz_import(&mut ret.v, v.len(), 1, 1, 0, 0, v.as_ptr() as *const c_void); + } + } + ret + } + + pub fn to_signed_bytes_be(&self) -> Vec { + let size = (self.bits() + 7) / 8; + let mut ret: Vec = Vec::new(); + if size == 0 { + return ret; + } + ret.resize(size + 1, 0); + let sign = self.sign(); + if sign == Sign::Minus { + // If the value is negative, we need to convert it to two's + // complement. We can't do that in-place. + let mut out_size: usize = size; + unsafe { + gmp::mpz_export( + ret.as_mut_slice()[1..].as_mut_ptr() as *mut c_void, + &mut out_size, + 1, + 1, + 0, + 0, + &self.v, + ); + } + let mut carry = true; + for digit in &mut ret.iter_mut().rev() { + let res = (!*digit).overflowing_add(carry as u8); + *digit = res.0; + carry = res.1; + } + assert!(!carry); + assert!(out_size == ret.len() - 1); + assert!(ret[0] & 0x80 != 0); + if (ret[1] & 0x80) != 0 { + ret.remove(0); + } + } else { + let mut out_size: usize = size; + unsafe { + gmp::mpz_export( + ret.as_mut_slice()[1..].as_mut_ptr() as *mut c_void, + &mut out_size, + 1, + 1, + 0, + 0, + &self.v, + ); + } + // apparently mpz_export prints 0 bytes to the buffer if the value is 0 + // hence the special case in the assert below. + assert!(out_size == ret.len() - 1); + if ret[1] & 0x80 == 0 { + ret.remove(0); + } + } + ret + } + + pub fn to_bytes_le(&self) -> (Sign, Vec) { + let sgn = self.sign(); + + let size = (self.bits() + 7) / 8; + let mut ret: Vec = Vec::new(); + if size == 0 { + return (Sign::NoSign, ret); + } + ret.resize(size, 0); + + let mut out_size: usize = size; + unsafe { + gmp::mpz_export( + ret.as_mut_ptr() as *mut c_void, + &mut out_size, + -1, + 1, + 0, + 0, + &self.v, + ); + } + assert_eq!(out_size, ret.len()); + (sgn, ret) + } + + pub fn bits(&self) -> usize { + // GnuMP says that any integer needs at least 1 bit to be represented. + // but we say 0 requires 0 bits + if self.sign() == Sign::NoSign { + 0 + } else { + unsafe { gmp::mpz_sizeinbase(&self.v, 2) } + } + } + + pub fn sign(&self) -> Sign { + match unsafe { gmp::mpz_sgn(&self.v) } { + -1 => Sign::Minus, + 0 => Sign::NoSign, + 1 => Sign::Plus, + _ => { + panic!("unexpected mpz_sign() return value"); + } + } + } + + // returns the quotient and remained, from dividing self with denominator + pub fn div_mod_floor(&self, denominator: &Number) -> (Number, Number) { + let mut q = Number::zero(); + let mut r = Number::zero(); + unsafe { + gmp::mpz_fdiv_qr(&mut q.v, &mut r.v, &self.v, &denominator.v); + } + (q, r) + } + + pub fn mod_floor(&self, denominator: &Number) -> Number { + let mut r = Number::zero(); + unsafe { + gmp::mpz_fdiv_r(&mut r.v, &self.v, &denominator.v); + } + r + } + + pub fn div_floor(&self, denominator: &Number) -> Number { + let mut ret = Number::zero(); + unsafe { + gmp::mpz_fdiv_q(&mut ret.v, &self.v, &denominator.v); + } + ret + } +} + +impl Drop for Number { + fn drop(&mut self) { + unsafe { + gmp::mpz_clear(&mut self.v); + } + } +} + +// Addition + +impl AddAssign<&Number> for Number { + fn add_assign(&mut self, other: &Self) { + unsafe { + gmp::mpz_add(&mut self.v, &self.v, &other.v); + } + } +} + +// This is only here for op_div() +impl AddAssign for Number { + fn add_assign(&mut self, other: u64) { + unsafe { + gmp::mpz_add_ui(&mut self.v, &self.v, other); + } + } +} + +// Subtraction + +impl SubAssign<&Number> for Number { + fn sub_assign(&mut self, other: &Self) { + unsafe { + gmp::mpz_sub(&mut self.v, &self.v, &other.v); + } + } +} + +// Multiplication + +impl MulAssign for Number { + fn mul_assign(&mut self, other: Self) { + unsafe { + gmp::mpz_mul(&mut self.v, &self.v, &other.v); + } + } +} + +// Shift + +impl Shl for Number { + type Output = Self; + fn shl(mut self, n: i32) -> Self { + assert!(n >= 0); + unsafe { + gmp::mpz_mul_2exp(&mut self.v, &self.v, n as u64); + } + self + } +} + +impl Shr for Number { + type Output = Self; + fn shr(mut self, n: i32) -> Self { + assert!(n >= 0); + unsafe { + gmp::mpz_fdiv_q_2exp(&mut self.v, &self.v, n as u64); + } + self + } +} + +// Conversion + +impl From for Number { + fn from(other: i64) -> Self { + let mut v = MaybeUninit::::uninit(); + unsafe { + gmp::mpz_init_set_si(v.as_mut_ptr(), other); + } + Number { + v: unsafe { v.assume_init() }, + } + } +} + +impl From for Number { + fn from(other: i32) -> Self { + let mut v = MaybeUninit::::uninit(); + unsafe { + gmp::mpz_init_set_si(v.as_mut_ptr(), other as i64); + } + Number { + v: unsafe { v.assume_init() }, + } + } +} + +impl From for Number { + fn from(other: u64) -> Self { + let mut v = MaybeUninit::::uninit(); + unsafe { + gmp::mpz_init_set_ui(v.as_mut_ptr(), other); + } + Number { + v: unsafe { v.assume_init() }, + } + } +} + +impl From for Number { + fn from(other: usize) -> Self { + let mut v = MaybeUninit::::uninit(); + unsafe { + gmp::mpz_init_set_ui(v.as_mut_ptr(), other as u64); + } + Number { + v: unsafe { v.assume_init() }, + } + } +} - // make number minimal by removing leading zeros - while (!slice.is_empty()) && (slice[0] == 0) { - if slice.len() > 1 && (slice[1] & 0x80 == 0x80) { - break; +impl From for u64 { + fn from(n: Number) -> u64 { + unsafe { + assert!(gmp::mpz_sizeinbase(&n.v, 2) <= 64); + assert!(gmp::mpz_sgn(&n.v) >= 0); + gmp::mpz_get_ui(&n.v) } - slice = &slice[1..]; } - allocator.new_atom(slice) +} + +impl From for i64 { + fn from(n: Number) -> i64 { + unsafe { + assert!(gmp::mpz_sizeinbase(&n.v, 2) <= 64); + gmp::mpz_get_si(&n.v) + } + } +} + +// Bit operations + +impl BitXorAssign<&Number> for Number { + fn bitxor_assign(&mut self, other: &Self) { + unsafe { + gmp::mpz_xor(&mut self.v, &self.v, &other.v); + } + } +} + +impl BitOrAssign<&Number> for Number { + fn bitor_assign(&mut self, other: &Self) { + unsafe { + gmp::mpz_ior(&mut self.v, &self.v, &other.v); + } + } +} + +impl BitAndAssign<&Number> for Number { + fn bitand_assign(&mut self, other: &Self) { + unsafe { + gmp::mpz_and(&mut self.v, &self.v, &other.v); + } + } +} + +impl Not for Number { + type Output = Self; + fn not(self) -> Self { + let mut ret = Number::zero(); + unsafe { + gmp::mpz_com(&mut ret.v, &self.v); + } + ret + } +} + +// Comparisons + +impl PartialEq for Number { + fn eq(&self, other: &Self) -> bool { + unsafe { gmp::mpz_cmp(&self.v, &other.v) == 0 } + } +} + +impl PartialEq for Number { + fn eq(&self, other: &u64) -> bool { + unsafe { gmp::mpz_cmp_ui(&self.v, *other) == 0 } + } +} + +impl PartialEq for Number { + fn eq(&self, other: &i64) -> bool { + unsafe { gmp::mpz_cmp_si(&self.v, *other) == 0 } + } +} + +impl PartialEq for Number { + fn eq(&self, other: &i32) -> bool { + unsafe { gmp::mpz_cmp_si(&self.v, *other as i64) == 0 } + } +} + +fn ord_helper(r: i32) -> Option { + match r { + d if d < 0 => Some(Ordering::Less), + d if d > 0 => Some(Ordering::Greater), + _ => Some(Ordering::Equal), + } +} + +impl PartialOrd for Number { + fn partial_cmp(&self, other: &Number) -> Option { + ord_helper(unsafe { gmp::mpz_cmp(&self.v, &other.v) }) + } +} + +impl PartialOrd for Number { + fn partial_cmp(&self, other: &u64) -> Option { + ord_helper(unsafe { gmp::mpz_cmp_ui(&self.v, *other) }) + } +} + +unsafe impl Sync for Number {} + +pub fn ptr_from_number(allocator: &mut Allocator, item: &Number) -> Result { + let bytes: Vec = item.to_signed_bytes_be(); + allocator.new_atom(bytes.as_slice()) } impl From<&Node<'_>> for Option { @@ -27,11 +445,48 @@ impl From<&Node<'_>> for Option { } pub fn number_from_u8(v: &[u8]) -> Number { - let len = v.len(); - if len == 0 { - 0.into() - } else { - Number::from_signed_bytes_be(v) + Number::from_signed_bytes_be(v) +} + +// ==== TESTS ==== + +#[cfg(test)] +use std::ffi::{CStr, CString}; +#[cfg(test)] +use std::fmt; + +#[cfg(test)] +impl Number { + pub fn from_str_radix(mut s: &str, radix: i32) -> Number { + let negative = s.get(0..1).unwrap() == "-"; + if negative { + s = s.get(1..).unwrap(); + } + let input = CString::new(s).unwrap(); + let mut v = MaybeUninit::::uninit(); + let result = unsafe { gmp::mpz_init_set_str(v.as_mut_ptr(), input.as_ptr(), radix) }; + // v will be initialized even if an error occurs, so we will need to + // capture it in a Number regardless + let mut ret = Number { + v: unsafe { v.assume_init() }, + }; + if negative { + unsafe { + gmp::mpz_neg(&mut ret.v, &ret.v); + } + } + assert!(result == 0); + ret + } +} + +#[cfg(test)] +impl fmt::Display for Number { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let len = unsafe { gmp::mpz_sizeinbase(&self.v, 10) } + 2; + let mut storage = Vec::::with_capacity(len); + let c_str = unsafe { gmp::mpz_get_str(storage.as_mut_ptr(), 10, &self.v) }; + unsafe { f.write_str(CStr::from_ptr(c_str).to_str().unwrap()) } } } @@ -95,12 +550,6 @@ fn test_ptr_from_number() { assert_eq!(&[0x40, 0x00], &a.atom(ptr)); } -#[cfg(test)] -use num_bigint::{BigUint, Sign}; - -#[cfg(test)] -use std::convert::TryFrom; - #[cfg(test)] fn roundtrip_bytes(b: &[u8]) { let negative = b.len() > 0 && (b[0] & 0x80) != 0; @@ -118,25 +567,12 @@ fn roundtrip_bytes(b: &[u8]) { } let round_trip = num.to_signed_bytes_be(); - // num-bigin produces a single 0 byte for the value 0. We expect an - // empty array - let round_trip = if round_trip == &[0] { - &round_trip[1..] - } else { - &round_trip - }; assert_eq!(round_trip, b); // test to_bytes_le() let (sign, mut buf_le) = num.to_bytes_le(); - // there's a special case for empty input buffers, which will result in - // a single 0 byte here - if b == &[] { - assert_eq!(buf_le, &[0]); - buf_le.remove(0); - } assert!(sign == num.sign()); // the buffer we get from to_bytes_le() is unsigned (since the sign is @@ -153,7 +589,10 @@ fn roundtrip_bytes(b: &[u8]) { if sign != Sign::Minus { assert!(buf_le.iter().eq(b.iter().rev())); } else { - let negated = -num; + let mut negated = Number::zero(); + unsafe { + gmp::mpz_neg(&mut negated.v, &num.v); + } let magnitude = negated.to_signed_bytes_be(); assert!(buf_le.iter().eq(magnitude.iter().rev())); } @@ -161,7 +600,7 @@ fn roundtrip_bytes(b: &[u8]) { // test parsing unsigned bytes { - let unsigned_num: Number = BigUint::from_bytes_be(b).into(); + let unsigned_num = Number::from_unsigned_bytes_be(b); assert!(unsigned_num.sign() != Sign::Minus); let unsigned_round_trip = unsigned_num.to_signed_bytes_be(); let unsigned_round_trip = if unsigned_round_trip == &[0] { @@ -183,6 +622,7 @@ fn roundtrip_bytes(b: &[u8]) { fn test_number_round_trip_bytes() { roundtrip_bytes(&[]); + // 0 doesn't round-trip, since we represent that by an empty buffer for i in 1..=255 { roundtrip_bytes(&[i]); } @@ -222,8 +662,32 @@ fn roundtrip_u64(v: u64) { assert!(num.sign() != Sign::Minus); assert!(num.bits() <= 64); + assert!(!(num < v)); + assert!(!(num > v)); + assert!(!(num != v)); + assert!(num == v); + assert!(num <= v); + assert!(num >= v); + + if v != u64::MAX { + assert!(num < v + 1); + assert!(!(num > v + 1)); + assert!(num != v + 1); + assert!(!(num == v + 1)); + assert!(num <= v + 1); + assert!(!(num >= v + 1)); + } - let round_trip: u64 = TryFrom::try_from(num).unwrap(); + if v != u64::MIN { + assert!(!(num < v - 1)); + assert!(num > v - 1); + assert!(num != v - 1); + assert!(!(num == v - 1)); + assert!(!(num <= v - 1)); + assert!(num >= v - 1); + } + + let round_trip: u64 = num.into(); assert_eq!(round_trip, v); } @@ -269,8 +733,7 @@ fn roundtrip_i64(v: i64) { } assert!(num.bits() <= 64); - - let round_trip: i64 = TryFrom::try_from(num).unwrap(); + let round_trip: i64 = num.into(); assert_eq!(round_trip, v); } @@ -323,7 +786,7 @@ fn test_round_trip_i64() { #[cfg(test)] fn bits(b: &[u8]) -> u64 { - Number::from_signed_bytes_be(b).bits() + Number::from_signed_bytes_be(b).bits() as u64 } #[test] diff --git a/src/test_ops.rs b/src/test_ops.rs index 1122adf1..ddd789c6 100644 --- a/src/test_ops.rs +++ b/src/test_ops.rs @@ -9,7 +9,6 @@ use crate::more_ops::{ use crate::number::{ptr_from_number, Number}; use crate::reduction::{Reduction, Response}; use hex::FromHex; -use num_traits::Num; use std::collections::HashMap; static TEST_CASES: &str = r#" @@ -705,7 +704,7 @@ fn parse_atom(a: &mut Allocator, v: &str) -> NodePtr { } if v.starts_with("-") || "0123456789".contains(v.get(0..1).unwrap()) { - let num = Number::from_str_radix(v, 10).unwrap(); + let num = Number::from_str_radix(v, 10); return ptr_from_number(a, &num).unwrap(); }