From 04252b771604f125480050391cefb36257ad52a5 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 15 May 2018 18:01:14 -0700 Subject: [PATCH 1/2] Add basic conversions for i128 and u128 --- Cargo.toml | 6 ++- README.md | 6 +++ build.rs | 35 ++++++++++++++++ src/bigint.rs | 84 +++++++++++++++++++++++++++++++++++++- src/biguint.rs | 72 ++++++++++++++++++++++++++++---- tests/bigint.rs | 104 ++++++++++++++++++++++++++++++++++++++++------- tests/biguint.rs | 55 ++++++++++++++++++++++++- 7 files changed, 334 insertions(+), 28 deletions(-) create mode 100644 build.rs diff --git a/Cargo.toml b/Cargo.toml index 4c1b21d9..51d9f00a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ repository = "https://github.com/rust-num/num-bigint" version = "0.2.0-git" publish = false readme = "README.md" +build = "build.rs" [[bench]] name = "bigint" @@ -28,11 +29,11 @@ name = "shootout-pidigits" [dependencies] [dependencies.num-integer] -version = "0.1.36" +version = "0.1.38" default-features = false [dependencies.num-traits] -version = "0.2.1" +version = "0.2.4" default-features = false features = ["std"] @@ -52,3 +53,4 @@ version = "0.4" [features] default = [] +i128 = ["num-integer/i128", "num-traits/i128"] diff --git a/README.md b/README.md index 93a9b97f..478c8b34 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,12 @@ and this to your crate root: extern crate num_bigint; ``` +## Features + +Implementations for `i128` and `u128` are only available with Rust 1.26 and +later. The build script automatically detects this, but you can make it +mandatory by enabling the `i128` crate feature. + ## Releases Release notes are available in [RELEASES.md](RELEASES.md). diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..fd608665 --- /dev/null +++ b/build.rs @@ -0,0 +1,35 @@ +use std::env; +use std::io::Write; +use std::process::{Command, Stdio}; + +fn main() { + if probe("fn main() { 0i128; }") { + println!("cargo:rustc-cfg=has_i128"); + } else if env::var_os("CARGO_FEATURE_I128").is_some() { + panic!("i128 support was not detected!"); + } +} + +/// Test if a code snippet can be compiled +fn probe(code: &str) -> bool { + let rustc = env::var_os("RUSTC").unwrap_or_else(|| "rustc".into()); + let out_dir = env::var_os("OUT_DIR").expect("environment variable OUT_DIR"); + + let mut child = Command::new(rustc) + .arg("--out-dir") + .arg(out_dir) + .arg("--emit=obj") + .arg("-") + .stdin(Stdio::piped()) + .spawn() + .expect("rustc probe"); + + child + .stdin + .as_mut() + .expect("rustc stdin") + .write_all(code.as_bytes()) + .expect("write rustc stdin"); + + child.wait().expect("rustc probe").success() +} diff --git a/src/bigint.rs b/src/bigint.rs index aa714eb2..7159c02c 100644 --- a/src/bigint.rs +++ b/src/bigint.rs @@ -7,7 +7,9 @@ use std::fmt; use std::mem; use std::cmp::Ordering::{self, Less, Greater, Equal}; use std::{i64, u64}; -#[allow(unused_imports)] +#[cfg(has_i128)] +use std::{i128, u128}; +#[allow(deprecated, unused_imports)] use std::ascii::AsciiExt; use std::iter::{Product, Sum}; @@ -1811,6 +1813,27 @@ impl ToPrimitive for BigInt { } } + #[inline] + #[cfg(has_i128)] + fn to_i128(&self) -> Option { + match self.sign { + Plus => self.data.to_i128(), + NoSign => Some(0), + Minus => { + self.data.to_u128().and_then(|n| { + let m: u128 = 1 << 127; + if n < m { + Some(-(n as i128)) + } else if n == m { + Some(i128::MIN) + } else { + None + } + }) + } + } + } + #[inline] fn to_u64(&self) -> Option { match self.sign { @@ -1820,6 +1843,16 @@ impl ToPrimitive for BigInt { } } + #[inline] + #[cfg(has_i128)] + fn to_u128(&self) -> Option { + match self.sign { + Plus => self.data.to_u128(), + NoSign => Some(0), + Minus => None, + } + } + #[inline] fn to_f32(&self) -> Option { self.data.to_f32().map(|n| { @@ -1849,11 +1882,23 @@ impl FromPrimitive for BigInt { Some(BigInt::from(n)) } + #[inline] + #[cfg(has_i128)] + fn from_i128(n: i128) -> Option { + Some(BigInt::from(n)) + } + #[inline] fn from_u64(n: u64) -> Option { Some(BigInt::from(n)) } + #[inline] + #[cfg(has_i128)] + fn from_u128(n: u128) -> Option { + Some(BigInt::from(n)) + } + #[inline] fn from_f64(n: f64) -> Option { if n >= 0.0 { @@ -1879,6 +1924,22 @@ impl From for BigInt { } } +#[cfg(has_i128)] +impl From for BigInt { + #[inline] + fn from(n: i128) -> Self { + if n >= 0 { + BigInt::from(n as u128) + } else { + let u = u128::MAX - (n as u128) + 1; + BigInt { + sign: Minus, + data: BigUint::from(u), + } + } + } +} + macro_rules! impl_bigint_from_int { ($T:ty) => { impl From<$T> for BigInt { @@ -1909,6 +1970,21 @@ impl From for BigInt { } } +#[cfg(has_i128)] +impl From for BigInt { + #[inline] + fn from(n: u128) -> Self { + if n > 0 { + BigInt { + sign: Plus, + data: BigUint::from(n), + } + } else { + BigInt::zero() + } + } +} + macro_rules! impl_bigint_from_uint { ($T:ty) => { impl From<$T> for BigInt { @@ -2040,11 +2116,17 @@ impl_to_bigint!(i8, FromPrimitive::from_i8); impl_to_bigint!(i16, FromPrimitive::from_i16); impl_to_bigint!(i32, FromPrimitive::from_i32); impl_to_bigint!(i64, FromPrimitive::from_i64); +#[cfg(has_i128)] +impl_to_bigint!(i128, FromPrimitive::from_i128); + impl_to_bigint!(usize, FromPrimitive::from_usize); impl_to_bigint!(u8, FromPrimitive::from_u8); impl_to_bigint!(u16, FromPrimitive::from_u16); impl_to_bigint!(u32, FromPrimitive::from_u32); impl_to_bigint!(u64, FromPrimitive::from_u64); +#[cfg(has_i128)] +impl_to_bigint!(u128, FromPrimitive::from_u128); + impl_to_bigint!(f32, FromPrimitive::from_f32); impl_to_bigint!(f64, FromPrimitive::from_f64); diff --git a/src/biguint.rs b/src/biguint.rs index 7bf9da21..5d4aaf89 100644 --- a/src/biguint.rs +++ b/src/biguint.rs @@ -11,7 +11,7 @@ use std::mem; use std::cmp::Ordering::{self, Less, Greater, Equal}; use std::{f32, f64}; use std::{u8, u64}; -#[allow(unused_imports)] +#[allow(deprecated, unused_imports)] use std::ascii::AsciiExt; #[cfg(feature = "serde")] @@ -1059,14 +1059,13 @@ fn high_bits_to_u64(v: &BigUint) -> u64 { impl ToPrimitive for BigUint { #[inline] fn to_i64(&self) -> Option { - self.to_u64().and_then(|n| { - // If top bit of u64 is set, it's too large to convert to i64. - if n >> 63 == 0 { - Some(n as i64) - } else { - None - } - }) + self.to_u64().as_ref().and_then(u64::to_i64) + } + + #[inline] + #[cfg(has_i128)] + fn to_i128(&self) -> Option { + self.to_u128().as_ref().and_then(u128::to_i128) } #[inline] @@ -1086,6 +1085,24 @@ impl ToPrimitive for BigUint { Some(ret) } + #[inline] + #[cfg(has_i128)] + fn to_u128(&self) -> Option { + let mut ret: u128 = 0; + let mut bits = 0; + + for i in self.data.iter() { + if bits >= 128 { + return None; + } + + ret += (*i as u128) << bits; + bits += big_digit::BITS; + } + + Some(ret) + } + #[inline] fn to_f32(&self) -> Option { let mantissa = high_bits_to_u64(self); @@ -1131,11 +1148,27 @@ impl FromPrimitive for BigUint { } } + #[inline] + #[cfg(has_i128)] + fn from_i128(n: i128) -> Option { + if n >= 0 { + Some(BigUint::from(n as u128)) + } else { + None + } + } + #[inline] fn from_u64(n: u64) -> Option { Some(BigUint::from(n)) } + #[inline] + #[cfg(has_i128)] + fn from_u128(n: u128) -> Option { + Some(BigUint::from(n)) + } + #[inline] fn from_f64(mut n: f64) -> Option { // handle NAN, INFINITY, NEG_INFINITY @@ -1182,6 +1215,21 @@ impl From for BigUint { } } +#[cfg(has_i128)] +impl From for BigUint { + #[inline] + fn from(mut n: u128) -> Self { + let mut ret: BigUint = Zero::zero(); + + while n != 0 { + ret.data.push(n as BigDigit); + n >>= big_digit::BITS; + } + + ret + } +} + macro_rules! impl_biguint_from_uint { ($T:ty) => { impl From<$T> for BigUint { @@ -1227,11 +1275,17 @@ impl_to_biguint!(i8, FromPrimitive::from_i8); impl_to_biguint!(i16, FromPrimitive::from_i16); impl_to_biguint!(i32, FromPrimitive::from_i32); impl_to_biguint!(i64, FromPrimitive::from_i64); +#[cfg(has_i128)] +impl_to_biguint!(i128, FromPrimitive::from_i128); + impl_to_biguint!(usize, FromPrimitive::from_usize); impl_to_biguint!(u8, FromPrimitive::from_u8); impl_to_biguint!(u16, FromPrimitive::from_u16); impl_to_biguint!(u32, FromPrimitive::from_u32); impl_to_biguint!(u64, FromPrimitive::from_u64); +#[cfg(has_i128)] +impl_to_biguint!(u128, FromPrimitive::from_u128); + impl_to_biguint!(f32, FromPrimitive::from_f32); impl_to_biguint!(f64, FromPrimitive::from_f64); diff --git a/tests/bigint.rs b/tests/bigint.rs index e8193686..0e9e1bdb 100644 --- a/tests/bigint.rs +++ b/tests/bigint.rs @@ -12,6 +12,8 @@ use std::{f32, f64}; use std::{i8, i16, i32, i64, isize}; use std::iter::repeat; use std::{u8, u16, u32, u64, usize}; +#[cfg(has_i128)] +use std::{i128, u128}; use std::ops::Neg; use std::hash::{BuildHasher, Hasher, Hash}; use std::collections::hash_map::RandomState; @@ -240,16 +242,52 @@ fn test_convert_i64() { assert_eq!((i64::MAX as u64 + 1).to_bigint().unwrap().to_i64(), None); - assert_eq!(BigInt::from_biguint(Plus, BigUint::new(vec![1, 2, 3, 4, 5])).to_i64(), - None); + assert_eq!( + BigInt::from_biguint(Plus, BigUint::new(vec![1, 2, 3, 4, 5])).to_i64(), + None + ); - assert_eq!(BigInt::from_biguint(Minus, - BigUint::new(vec![1, 0, 0, 1 << 31])) - .to_i64(), - None); + assert_eq!( + BigInt::from_biguint(Minus, BigUint::new(vec![1, 0, 0, 1 << 31])).to_i64(), + None + ); - assert_eq!(BigInt::from_biguint(Minus, BigUint::new(vec![1, 2, 3, 4, 5])).to_i64(), - None); + assert_eq!( + BigInt::from_biguint(Minus, BigUint::new(vec![1, 2, 3, 4, 5])).to_i64(), + None + ); +} + +#[test] +#[cfg(has_i128)] +fn test_convert_i128() { + fn check(b1: BigInt, i: i128) { + let b2: BigInt = FromPrimitive::from_i128(i).unwrap(); + assert!(b1 == b2); + assert!(b1.to_i128().unwrap() == i); + } + + check(Zero::zero(), 0); + check(One::one(), 1); + check(i128::MIN.to_bigint().unwrap(), i128::MIN); + check(i128::MAX.to_bigint().unwrap(), i128::MAX); + + assert_eq!((i128::MAX as u128 + 1).to_bigint().unwrap().to_i128(), None); + + assert_eq!( + BigInt::from_biguint(Plus, BigUint::new(vec![1, 2, 3, 4, 5])).to_i128(), + None + ); + + assert_eq!( + BigInt::from_biguint(Minus, BigUint::new(vec![1, 0, 0, 1 << 31])).to_i128(), + None + ); + + assert_eq!( + BigInt::from_biguint(Minus, BigUint::new(vec![1, 2, 3, 4, 5])).to_i128(), + None + ); } #[test] @@ -265,13 +303,44 @@ fn test_convert_u64() { check(u64::MIN.to_bigint().unwrap(), u64::MIN); check(u64::MAX.to_bigint().unwrap(), u64::MAX); - assert_eq!(BigInt::from_biguint(Plus, BigUint::new(vec![1, 2, 3, 4, 5])).to_u64(), - None); + assert_eq!( + BigInt::from_biguint(Plus, BigUint::new(vec![1, 2, 3, 4, 5])).to_u64(), + None + ); let max_value: BigUint = FromPrimitive::from_u64(u64::MAX).unwrap(); assert_eq!(BigInt::from_biguint(Minus, max_value).to_u64(), None); - assert_eq!(BigInt::from_biguint(Minus, BigUint::new(vec![1, 2, 3, 4, 5])).to_u64(), - None); + assert_eq!( + BigInt::from_biguint(Minus, BigUint::new(vec![1, 2, 3, 4, 5])).to_u64(), + None + ); +} + +#[test] +#[cfg(has_i128)] +fn test_convert_u128() { + fn check(b1: BigInt, u: u128) { + let b2: BigInt = FromPrimitive::from_u128(u).unwrap(); + assert!(b1 == b2); + assert!(b1.to_u128().unwrap() == u); + } + + check(Zero::zero(), 0); + check(One::one(), 1); + check(u128::MIN.to_bigint().unwrap(), u128::MIN); + check(u128::MAX.to_bigint().unwrap(), u128::MAX); + + assert_eq!( + BigInt::from_biguint(Plus, BigUint::new(vec![1, 2, 3, 4, 5])).to_u128(), + None + ); + + let max_value: BigUint = FromPrimitive::from_u128(u128::MAX).unwrap(); + assert_eq!(BigInt::from_biguint(Minus, max_value).to_u128(), None); + assert_eq!( + BigInt::from_biguint(Minus, BigUint::new(vec![1, 2, 3, 4, 5])).to_u128(), + None + ); } #[test] @@ -455,14 +524,15 @@ fn test_convert_from_uint() { assert_eq!(BigInt::from($ty::one()), BigInt::one()); assert_eq!(BigInt::from($ty::MAX - $ty::one()), $max - BigInt::one()); assert_eq!(BigInt::from($ty::MAX), $max); - } + }; } check!(u8, BigInt::from_slice(Plus, &[u8::MAX as u32])); check!(u16, BigInt::from_slice(Plus, &[u16::MAX as u32])); check!(u32, BigInt::from_slice(Plus, &[u32::MAX])); - check!(u64, - BigInt::from_slice(Plus, &[u32::MAX, u32::MAX])); + check!(u64, BigInt::from_slice(Plus, &[u32::MAX, u32::MAX])); + #[cfg(has_i128)] + check!(u128, BigInt::from_slice(Plus, &[u32::MAX, u32::MAX, u32::MAX, u32::MAX])); check!(usize, BigInt::from(usize::MAX as u64)); } @@ -492,6 +562,10 @@ fn test_convert_from_int() { check!(i64, BigInt::from_slice(Minus, &[0, 1 << 31]), BigInt::from_slice(Plus, &[u32::MAX, i32::MAX as u32])); + #[cfg(has_i128)] + check!(i128, + BigInt::from_slice(Minus, &[0, 0, 0, 1 << 31]), + BigInt::from_slice(Plus, &[u32::MAX, u32::MAX, u32::MAX, i32::MAX as u32])); check!(isize, BigInt::from(isize::MIN as i64), BigInt::from(isize::MAX as i64)); diff --git a/tests/biguint.rs b/tests/biguint.rs index b62edde3..e864191b 100644 --- a/tests/biguint.rs +++ b/tests/biguint.rs @@ -14,6 +14,8 @@ use std::i64; use std::iter::repeat; use std::str::FromStr; use std::{u8, u16, u32, u64, usize}; +#[cfg(has_i128)] +use std::{i128, u128}; use std::hash::{BuildHasher, Hasher, Hash}; use std::collections::hash_map::RandomState; @@ -490,6 +492,31 @@ fn test_convert_i64() { assert_eq!(BigUint::new(vec![N1, N1, N1]).to_i64(), None); } +#[test] +#[cfg(has_i128)] +fn test_convert_i128() { + fn check(b1: BigUint, i: i128) { + let b2: BigUint = FromPrimitive::from_i128(i).unwrap(); + assert_eq!(b1, b2); + assert_eq!(b1.to_i128().unwrap(), i); + } + + check(Zero::zero(), 0); + check(One::one(), 1); + check(i128::MAX.to_biguint().unwrap(), i128::MAX); + + check(BigUint::new(vec![]), 0); + check(BigUint::new(vec![1]), 1); + check(BigUint::new(vec![N1]), (1 << 32) - 1); + check(BigUint::new(vec![0, 1]), 1 << 32); + check(BigUint::new(vec![N1, N1, N1, N1 >> 1]), i128::MAX); + + assert_eq!(i128::MIN.to_biguint(), None); + assert_eq!(BigUint::new(vec![N1, N1, N1, N1]).to_i128(), None); + assert_eq!(BigUint::new(vec![0, 0, 0, 0, 1]).to_i128(), None); + assert_eq!(BigUint::new(vec![N1, N1, N1, N1, N1]).to_i128(), None); +} + // `DoubleBigDigit` size dependent #[test] fn test_convert_u64() { @@ -514,6 +541,30 @@ fn test_convert_u64() { assert_eq!(BigUint::new(vec![N1, N1, N1]).to_u64(), None); } +#[test] +#[cfg(has_i128)] +fn test_convert_u128() { + fn check(b1: BigUint, u: u128) { + let b2: BigUint = FromPrimitive::from_u128(u).unwrap(); + assert_eq!(b1, b2); + assert_eq!(b1.to_u128().unwrap(), u); + } + + check(Zero::zero(), 0); + check(One::one(), 1); + check(u128::MIN.to_biguint().unwrap(), u128::MIN); + check(u128::MAX.to_biguint().unwrap(), u128::MAX); + + check(BigUint::new(vec![]), 0); + check(BigUint::new(vec![1]), 1); + check(BigUint::new(vec![N1]), (1 << 32) - 1); + check(BigUint::new(vec![0, 1]), 1 << 32); + check(BigUint::new(vec![N1, N1, N1, N1]), u128::MAX); + + assert_eq!(BigUint::new(vec![0, 0, 0, 0, 1]).to_u128(), None); + assert_eq!(BigUint::new(vec![N1, N1, N1, N1, N1]).to_u128(), None); +} + #[test] fn test_convert_f32() { fn check(b1: &BigUint, f: f32) { @@ -677,6 +728,8 @@ fn test_convert_from_uint() { check!(u16, BigUint::from_slice(&[u16::MAX as u32])); check!(u32, BigUint::from_slice(&[u32::MAX])); check!(u64, BigUint::from_slice(&[u32::MAX, u32::MAX])); + #[cfg(has_i128)] + check!(u128, BigUint::from_slice(&[u32::MAX, u32::MAX, u32::MAX, u32::MAX])); check!(usize, BigUint::from(usize::MAX as u64)); } @@ -1317,7 +1370,7 @@ fn test_from_str_radix() { #[test] fn test_all_str_radix() { - #[allow(unused_imports)] + #[allow(deprecated, unused_imports)] use std::ascii::AsciiExt; let n = BigUint::new((0..10).collect()); From c6bce8a130446e380b9cc1a7f3bc52e7f8f2cf46 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Wed, 16 May 2018 12:19:47 -0700 Subject: [PATCH 2/2] test i128 only on newer rustc --- .travis.yml | 2 ++ ci/rustup.sh | 2 +- ci/test_full.sh | 14 ++++++++++++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8ada3f7b..8a4946c9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,8 @@ language: rust rust: - 1.15.0 + - 1.20.0 + - 1.25.0 - stable - beta - nightly diff --git a/ci/rustup.sh b/ci/rustup.sh index dc1aa141..e8d1ba74 100755 --- a/ci/rustup.sh +++ b/ci/rustup.sh @@ -5,7 +5,7 @@ set -ex export TRAVIS_RUST_VERSION -for TRAVIS_RUST_VERSION in 1.15.0 stable beta nightly; do +for TRAVIS_RUST_VERSION in 1.15.0 1.20.0 1.25.0 stable beta nightly; do run="rustup run $TRAVIS_RUST_VERSION" $run cargo build --verbose $run $PWD/ci/test_full.sh diff --git a/ci/test_full.sh b/ci/test_full.sh index d9cf996d..6521bfc6 100755 --- a/ci/test_full.sh +++ b/ci/test_full.sh @@ -18,5 +18,15 @@ for feature in rand serde; do cargo test --verbose --no-default-features --features="$feature" done -cargo build --all-features -cargo test --all-features +# test `i128` and all features together +if [[ "$TRAVIS_RUST_VERSION" =~ ^(nightly|beta|stable)$ ]]; then + cargo build --verbose --features=i128 + cargo test --verbose --features=i128 + + cargo build --all-features + cargo test --all-features +else + # all except `i128` + cargo build --features="rand serde" + cargo test --features="rand serde" +fi