From 5d3273364e0f12a6a3bc9a060a5edf1741f157fa Mon Sep 17 00:00:00 2001 From: Jacob Date: Sat, 20 Jan 2024 21:44:51 +0800 Subject: [PATCH 01/15] Create the Python project --- Cargo.toml | 1 + python/Cargo.toml | 33 ++++++++++++++++++++ python/README.md | 3 ++ python/pyproject.toml | 12 ++++++++ python/src/float.rs | 0 python/src/int.rs | 71 +++++++++++++++++++++++++++++++++++++++++++ python/src/lib.rs | 18 +++++++++++ python/src/ratio.rs | 0 python/src/types.rs | 61 +++++++++++++++++++++++++++++++++++++ python/src/utils.rs | 0 10 files changed, 199 insertions(+) create mode 100644 python/Cargo.toml create mode 100644 python/README.md create mode 100644 python/pyproject.toml create mode 100644 python/src/float.rs create mode 100644 python/src/int.rs create mode 100644 python/src/lib.rs create mode 100644 python/src/ratio.rs create mode 100644 python/src/types.rs create mode 100644 python/src/utils.rs diff --git a/Cargo.toml b/Cargo.toml index c33204d..11121c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ members = [ "integer", "float", "macros", + "python", "rational", ] default-members = ["base", "integer", "float", "rational", "macros"] diff --git a/python/Cargo.toml b/python/Cargo.toml new file mode 100644 index 0000000..430f760 --- /dev/null +++ b/python/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "dashu-python" +version = "0.0.1" +authors = ["Jacob Zhong "] +edition = "2021" +description = "Python binding for the dashu numeric types" +keywords = ["mathematics", "numerics"] +categories = ["mathematics", "no-std"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/cmpute/dashu" +homepage = "https://github.com/cmpute/dashu" +documentation = "https://docs.rs/dashu-python" +readme = "README.md" +rust-version = "1.61" + +[package.metadata.docs.rs] +all-features = true + +[dependencies] +dashu-base = { version = "0.4.0", default-features = false, path = "../base" } +dashu-int = { version = "0.4.1", default-features = false, path = "../integer" } +dashu-float = { version = "0.4.2", default-features = false, path = "../float" } +dashu-ratio = { version = "0.4.1", default-features = false, path = "../rational", features = ['dashu-float'] } + +_num-modular = { optional = true, version = "0.6.1", package = "num-modular", default-features = false } + +[dependencies.pyo3] +version = "0.20" +features = ["extension-module"] + +[lib] +name = "dashu" +crate-type = ["cdylib"] diff --git a/python/README.md b/python/README.md new file mode 100644 index 0000000..ed09451 --- /dev/null +++ b/python/README.md @@ -0,0 +1,3 @@ +# dashu-python + +Python binding to the numeric types in the dashu crates, based on PyO3. diff --git a/python/pyproject.toml b/python/pyproject.toml new file mode 100644 index 0000000..f3ff88d --- /dev/null +++ b/python/pyproject.toml @@ -0,0 +1,12 @@ +[build-system] +requires = ["maturin>=1,<2"] +build-backend = "maturin" + +[project] +name = "dashu" +requires-python = ">=3.7" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] \ No newline at end of file diff --git a/python/src/float.rs b/python/src/float.rs new file mode 100644 index 0000000..e69de29 diff --git a/python/src/int.rs b/python/src/int.rs new file mode 100644 index 0000000..4d31801 --- /dev/null +++ b/python/src/int.rs @@ -0,0 +1,71 @@ +use crate::types::{UPy, IPy}; + +use pyo3::prelude::*; +use pyo3::exceptions::{PyTypeError, PyValueError}; +use pyo3::ffi; +use pyo3::types::PyLong; + +#[pymethods] +impl UPy { + #[new] + fn new(obj: &PyAny) -> PyResult { + ::extract(obj) + // If #[cfg(Py_LIMITED_API)], then convert from bigint is allowed + // If #[cfg(not(Py_LIMITED_API))], then only convert from long is allowed + } + + fn unwrap(self) -> PyObject { + todo!() // To builtin int + // If #[cfg(Py_LIMITED_API)], then convert to bigint is allowed + // If #[cfg(not(Py_LIMITED_API))], then only convert to long is allowed + } +} + +// conversion from python object +impl<'source> FromPyObject<'source> for UPy { + fn extract(ob: &'source PyAny) -> PyResult { + let py = ob.py(); + + unsafe { + let ptr = ob.as_ptr(); + if ffi::PyLong_Check(ptr) == 0 { + return Err(PyValueError::new_err( + "Only Python integers can be automatically converted to an UBig instance." + )); + } + // input is integer + let mut overflow = 0; + let v = ffi::PyLong_AsLongLongAndOverflow(ptr, &mut overflow); + if v == -1 && PyErr::occurred(py) { + Err(PyErr::fetch(py)) + } else if overflow != 0 { + // some code below is from https://github.com/PyO3/pyo3/blob/main/src/conversions/num_bigint.rs + let n_bits = ffi::_PyLong_NumBits(ptr) as usize; + let n_bytes = match n_bits { + usize::MAX => return Err(PyErr::fetch(py)), + 0 => 0, + n => (n as usize) / 8 + 1, + }; + let long_ptr = ptr as *mut ffi::PyLongObject; + let num_big = if n_bytes <= 64 { + let mut buffer = [0; 64]; + let bptr = buffer.as_mut_ptr(); + if ffi::_PyLong_AsByteArray(long_ptr, bptr, n_bytes, 1, 1) == -1 { + return Err(PyErr::fetch(py)); + } + BigInt::from_signed_bytes_le(&buffer[..n_bytes]) + } else { + let mut buffer = vec![0; n_bytes]; + let bptr = buffer.as_mut_ptr(); + if ffi::_PyLong_AsByteArray(long_ptr, bptr, n_bytes, 1, 1) == -1 { + return Err(PyErr::fetch(py)); + } + BigInt::from_signed_bytes_le(&buffer) + }; + Ok(SubjectInput(BInt(num_big))) + } else { + Ok(SubjectInput(Int(v))) + } + } + } +} diff --git a/python/src/lib.rs b/python/src/lib.rs new file mode 100644 index 0000000..db409ee --- /dev/null +++ b/python/src/lib.rs @@ -0,0 +1,18 @@ +mod int; +mod types; +mod utils; + +use pyo3::prelude::*; + +/// A Python module implemented in Rust. The name of this function must match +/// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to +/// import the module. +#[pymodule] +fn dashu(_py: Python<'_>, m: &PyModule) -> PyResult<()> { + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + Ok(()) +} diff --git a/python/src/ratio.rs b/python/src/ratio.rs new file mode 100644 index 0000000..e69de29 diff --git a/python/src/types.rs b/python/src/types.rs new file mode 100644 index 0000000..b69cc86 --- /dev/null +++ b/python/src/types.rs @@ -0,0 +1,61 @@ +use pyo3::prelude::*; + +use dashu_int::{UBig, IBig}; +use dashu_float::DBig; +use dashu_ratio::RBig; +type FBig = dashu_float::FBig; + +/// This struct is used for representing [UBig] in Python +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +#[pyclass(name = "UBig")] +pub struct UPy(UBig); + +impl From for UPy { + fn from(n: UBig) -> Self { + UPy(n) + } +} + +/// This struct is used for representing [IBig] in Python +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +#[pyclass(name = "IBig")] +pub struct IPy(IBig); + +impl From for IPy { + fn from(n: IBig) -> Self { + IPy(n) + } +} + +/// This struct is used for representing [FBig] in Python +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +#[pyclass(name = "FBig")] +pub struct FPy(FBig); + +impl From for FPy { + fn from(n: FBig) -> Self { + FPy(n) + } +} + +/// This struct is used for representing [DBig] in Python +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +#[pyclass(name = "DBig")] +pub struct DPy(DBig); + +impl From for DPy { + fn from(n: DBig) -> Self { + DPy(n) + } +} + +/// This struct is used for representing [RBig] in Python +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +#[pyclass(name = "RBig")] +pub struct RPy(RBig); + +impl From for RPy { + fn from(n: RBig) -> Self { + RPy(n) + } +} diff --git a/python/src/utils.rs b/python/src/utils.rs new file mode 100644 index 0000000..e69de29 From fee5d44b16d6df938adc6207bebda650e8ea7f73 Mon Sep 17 00:00:00 2001 From: Jacob Date: Sun, 21 Jan 2024 15:12:33 +0800 Subject: [PATCH 02/15] Add from/to bytes for IBig --- float/Cargo.toml | 2 +- integer/CHANGELOG.md | 1 + integer/src/convert.rs | 294 +++++++++++++++++++++++++++++++++------ integer/src/primitive.rs | 24 ++-- integer/tests/convert.rs | 22 +++ 5 files changed, 287 insertions(+), 56 deletions(-) diff --git a/float/Cargo.toml b/float/Cargo.toml index 8755de4..fb9b7fd 100644 --- a/float/Cargo.toml +++ b/float/Cargo.toml @@ -37,7 +37,7 @@ postgres-types_v02 = ["dep:postgres-types_v02", "dep:_bytes", "std"] [dependencies] dashu-base = { version = "0.4.0", default-features = false, path = "../base" } dashu-int = { version = "0.4.1", default-features = false, path = "../integer" } -static_assertions = { version = "1.1"} +static_assertions = { version = "1.1" } # stable dependencies rustversion = "1.0.0" diff --git a/integer/CHANGELOG.md b/integer/CHANGELOG.md index daf83f6..135bd1d 100644 --- a/integer/CHANGELOG.md +++ b/integer/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Implement `TryFrom` and `TryFrom` for `f32`/`f64`. +- Implement `IBig::{from_le_bytes, from_be_bytes}` and `IBig::{to_le_bytes, to_be_bytes}`. ## 0.4.1 diff --git a/integer/src/convert.rs b/integer/src/convert.rs index f847228..5aea007 100644 --- a/integer/src/convert.rs +++ b/integer/src/convert.rs @@ -1,11 +1,16 @@ //! Conversions between types. use crate::{ + add, arch::word::{DoubleWord, Word}, buffer::Buffer, + helper_macros::debug_assert_zero, ibig::IBig, primitive::{self, PrimitiveSigned, PrimitiveUnsigned, DWORD_BYTES, WORD_BITS, WORD_BYTES}, - repr::{Repr, TypedReprRef::*}, + repr::{ + Repr, + TypedReprRef::{self, *}, + }, ubig::UBig, Sign::*, }; @@ -32,7 +37,7 @@ impl Default for IBig { } } -pub(crate) fn words_to_le_bytes(words: &[Word]) -> Vec { +fn words_to_le_bytes(words: &[Word]) -> Vec { debug_assert!(!words.is_empty()); let n = words.len(); @@ -40,78 +45,226 @@ pub(crate) fn words_to_le_bytes(words: &[Word]) -> Vec { let skip_last_bytes = last.leading_zeros() as usize / 8; let mut bytes = Vec::with_capacity(n * WORD_BYTES - skip_last_bytes); for word in &words[..n - 1] { + let word = if FLIP { !*word } else { *word }; bytes.extend_from_slice(&word.to_le_bytes()); } + let last = if FLIP { !last } else { last }; let last_bytes = last.to_le_bytes(); bytes.extend_from_slice(&last_bytes[..WORD_BYTES - skip_last_bytes]); bytes } -pub(crate) fn words_to_be_bytes(words: &[Word]) -> Vec { +fn words_to_be_bytes(words: &[Word]) -> Vec { debug_assert!(!words.is_empty()); let n = words.len(); let last = words[n - 1]; let skip_last_bytes = last.leading_zeros() as usize / 8; let mut bytes = Vec::with_capacity(n * WORD_BYTES - skip_last_bytes); + let last = if FLIP { !last } else { last }; let last_bytes = last.to_be_bytes(); bytes.extend_from_slice(&last_bytes[skip_last_bytes..]); for word in words[..n - 1].iter().rev() { + let word = if FLIP { !*word } else { *word }; bytes.extend_from_slice(&word.to_be_bytes()); } bytes } +impl TypedReprRef<'_> { + fn to_le_bytes(self) -> Vec { + match self { + RefSmall(x) => { + let bytes = x.to_le_bytes(); + let skip_bytes = x.leading_zeros() as usize / 8; + bytes[..DWORD_BYTES - skip_bytes].into() + } + RefLarge(words) => words_to_le_bytes::(words), + } + } + + fn to_signed_le_bytes(self, negate: bool) -> Vec { + // make sure to return empty for zero + if let RefSmall(v) = self { + if v == 0 { + return Vec::new(); + } + } + + let mut bytes = if negate { + match self { + RefSmall(x) => { + let bytes = (!x + 1).to_le_bytes(); + let skip_bytes = x.leading_zeros() as usize / 8; + bytes[..DWORD_BYTES - skip_bytes].into() + } + RefLarge(words) => { + let mut buffer = Buffer::from(words); + debug_assert_zero!(add::sub_one_in_place(&mut buffer)); + words_to_le_bytes::(&buffer) + } + } + } else { + self.to_le_bytes() + }; + + let leading_zeros = match self { + RefSmall(x) => x.leading_zeros(), + RefLarge(words) => words.last().unwrap().leading_zeros(), + }; + if leading_zeros % 8 == 0 { + // add extra byte representing the sign, because the top bit is used + bytes.push(if negate { 0xff } else { 0 }); + } + + bytes + } + + fn to_be_bytes(self) -> Vec { + match self { + RefSmall(x) => { + let bytes = x.to_be_bytes(); + let skip_bytes = x.leading_zeros() as usize / 8; + bytes[skip_bytes..].into() + } + RefLarge(words) => words_to_be_bytes::(words), + } + } + + fn to_signed_be_bytes(self, negate: bool) -> Vec { + // make sure to return empty for zero + if let RefSmall(v) = self { + if v == 0 { + return Vec::new(); + } + } + + let mut bytes = if negate { + match self { + RefSmall(x) => { + let bytes = (!x + 1).to_be_bytes(); + let skip_bytes = x.leading_zeros() as usize / 8; + bytes[skip_bytes..].into() + } + RefLarge(words) => { + let mut buffer = Buffer::from(words); + debug_assert_zero!(add::sub_one_in_place(&mut buffer)); + words_to_be_bytes::(&buffer) + } + } + } else { + self.to_be_bytes() + }; + + let leading_zeros = match self { + RefSmall(x) => x.leading_zeros(), + RefLarge(words) => words.last().unwrap().leading_zeros(), + }; + if leading_zeros % 8 == 0 { + // add extra byte representing the sign, because the top bit is used + bytes.insert(0, if negate { 0xff } else { 0 }); + } + + bytes + } +} + impl Repr { - #[inline] - pub fn from_le_bytes(bytes: &[u8]) -> Repr { - if bytes.len() <= WORD_BYTES { + fn from_le_bytes(bytes: &[u8]) -> Self { + if bytes.len() <= DWORD_BYTES { // fast path - Self::from_word(primitive::word_from_le_bytes_partial(bytes)) - } else if bytes.len() <= DWORD_BYTES { - Self::from_dword(primitive::dword_from_le_bytes_partial(bytes)) + Self::from_dword(primitive::dword_from_le_bytes_partial::(bytes)) } else { // slow path - Self::from_le_bytes_large(bytes) + Self::from_le_bytes_large::(bytes) + } + } + + fn from_signed_le_bytes(bytes: &[u8]) -> Self { + if let Some(v) = bytes.last() { + if *v < 0x80 { + return Self::from_le_bytes(bytes); + } + } else { + return Self::zero(); } + + // negative + let repr = if bytes.len() <= DWORD_BYTES { + // fast path + let dword = primitive::dword_from_le_bytes_partial::(bytes); + Self::from_dword(!dword + 1) + } else { + // slow path + Self::from_le_bytes_large::(bytes) + }; + repr.with_sign(Sign::Negative) } - pub fn from_le_bytes_large(bytes: &[u8]) -> Repr { + fn from_le_bytes_large(bytes: &[u8]) -> Self { debug_assert!(bytes.len() >= DWORD_BYTES); let mut buffer = Buffer::allocate((bytes.len() - 1) / WORD_BYTES + 1); let mut chunks = bytes.chunks_exact(WORD_BYTES); for chunk in &mut chunks { - buffer.push(Word::from_le_bytes(chunk.try_into().unwrap())); + let word = Word::from_le_bytes(chunk.try_into().unwrap()); + buffer.push(if NEG { !word } else { word }); } if !chunks.remainder().is_empty() { - buffer.push(primitive::word_from_le_bytes_partial(chunks.remainder())); + let word = primitive::word_from_le_bytes_partial::(chunks.remainder()); + buffer.push(if NEG { !word } else { word }); + } + if NEG { + debug_assert_zero!(add::add_one_in_place(&mut buffer)); } - Repr::from_buffer(buffer) + Self::from_buffer(buffer) } - pub fn from_be_bytes(bytes: &[u8]) -> Repr { - if bytes.len() <= WORD_BYTES { - // fast path - Repr::from_word(primitive::word_from_be_bytes_partial(bytes)) - } else if bytes.len() <= DWORD_BYTES { - Repr::from_dword(primitive::dword_from_be_bytes_partial(bytes)) + fn from_be_bytes(bytes: &[u8]) -> Self { + if bytes.len() <= DWORD_BYTES { + Self::from_dword(primitive::dword_from_be_bytes_partial::(bytes)) } else { // slow path - Self::from_be_bytes_large(bytes) + Self::from_be_bytes_large::(bytes) } } - pub fn from_be_bytes_large(bytes: &[u8]) -> Repr { + fn from_signed_be_bytes(bytes: &[u8]) -> Self { + if let Some(v) = bytes.first() { + if *v < 0x80 { + return Self::from_be_bytes(bytes); + } + } else { + return Self::zero(); + } + + // negative + let repr = if bytes.len() <= DWORD_BYTES { + // fast path + let dword = primitive::dword_from_be_bytes_partial::(bytes); + Self::from_dword(!dword + 1) + } else { + // slow path + Self::from_be_bytes_large::(bytes) + }; + repr.with_sign(Sign::Negative) + } + + fn from_be_bytes_large(bytes: &[u8]) -> Self { debug_assert!(bytes.len() >= DWORD_BYTES); let mut buffer = Buffer::allocate((bytes.len() - 1) / WORD_BYTES + 1); let mut chunks = bytes.rchunks_exact(WORD_BYTES); for chunk in &mut chunks { - buffer.push(Word::from_be_bytes(chunk.try_into().unwrap())); + let word = Word::from_be_bytes(chunk.try_into().unwrap()); + buffer.push(if NEG { !word } else { word }); } if !chunks.remainder().is_empty() { - buffer.push(primitive::word_from_be_bytes_partial(chunks.remainder())); + let word = primitive::word_from_be_bytes_partial::(chunks.remainder()); + buffer.push(if NEG { !word } else { word }); + } + if NEG { + debug_assert_zero!(add::add_one_in_place(&mut buffer)); } - Repr::from_buffer(buffer) + Self::from_buffer(buffer) } } @@ -152,14 +305,7 @@ impl UBig { /// assert_eq!(*UBig::from(0x010203u32).to_le_bytes(), [3, 2, 1]); /// ``` pub fn to_le_bytes(&self) -> Box<[u8]> { - match self.repr() { - RefSmall(x) => { - let bytes = x.to_le_bytes(); - let skip_bytes = x.leading_zeros() as usize / 8; - bytes[..DWORD_BYTES - skip_bytes].into() - } - RefLarge(words) => words_to_le_bytes(words).into_boxed_slice(), - } + self.repr().to_le_bytes().into_boxed_slice() } /// Return big-endian bytes. @@ -172,14 +318,7 @@ impl UBig { /// assert_eq!(*UBig::from(0x010203u32).to_be_bytes(), [1, 2, 3]); /// ``` pub fn to_be_bytes(&self) -> Box<[u8]> { - match self.repr() { - RefSmall(x) => { - let bytes = x.to_be_bytes(); - let skip_bytes = x.leading_zeros() as usize / 8; - bytes[skip_bytes..].into() - } - RefLarge(words) => words_to_be_bytes(words).into_boxed_slice(), - } + self.repr().to_be_bytes().into_boxed_slice() } /// Convert to f32. @@ -234,6 +373,73 @@ impl UBig { } impl IBig { + /// Construct from signed little-endian bytes. + /// + /// The negative number must be represented in a two's complement format, assuming + /// the top bits are all ones. The number is assumed negative when the top bit of + /// the top byte is set. + /// + /// # Examples + /// + /// ``` + /// # use dashu_int::IBig; + /// assert_eq!(IBig::from_le_bytes(&[1, 2, 0xf3]), IBig::from(0xfff30201u32 as i32)); + /// ``` + #[inline] + pub fn from_le_bytes(bytes: &[u8]) -> IBig { + IBig(Repr::from_signed_le_bytes(bytes)) + } + + /// Construct from big-endian bytes. + /// + /// The negative number must be represented in a two's complement format, assuming + /// the top bits are all ones. The number is assumed negative when the top bit of + /// the top byte is set. + /// + /// # Examples + /// + /// ``` + /// # use dashu_int::IBig; + /// assert_eq!(IBig::from_be_bytes(&[0xf3, 2, 1]), IBig::from(0xfff30201u32 as i32)); + /// ``` + #[inline] + pub fn from_be_bytes(bytes: &[u8]) -> IBig { + IBig(Repr::from_signed_be_bytes(bytes)) + } + + /// Return little-endian bytes. + /// + /// The negative number will be represented in a two's complement format + /// + /// # Examples + /// + /// ``` + /// # use dashu_int::IBig; + /// assert!(IBig::ZERO.to_le_bytes().is_empty()); + /// assert_eq!(*IBig::from(0xfff30201u32 as i32).to_le_bytes(), [1, 2, 0xf3]); + /// ``` + #[inline] + pub fn to_le_bytes(&self) -> Box<[u8]> { + let (sign, repr) = self.as_sign_repr(); + repr.to_signed_le_bytes(sign.into()).into_boxed_slice() + } + + /// Return big-endian bytes. + /// + /// The negative number will be represented in a two's complement format + /// + /// # Examples + /// + /// ``` + /// # use dashu_int::IBig; + /// assert!(IBig::ZERO.to_be_bytes().is_empty()); + /// assert_eq!(*IBig::from(0xfff30201u32 as i32).to_be_bytes(), [0xf3, 2, 1]); + /// ``` + pub fn to_be_bytes(&self) -> Box<[u8]> { + let (sign, repr) = self.as_sign_repr(); + repr.to_signed_be_bytes(sign.into()).into_boxed_slice() + } + /// Convert to f32. /// /// Round to nearest, breaking ties to even last bit. The returned approximation @@ -619,13 +825,11 @@ mod repr { where T: PrimitiveUnsigned, { - if let Ok(w) = x.try_into() { - Self::from_word(w) - } else if let Ok(dw) = x.try_into() { + if let Ok(dw) = x.try_into() { Self::from_dword(dw) } else { let repr = x.to_le_bytes(); - Self::from_le_bytes_large(repr.as_ref()) + Self::from_le_bytes_large::(repr.as_ref()) } } } diff --git a/integer/src/primitive.rs b/integer/src/primitive.rs index e2ce3d5..21d62a9 100644 --- a/integer/src/primitive.rs +++ b/integer/src/primitive.rs @@ -233,29 +233,33 @@ pub const DWORD_BITS_USIZE: usize = DWORD_BITS as usize; pub const DWORD_BYTES: usize = DoubleWord::BYTE_SIZE; #[inline] -pub fn word_from_le_bytes_partial(bytes: &[u8]) -> Word { - let mut word_bytes = [0; WORD_BYTES]; +pub fn word_from_le_bytes_partial(bytes: &[u8]) -> Word { + let pad: u8 = if ONE_PAD { 0xff } else { 0 }; + let mut word_bytes = [pad; WORD_BYTES]; word_bytes[..bytes.len()].copy_from_slice(bytes); Word::from_le_bytes(word_bytes) } #[inline] -pub fn dword_from_le_bytes_partial(bytes: &[u8]) -> DoubleWord { - let mut dword_bytes = [0; DWORD_BYTES]; +pub fn dword_from_le_bytes_partial(bytes: &[u8]) -> DoubleWord { + let pad: u8 = if ONE_PAD { 0xff } else { 0 }; + let mut dword_bytes = [pad; DWORD_BYTES]; dword_bytes[..bytes.len()].copy_from_slice(bytes); DoubleWord::from_le_bytes(dword_bytes) } #[inline] -pub fn word_from_be_bytes_partial(bytes: &[u8]) -> Word { - let mut word_bytes = [0; WORD_BYTES]; +pub fn word_from_be_bytes_partial(bytes: &[u8]) -> Word { + let pad: u8 = if ONE_PAD { 0xff } else { 0 }; + let mut word_bytes = [pad; WORD_BYTES]; word_bytes[WORD_BYTES - bytes.len()..].copy_from_slice(bytes); Word::from_be_bytes(word_bytes) } #[inline] -pub fn dword_from_be_bytes_partial(bytes: &[u8]) -> DoubleWord { - let mut dword_bytes = [0; DWORD_BYTES]; +pub fn dword_from_be_bytes_partial(bytes: &[u8]) -> DoubleWord { + let pad: u8 = if ONE_PAD { 0xff } else { 0 }; + let mut dword_bytes = [pad; DWORD_BYTES]; dword_bytes[DWORD_BYTES - bytes.len()..].copy_from_slice(bytes); DoubleWord::from_be_bytes(dword_bytes) } @@ -274,12 +278,12 @@ mod tests { #[test] fn test_word_from_le_bytes_partial() { - assert_eq!(word_from_le_bytes_partial(&[1, 2]), 0x0201); + assert_eq!(word_from_le_bytes_partial::(&[1, 2]), 0x0201); } #[test] fn test_word_from_be_bytes_partial() { - assert_eq!(word_from_be_bytes_partial(&[1, 2]), 0x0102); + assert_eq!(word_from_be_bytes_partial::(&[1, 2]), 0x0102); } #[test] diff --git a/integer/tests/convert.rs b/integer/tests/convert.rs index 6d85c66..7ed3fbb 100644 --- a/integer/tests/convert.rs +++ b/integer/tests/convert.rs @@ -8,22 +8,44 @@ mod helper_macros; fn test_from_to_le_bytes() { let empty: [u8; 0] = []; assert_eq!(*UBig::from_le_bytes(&[]).to_le_bytes(), empty); + assert_eq!(*UBig::ONE.to_le_bytes(), [1]); assert_eq!(*UBig::from_le_bytes(&[0; 100]).to_le_bytes(), empty); assert_eq!(*UBig::from_le_bytes(&[1, 2, 3, 0]).to_le_bytes(), [1, 2, 3]); let bytes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]; assert_eq!(*UBig::from_le_bytes(&bytes).to_le_bytes(), bytes); + + assert_eq!(*IBig::ONE.to_le_bytes(), [1]); + assert_eq!(*IBig::ZERO.to_le_bytes(), empty); + assert_eq!(*IBig::NEG_ONE.to_le_bytes(), [255]); + assert_eq!(IBig::from_le_bytes(&[255]), IBig::NEG_ONE); + assert_eq!(IBig::from_le_bytes(&[255; 100]), IBig::NEG_ONE); + let bytes = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 217, + ]; + assert_eq!(*IBig::from_le_bytes(&bytes).to_le_bytes(), bytes); } #[test] fn test_from_to_be_bytes() { let empty: [u8; 0] = []; assert_eq!(*UBig::from_be_bytes(&[]).to_be_bytes(), empty); + assert_eq!(*UBig::ONE.to_be_bytes(), [1]); assert_eq!(*UBig::from_be_bytes(&[0; 100]).to_be_bytes(), empty); assert_eq!(*UBig::from_be_bytes(&[0, 1, 2, 3]).to_be_bytes(), [1, 2, 3]); let bytes = [ 100, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, ]; assert_eq!(*UBig::from_be_bytes(&bytes).to_be_bytes(), bytes); + + assert_eq!(*IBig::ONE.to_be_bytes(), [1]); + assert_eq!(*IBig::ZERO.to_be_bytes(), empty); + assert_eq!(*IBig::NEG_ONE.to_be_bytes(), [255]); + assert_eq!(IBig::from_be_bytes(&[255]), IBig::NEG_ONE); + assert_eq!(IBig::from_be_bytes(&[255; 100]), IBig::NEG_ONE); + let bytes = [ + 200, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + ]; + assert_eq!(*IBig::from_be_bytes(&bytes).to_be_bytes(), bytes); } #[test] From 503fb10e8f7c1efe325c115efbdc8db0c1f94103 Mon Sep 17 00:00:00 2001 From: Jacob Date: Sun, 21 Jan 2024 21:33:19 +0800 Subject: [PATCH 03/15] Basic implementations for Python binding --- python/Cargo.toml | 9 +- python/src/int.rs | 224 +++++++++++++++++++++++++++++++++----------- python/src/lib.rs | 2 + python/src/types.rs | 33 +++++-- python/src/utils.rs | 16 ++++ 5 files changed, 221 insertions(+), 63 deletions(-) diff --git a/python/Cargo.toml b/python/Cargo.toml index 430f760..81e65d2 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -17,10 +17,11 @@ rust-version = "1.61" all-features = true [dependencies] -dashu-base = { version = "0.4.0", default-features = false, path = "../base" } -dashu-int = { version = "0.4.1", default-features = false, path = "../integer" } -dashu-float = { version = "0.4.2", default-features = false, path = "../float" } -dashu-ratio = { version = "0.4.1", default-features = false, path = "../rational", features = ['dashu-float'] } +dashu-base = { version = "0.4.0", path = "../base", features = ["std"]} +dashu-int = { version = "0.4.1", path = "../integer", features = ["std", "num-order"] } +dashu-float = { version = "0.4.2", path = "../float", features = ["std", "num-order"] } +dashu-ratio = { version = "0.4.1", path = "../rational", features = ["std", "num-order", "dashu-float"] } +num-order = "1.2.0" _num-modular = { optional = true, version = "0.6.1", package = "num-modular", default-features = false } diff --git a/python/src/int.rs b/python/src/int.rs index 4d31801..c694b78 100644 --- a/python/src/int.rs +++ b/python/src/int.rs @@ -1,71 +1,189 @@ -use crate::types::{UPy, IPy}; +use std::collections::hash_map::DefaultHasher; +use std::hash::Hasher; -use pyo3::prelude::*; -use pyo3::exceptions::{PyTypeError, PyValueError}; +use pyo3::exceptions::{PyOverflowError, PyTypeError}; use pyo3::ffi; -use pyo3::types::PyLong; +use pyo3::intern; +use pyo3::prelude::*; +use pyo3::types::{PyBytes, PyDict, PyLong}; + +use crate::types::{IPy, UPy, PyWords}; +use dashu_int::{IBig, UBig}; +use num_order::NumHash; + +fn py_to_long_or_big(ob: &PyAny) -> PyResult<(i64, bool)> { + let py = ob.py(); + + unsafe { + let ptr = ob.as_ptr(); + if ffi::PyLong_Check(ptr) == 0 { + return Err(PyTypeError::new_err( + "only Python integers can be automatically converted to an UBig instance.", + )); + } + + let mut overflow: i32 = 0; + let v = ffi::PyLong_AsLongLongAndOverflow(ptr, &mut overflow); + + if v == -1 && PyErr::occurred(py) { + Err(PyErr::fetch(py)) + } else { + Ok((v, overflow != 0)) + } + } +} + +impl UPy { + // Conversion from python integer object. + // + // The most efficient way here is to use ffi::_PyLong_AsByteArray. + // However, the conversion should not performed frequently, so the stable + // API `to_bytes` is preferred here. + fn wrap(ob: &PyAny) -> PyResult { + let py = ob.py(); + + let (v, overflow) = py_to_long_or_big(ob)?; + + if !overflow { + if let Ok(n) = u64::try_from(v) { + Ok(UPy(UBig::from(n))) + } else { + Err(PyOverflowError::new_err("can't convert negative int to unsigned")) + } + } else { + let bit_len: usize = ob.call_method0(intern!(py, "bit_length"))?.extract()?; + let byte_len = (bit_len + 7) / 8; + let bytes: &PyBytes = ob + .call_method1(intern!(py, "to_bytes"), (byte_len, intern!(py, "little")))? + .downcast()?; + Ok(UPy(UBig::from_le_bytes(bytes.as_bytes()))) + } + } +} #[pymethods] impl UPy { #[new] - fn new(obj: &PyAny) -> PyResult { - ::extract(obj) - // If #[cfg(Py_LIMITED_API)], then convert from bigint is allowed - // If #[cfg(not(Py_LIMITED_API))], then only convert from long is allowed + fn __new__(ob: &PyAny, radix: Option) -> PyResult { + let string: PyResult<&str> = ob.extract(); + if let Ok(s) = string { + let n = if let Some(r) = radix { + UBig::from_str_radix(s, r) + } else { + UBig::from_str_with_radix_prefix(s).map(|v| v.0) + }; + Ok(UPy(n.map_err(crate::utils::parse_error_to_py)?)) + } else { + if radix.is_some() { + Err(PyTypeError::new_err("can't convert non-string with explicit base")) + } else { + Self::wrap(ob) + } + } + } + fn __repr__(&self) -> String { + format!("", self.0) + } + fn __str__(&self) -> String { + format!("{}", self.0) + } + fn __hash__(&self) -> u64 { + let mut hasher = DefaultHasher::new(); + self.0.num_hash(&mut hasher); + hasher.finish() + } + fn __int__(&self, py: Python) -> PyResult { + let bytes = self.0.to_le_bytes(); + let bytes_obj = PyBytes::new(py, &bytes); + py.get_type::() + .call_method1(intern!(py, "from_bytes"), (bytes_obj, intern!(py, "little"))) + .map(PyObject::from) } - fn unwrap(self) -> PyObject { - todo!() // To builtin int - // If #[cfg(Py_LIMITED_API)], then convert to bigint is allowed - // If #[cfg(not(Py_LIMITED_API))], then only convert to long is allowed + fn to_words(&self) -> PyWords { + todo!() + } + #[staticmethod] + fn from_words(words: &PyWords) -> Self { + todo!() } } -// conversion from python object -impl<'source> FromPyObject<'source> for UPy { - fn extract(ob: &'source PyAny) -> PyResult { +impl IPy { + // Conversion from python integer object. + // + // The most efficient way here is to use ffi::_PyLong_AsByteArray. + // However, the conversion should not performed frequently, so the stable + // API `to_bytes` is preferred here. + fn wrap(ob: &PyAny) -> PyResult { let py = ob.py(); - unsafe { - let ptr = ob.as_ptr(); - if ffi::PyLong_Check(ptr) == 0 { - return Err(PyValueError::new_err( - "Only Python integers can be automatically converted to an UBig instance." - )); - } - // input is integer - let mut overflow = 0; - let v = ffi::PyLong_AsLongLongAndOverflow(ptr, &mut overflow); - if v == -1 && PyErr::occurred(py) { - Err(PyErr::fetch(py)) - } else if overflow != 0 { - // some code below is from https://github.com/PyO3/pyo3/blob/main/src/conversions/num_bigint.rs - let n_bits = ffi::_PyLong_NumBits(ptr) as usize; - let n_bytes = match n_bits { - usize::MAX => return Err(PyErr::fetch(py)), - 0 => 0, - n => (n as usize) / 8 + 1, - }; - let long_ptr = ptr as *mut ffi::PyLongObject; - let num_big = if n_bytes <= 64 { - let mut buffer = [0; 64]; - let bptr = buffer.as_mut_ptr(); - if ffi::_PyLong_AsByteArray(long_ptr, bptr, n_bytes, 1, 1) == -1 { - return Err(PyErr::fetch(py)); - } - BigInt::from_signed_bytes_le(&buffer[..n_bytes]) - } else { - let mut buffer = vec![0; n_bytes]; - let bptr = buffer.as_mut_ptr(); - if ffi::_PyLong_AsByteArray(long_ptr, bptr, n_bytes, 1, 1) == -1 { - return Err(PyErr::fetch(py)); - } - BigInt::from_signed_bytes_le(&buffer) - }; - Ok(SubjectInput(BInt(num_big))) + let (v, overflow) = py_to_long_or_big(ob)?; + + if !overflow { + Ok(IPy(IBig::from(v))) + } else { + let bit_len: usize = ob.call_method0(intern!(py, "bit_length"))?.extract()?; + let byte_len = (bit_len + 7) / 8; + + let kwargs = PyDict::new(py); + kwargs.set_item(intern!(py, "signed"), true).unwrap(); + let bytes: &PyBytes = ob + .call_method( + intern!(py, "to_bytes"), + (byte_len, intern!(py, "little")), + Some(kwargs), + )? + .downcast()?; + Ok(IPy(IBig::from_le_bytes(bytes.as_bytes()))) + } + } +} + +#[pymethods] +impl IPy { + #[new] + #[inline] + fn __new__(ob: &PyAny, radix: Option) -> PyResult { + let string: PyResult<&str> = ob.extract(); + if let Ok(s) = string { + let n = if let Some(r) = radix { + IBig::from_str_radix(s, r) + } else { + IBig::from_str_with_radix_prefix(s).map(|v| v.0) + }; + Ok(IPy(n.map_err(crate::utils::parse_error_to_py)?)) + } else { + if radix.is_some() { + Err(PyTypeError::new_err("can't convert non-string with explicit base")) } else { - Ok(SubjectInput(Int(v))) + Self::wrap(ob) } } } + fn __repr__(&self) -> String { + format!("", self.0) + } + fn __str__(&self) -> String { + format!("{}", self.0) + } + fn __hash__(&self) -> u64 { + let mut hasher = DefaultHasher::new(); + self.0.num_hash(&mut hasher); + hasher.finish() + } + fn __int__(&self, py: Python) -> PyResult { + let bytes = self.0.to_le_bytes(); + let bytes_obj = PyBytes::new(py, &bytes); + + let kwargs = PyDict::new(py); + kwargs.set_item(intern!(py, "signed"), true).unwrap(); + py.get_type::() + .call_method( + intern!(py, "from_bytes"), + (bytes_obj, intern!(py, "little")), + Some(kwargs), + ) + .map(PyObject::from) + } } diff --git a/python/src/lib.rs b/python/src/lib.rs index db409ee..4e994a4 100644 --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -9,6 +9,8 @@ use pyo3::prelude::*; /// import the module. #[pymodule] fn dashu(_py: Python<'_>, m: &PyModule) -> PyResult<()> { + m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/python/src/types.rs b/python/src/types.rs index b69cc86..1453b4a 100644 --- a/python/src/types.rs +++ b/python/src/types.rs @@ -1,14 +1,32 @@ +use std::vec::Vec; use pyo3::prelude::*; -use dashu_int::{UBig, IBig}; +use dashu_base::Sign; use dashu_float::DBig; +use dashu_int::{IBig, UBig}; use dashu_ratio::RBig; type FBig = dashu_float::FBig; +#[pyclass] +pub enum PySign { + Positive, + Negative +} + +impl From for PySign { + #[inline] + fn from(value: Sign) -> Self { + match value { + Sign::Positive => Self::Positive, + Sign::Negative => Self::Negative + } + } +} + /// This struct is used for representing [UBig] in Python #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] #[pyclass(name = "UBig")] -pub struct UPy(UBig); +pub struct UPy(pub UBig); impl From for UPy { fn from(n: UBig) -> Self { @@ -19,7 +37,7 @@ impl From for UPy { /// This struct is used for representing [IBig] in Python #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] #[pyclass(name = "IBig")] -pub struct IPy(IBig); +pub struct IPy(pub IBig); impl From for IPy { fn from(n: IBig) -> Self { @@ -30,7 +48,7 @@ impl From for IPy { /// This struct is used for representing [FBig] in Python #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] #[pyclass(name = "FBig")] -pub struct FPy(FBig); +pub struct FPy(pub FBig); impl From for FPy { fn from(n: FBig) -> Self { @@ -41,7 +59,7 @@ impl From for FPy { /// This struct is used for representing [DBig] in Python #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] #[pyclass(name = "DBig")] -pub struct DPy(DBig); +pub struct DPy(pub DBig); impl From for DPy { fn from(n: DBig) -> Self { @@ -52,10 +70,13 @@ impl From for DPy { /// This struct is used for representing [RBig] in Python #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] #[pyclass(name = "RBig")] -pub struct RPy(RBig); +pub struct RPy(pub RBig); impl From for RPy { fn from(n: RBig) -> Self { RPy(n) } } + +#[pyclass] +pub struct PyWords(Vec); diff --git a/python/src/utils.rs b/python/src/utils.rs index e69de29..9b3a568 100644 --- a/python/src/utils.rs +++ b/python/src/utils.rs @@ -0,0 +1,16 @@ +use dashu_base::ParseError; +use pyo3::PyErr; +use pyo3::exceptions::PySyntaxError; + +pub fn parse_error_to_py(error: ParseError) -> PyErr { + let expl = match error { + ParseError::NoDigits => "no valid digits in the string.", + ParseError::InvalidDigit => "invalid digit for the given radix.", + ParseError::UnsupportedRadix => "the radix is not supported.", + ParseError::InconsistentRadix => "the radices of different components of the number are different.", + }; + + PySyntaxError::new_err(expl).into() +} + +// TODO: split_dword, double_word, etc. From 17a652dbd2c92f7ca22fbf4b15293b3c1333e42a Mon Sep 17 00:00:00 2001 From: Jacob Date: Tue, 23 Jan 2024 23:16:54 +0800 Subject: [PATCH 04/15] Add `ones` for UBig --- integer/CHANGELOG.md | 1 + integer/src/buffer.rs | 16 ++++- integer/src/repr.rs | 27 ++++++- integer/src/ubig.rs | 16 +++++ integer/tests/bits.rs | 12 +++- python/src/int.rs | 164 +++++++++++++++++++++++++++++++++--------- python/src/lib.rs | 1 + python/src/types.rs | 7 +- python/src/utils.rs | 12 +++- python/src/words.rs | 30 ++++++++ 10 files changed, 238 insertions(+), 48 deletions(-) create mode 100644 python/src/words.rs diff --git a/integer/CHANGELOG.md b/integer/CHANGELOG.md index 135bd1d..8ddc1cb 100644 --- a/integer/CHANGELOG.md +++ b/integer/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Add `UBig::ones`. - Implement `TryFrom` and `TryFrom` for `f32`/`f64`. - Implement `IBig::{from_le_bytes, from_be_bytes}` and `IBig::{to_le_bytes, to_be_bytes}`. diff --git a/integer/src/buffer.rs b/integer/src/buffer.rs index 7c63192..92037a7 100644 --- a/integer/src/buffer.rs +++ b/integer/src/buffer.rs @@ -222,12 +222,12 @@ impl Buffer { } } - /// Append `n` zeros. + /// Append `n` same elements, specified by the generic param. /// /// # Panics /// /// Panics if there is not enough capacity. - pub fn push_zeros(&mut self, n: usize) { + pub fn push_repeat(&mut self, n: usize) { assert!(n <= self.capacity - self.len); // SAFETY: it's checked that n + self.len <= self.capacity @@ -235,13 +235,23 @@ impl Buffer { unsafe { let mut ptr = self.ptr.as_ptr().add(self.len); for _ in 0..n { - ptr::write(ptr, 0); + ptr::write(ptr, ELEM); ptr = ptr.add(1); } self.len += n; } } + /// Append `n` zeros. + /// + /// # Panics + /// + /// Panics if there is not enough capacity. + #[inline] + pub fn push_zeros(&mut self, n: usize) { + self.push_repeat::<0>(n); + } + /// Insert `n` zeros in front. /// /// # Panics diff --git a/integer/src/repr.rs b/integer/src/repr.rs index 74e1bf4..4458653 100644 --- a/integer/src/repr.rs +++ b/integer/src/repr.rs @@ -3,8 +3,8 @@ use crate::{ arch::word::{DoubleWord, Word}, buffer::Buffer, - primitive::{double_word, split_dword}, - Sign, + primitive::{double_word, split_dword, WORD_BITS_USIZE, DWORD_BITS_USIZE}, + Sign, math::{ones_word, ones_dword}, }; use core::{ fmt::{self, Write}, @@ -327,7 +327,7 @@ impl Repr { // there will be no reallocation here. buffer.shrink_to_fit(); - // SAFETY: the length has been checked and capacity >= lenght, + // SAFETY: the length has been checked and capacity >= length, // so capacity is nonzero and larger than 2 unsafe { mem::transmute(buffer) } } @@ -412,6 +412,27 @@ impl Repr { Self::from_word(1).with_sign(Sign::Negative) } + /// Create a `Repr` with n one bits + pub fn ones(n: usize) -> Self { + if n < WORD_BITS_USIZE { + Self::from_word(ones_word(n as _)) + } else if n < DWORD_BITS_USIZE { + Self::from_dword(ones_dword(n as _)) + } else { + let lo_words = n / WORD_BITS_USIZE; + let hi_bits = n % WORD_BITS_USIZE; + let mut buffer = Buffer::allocate(lo_words + 1); + buffer.push_repeat::<{ Word::MAX }>(lo_words); + if hi_bits > 0 { + buffer.push(ones_word(hi_bits as _)); + } + + // SAFETY: the bit length has been checked and capacity >= length, + // so capacity is nonzero and larger than 2 + unsafe { mem::transmute(buffer) } + } + } + /// Flip the sign bit of the Repr and return it pub const fn neg(mut self) -> Self { if !self.is_zero() { diff --git a/integer/src/ubig.rs b/integer/src/ubig.rs index 94e26fd..6bc1dc6 100644 --- a/integer/src/ubig.rs +++ b/integer/src/ubig.rs @@ -195,6 +195,22 @@ impl UBig { pub const fn is_one(&self) -> bool { self.0.is_one() } + + /// Create an integer with `n` consecutive one bits (i.e. 2^n - 1). + /// + /// # Examples + /// + /// ``` + /// # use dashu_int::UBig; + /// let mut n = UBig::ZERO; + /// n.set_bit(20); + /// n -= UBig::ONE; + /// assert_eq!(UBig::ones(20), n); + /// ``` + #[inline] + pub fn ones(n: usize) -> Self { + Self(Repr::ones(n)) + } } // This custom implementation is necessary due to https://github.com/rust-lang/rust/issues/98374 diff --git a/integer/tests/bits.rs b/integer/tests/bits.rs index 406f1b8..adb1131 100644 --- a/integer/tests/bits.rs +++ b/integer/tests/bits.rs @@ -1,7 +1,17 @@ -use dashu_int::{ops::*, IBig}; +use dashu_int::{ops::*, UBig, IBig}; mod helper_macros; +#[test] +fn test_ones() { + assert_eq!(UBig::ones(0), ubig!(0)); + assert_eq!(UBig::ones(1), ubig!(1)); + assert_eq!(UBig::ones(2), ubig!(3)); + assert_eq!(UBig::ones(16), ubig!(0xffff)); + assert_eq!(UBig::ones(128), UBig::from(u128::MAX)); + assert_eq!(UBig::ones(200), (ubig!(1) << 200) - ubig!(1)); +} + #[test] #[allow(clippy::bool_assert_comparison)] fn test_bit() { diff --git a/python/src/int.rs b/python/src/int.rs index c694b78..f2135fb 100644 --- a/python/src/int.rs +++ b/python/src/int.rs @@ -1,27 +1,33 @@ use std::collections::hash_map::DefaultHasher; use std::hash::Hasher; -use pyo3::exceptions::{PyOverflowError, PyTypeError}; -use pyo3::ffi; -use pyo3::intern; +use dashu_base::BitTest; +use pyo3::exceptions::{PyIndexError, PyNotImplementedError}; use pyo3::prelude::*; -use pyo3::types::{PyBytes, PyDict, PyLong}; +use pyo3::types::PySlice; +use pyo3::{ + exceptions::{PyOverflowError, PyTypeError}, + ffi, intern, + types::{PyBytes, PyDict, PyLong}, +}; -use crate::types::{IPy, UPy, PyWords}; +use crate::types::{IPy, PyWords, UPy}; use dashu_int::{IBig, UBig}; use num_order::NumHash; +// error messages +const ERRMSG_LENGTH_TOO_LARGE: &'static str = "the integer has too many bits for indexing"; +const ERRMSG_STEPSIZE_TOO_LARGE: &'static str = "the integer has too many bits for indexing"; +const ERRMSG_UBIG_WRONG_SRC_TYPE: &'static str = "only integers or strings can be converted to a UBig instance"; +const ERRMSG_INT_WITH_RADIX: &'static str = "can't convert non-string with explicit base"; +const ERRMSG_WRONG_INDEX_TYPE: &'static str = "indices must be integers or slices"; +const ERRMSG_UBIG_FROM_NEG: &'static str = "can't convert negative int to unsigned"; + fn py_to_long_or_big(ob: &PyAny) -> PyResult<(i64, bool)> { let py = ob.py(); unsafe { let ptr = ob.as_ptr(); - if ffi::PyLong_Check(ptr) == 0 { - return Err(PyTypeError::new_err( - "only Python integers can be automatically converted to an UBig instance.", - )); - } - let mut overflow: i32 = 0; let v = ffi::PyLong_AsLongLongAndOverflow(ptr, &mut overflow); @@ -34,23 +40,21 @@ fn py_to_long_or_big(ob: &PyAny) -> PyResult<(i64, bool)> { } impl UPy { - // Conversion from python integer object. + // Conversion from python integer object, without type checking. // // The most efficient way here is to use ffi::_PyLong_AsByteArray. // However, the conversion should not performed frequently, so the stable // API `to_bytes` is preferred here. fn wrap(ob: &PyAny) -> PyResult { - let py = ob.py(); - let (v, overflow) = py_to_long_or_big(ob)?; - if !overflow { if let Ok(n) = u64::try_from(v) { Ok(UPy(UBig::from(n))) } else { - Err(PyOverflowError::new_err("can't convert negative int to unsigned")) + Err(PyOverflowError::new_err(ERRMSG_UBIG_FROM_NEG)) } } else { + let py = ob.py(); let bit_len: usize = ob.call_method0(intern!(py, "bit_length"))?.extract()?; let byte_len = (bit_len + 7) / 8; let bytes: &PyBytes = ob @@ -65,8 +69,13 @@ impl UPy { impl UPy { #[new] fn __new__(ob: &PyAny, radix: Option) -> PyResult { - let string: PyResult<&str> = ob.extract(); - if let Ok(s) = string { + if ob.is_instance_of::() { + if radix.is_some() { + Err(PyTypeError::new_err(ERRMSG_INT_WITH_RADIX)) + } else { + Self::wrap(ob) + } + } else if let Ok(s) = ob.extract() { let n = if let Some(r) = radix { UBig::from_str_radix(s, r) } else { @@ -74,11 +83,7 @@ impl UPy { }; Ok(UPy(n.map_err(crate::utils::parse_error_to_py)?)) } else { - if radix.is_some() { - Err(PyTypeError::new_err("can't convert non-string with explicit base")) - } else { - Self::wrap(ob) - } + Err(PyTypeError::new_err(ERRMSG_UBIG_WRONG_SRC_TYPE)) } } fn __repr__(&self) -> String { @@ -87,6 +92,9 @@ impl UPy { fn __str__(&self) -> String { format!("{}", self.0) } + fn __format__(&self) { + todo!() + } fn __hash__(&self) -> u64 { let mut hasher = DefaultHasher::new(); self.0.num_hash(&mut hasher); @@ -100,29 +108,101 @@ impl UPy { .map(PyObject::from) } + // use as a bit vector + fn __len__(&self) -> usize { + self.0.bit_len() + } + fn __getitem__(&self, index: &PyAny) -> PyResult { + let py = index.py(); + if let Ok(mut i) = ::extract(index) { + let i = if i <= 0 { self.0.bit_len() + i.unsigned_abs() } else { i as usize }; + Ok(self.0.bit(i).into_py(py)) + } else if let Ok(range) = index.downcast::() { + let len = self.0.bit_len().try_into().map_err(|_| PyNotImplementedError::new_err(ERRMSG_STEPSIZE_TOO_LARGE))?; + let indices = range.indices(len)?; + if indices.step != 1 { + return Err(PyNotImplementedError::new_err(ERRMSG_STEPSIZE_TOO_LARGE)); + } + + let mut data = self.0.clone(); + data.clear_high_bits(indices.stop as _); + let split = Self(data.split_bits(indices.start as _).1); + Ok(split.into_py(py)) + } else { + Err(PyTypeError::new_err(ERRMSG_WRONG_INDEX_TYPE)) + } + + } + fn __setitem__(&mut self, index: &PyAny, set: bool) -> PyResult<()> { + if let Ok(i) = ::extract(index) { + let i = if i <= 0 { self.0.bit_len() + i.unsigned_abs() } else { i as usize }; + if set { + self.0.set_bit(i) + } else { + self.0.clear_bit(i) + } + Ok(()) + } else if let Ok(range) = index.downcast::() { + let len = self.0.bit_len().try_into().map_err(|_| PyNotImplementedError::new_err(ERRMSG_STEPSIZE_TOO_LARGE))?; + let indices = range.indices(len)?; + if indices.step != 1 { + return Err(PyNotImplementedError::new_err(ERRMSG_STEPSIZE_TOO_LARGE)); + } + + // shortcut for clearing high bits + if indices.stop == len as _ && !set { + self.0.clear_high_bits(indices.start as _); + } + + // here the operations rely on the And and Or ops. + // they can be optimized if UBig implements more bit operations. + if set { + let ones = indices.stop - indices.start; + let mask = UBig::ones(ones as _) << (indices.start as usize); + self.0 |= mask; + } else { + let mask_lo = UBig::ones(indices.stop as _) - UBig::ones(indices.start as _); + let mask = UBig::ones(len as _) - mask_lo; + self.0 &= mask; + } + Ok(()) + } else { + Err(PyTypeError::new_err(ERRMSG_WRONG_INDEX_TYPE)) + } + } + fn __delitem__(&mut self, index: &PyAny) { + todo!() // TODO: allow remove low or top bits + } + + // interop fn to_words(&self) -> PyWords { - todo!() + PyWords(self.0.as_words().to_vec()) } #[staticmethod] fn from_words(words: &PyWords) -> Self { + UPy(UBig::from_words(&words.0)) + } + fn to_bytes(&self, py: Python) -> PyObject { + PyBytes::new(py, &self.0.to_le_bytes()).into() + } + #[staticmethod] + fn from_bytes(bytes: &PyBytes) -> Self { todo!() } } impl IPy { - // Conversion from python integer object. + // Conversion from python integer object, without type checking. // // The most efficient way here is to use ffi::_PyLong_AsByteArray. // However, the conversion should not performed frequently, so the stable // API `to_bytes` is preferred here. fn wrap(ob: &PyAny) -> PyResult { - let py = ob.py(); - let (v, overflow) = py_to_long_or_big(ob)?; - if !overflow { Ok(IPy(IBig::from(v))) } else { + let py = ob.py(); let bit_len: usize = ob.call_method0(intern!(py, "bit_length"))?.extract()?; let byte_len = (bit_len + 7) / 8; @@ -145,8 +225,13 @@ impl IPy { #[new] #[inline] fn __new__(ob: &PyAny, radix: Option) -> PyResult { - let string: PyResult<&str> = ob.extract(); - if let Ok(s) = string { + if ob.is_instance_of::() { + if radix.is_some() { + Err(PyTypeError::new_err("can't convert non-string with explicit base")) + } else { + Self::wrap(ob) + } + } else if let Ok(s) = ob.extract() { let n = if let Some(r) = radix { IBig::from_str_radix(s, r) } else { @@ -154,11 +239,9 @@ impl IPy { }; Ok(IPy(n.map_err(crate::utils::parse_error_to_py)?)) } else { - if radix.is_some() { - Err(PyTypeError::new_err("can't convert non-string with explicit base")) - } else { - Self::wrap(ob) - } + Err(PyTypeError::new_err( + "only Python integers can be automatically converted to an IBig instance" + )) } } fn __repr__(&self) -> String { @@ -167,6 +250,9 @@ impl IPy { fn __str__(&self) -> String { format!("{}", self.0) } + fn __format__(&self) { + todo!() + } fn __hash__(&self) -> u64 { let mut hasher = DefaultHasher::new(); self.0.num_hash(&mut hasher); @@ -186,4 +272,12 @@ impl IPy { ) .map(PyObject::from) } + + // use as a bit vector with very limited capabilities + fn __len__(&self) -> usize { + self.0.bit_len() + } + fn __getitem__(&self, i: usize) -> bool { + self.0.bit(i) + } } diff --git a/python/src/lib.rs b/python/src/lib.rs index 4e994a4..180b594 100644 --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -1,6 +1,7 @@ mod int; mod types; mod utils; +mod words; use pyo3::prelude::*; diff --git a/python/src/types.rs b/python/src/types.rs index 1453b4a..ce5479e 100644 --- a/python/src/types.rs +++ b/python/src/types.rs @@ -1,4 +1,3 @@ -use std::vec::Vec; use pyo3::prelude::*; use dashu_base::Sign; @@ -10,7 +9,7 @@ type FBig = dashu_float::FBig; #[pyclass] pub enum PySign { Positive, - Negative + Negative, } impl From for PySign { @@ -18,7 +17,7 @@ impl From for PySign { fn from(value: Sign) -> Self { match value { Sign::Positive => Self::Positive, - Sign::Negative => Self::Negative + Sign::Negative => Self::Negative, } } } @@ -79,4 +78,4 @@ impl From for RPy { } #[pyclass] -pub struct PyWords(Vec); +pub struct PyWords(pub std::vec::Vec); diff --git a/python/src/utils.rs b/python/src/utils.rs index 9b3a568..3581426 100644 --- a/python/src/utils.rs +++ b/python/src/utils.rs @@ -1,16 +1,24 @@ use dashu_base::ParseError; -use pyo3::PyErr; use pyo3::exceptions::PySyntaxError; +use pyo3::ffi::PyObject; +use pyo3::{PyErr, PyAny}; pub fn parse_error_to_py(error: ParseError) -> PyErr { let expl = match error { ParseError::NoDigits => "no valid digits in the string.", ParseError::InvalidDigit => "invalid digit for the given radix.", ParseError::UnsupportedRadix => "the radix is not supported.", - ParseError::InconsistentRadix => "the radices of different components of the number are different.", + ParseError::InconsistentRadix => { + "the radices of different components of the number are different." + } }; PySyntaxError::new_err(expl).into() } +pub fn auto(ob: PyAny) -> PyObject { + // convert input automatically to corresponding type (int -> IBig, float -> FBig, decimal -> DBig, fraction -> RBig) + todo!() +} + // TODO: split_dword, double_word, etc. diff --git a/python/src/words.rs b/python/src/words.rs new file mode 100644 index 0000000..bbc745f --- /dev/null +++ b/python/src/words.rs @@ -0,0 +1,30 @@ +use crate::types::PyWords; + +use pyo3::{prelude::*, exceptions::{PyIndexError, PyTypeError}, types::PySlice}; + +use dashu_int::Word; + +#[pymethods] +impl PyWords { + fn __repr__(&self) -> String { + format!("", self.0.len()) + } + fn __str__(&self) -> String { + format!("{:?}", self.0) + } + fn __len__(&self) -> usize { + self.0.len() + } + fn __getitem__(&self, index: &PyAny) -> PyResult { + if let Ok(n) = ::extract(index) { + self.0.get(n).copied().ok_or(PyIndexError::new_err("words index out of range")) + } else if let Ok(slice) = index.downcast::() { + todo!() + } else { + Err(PyTypeError::new_err("words indices must be integers or slices")) + } + } + fn __setitem__(&mut self, index: &PyAny, value: Word) { + todo!() + } +} From 307c37f787bc1b5c5e5c846d43d254bfb58b9291 Mon Sep 17 00:00:00 2001 From: Jacob Date: Wed, 24 Jan 2024 21:03:25 +0800 Subject: [PATCH 05/15] Implement bit ops for UPy --- integer/src/repr.rs | 5 +-- integer/src/ubig.rs | 2 +- integer/tests/bits.rs | 2 +- python/dashu.pyi | 9 +++++ python/src/int.rs | 77 +++++++++++++++++++++++++++++++++------- python/src/types.rs | 11 ++++++ python/src/utils.rs | 20 ++++++++++- python/src/words.rs | 13 +++++-- python/tests/test_int.py | 49 +++++++++++++++++++++++++ 9 files changed, 167 insertions(+), 21 deletions(-) create mode 100644 python/dashu.pyi create mode 100644 python/tests/test_int.py diff --git a/integer/src/repr.rs b/integer/src/repr.rs index 4458653..dd85464 100644 --- a/integer/src/repr.rs +++ b/integer/src/repr.rs @@ -3,8 +3,9 @@ use crate::{ arch::word::{DoubleWord, Word}, buffer::Buffer, - primitive::{double_word, split_dword, WORD_BITS_USIZE, DWORD_BITS_USIZE}, - Sign, math::{ones_word, ones_dword}, + math::{ones_dword, ones_word}, + primitive::{double_word, split_dword, DWORD_BITS_USIZE, WORD_BITS_USIZE}, + Sign, }; use core::{ fmt::{self, Write}, diff --git a/integer/src/ubig.rs b/integer/src/ubig.rs index 6bc1dc6..2c3cbb4 100644 --- a/integer/src/ubig.rs +++ b/integer/src/ubig.rs @@ -197,7 +197,7 @@ impl UBig { } /// Create an integer with `n` consecutive one bits (i.e. 2^n - 1). - /// + /// /// # Examples /// /// ``` diff --git a/integer/tests/bits.rs b/integer/tests/bits.rs index adb1131..e6fe477 100644 --- a/integer/tests/bits.rs +++ b/integer/tests/bits.rs @@ -1,4 +1,4 @@ -use dashu_int::{ops::*, UBig, IBig}; +use dashu_int::{ops::*, IBig, UBig}; mod helper_macros; diff --git a/python/dashu.pyi b/python/dashu.pyi new file mode 100644 index 0000000..5f33982 --- /dev/null +++ b/python/dashu.pyi @@ -0,0 +1,9 @@ +from typing import Union + +class Words: + pass + +class UBig: + def __new__(obj: Union[int, str]): ... + def to_words(self) -> Words: ... + def from_words(self, words: Words): ... diff --git a/python/src/int.rs b/python/src/int.rs index f2135fb..cd341bd 100644 --- a/python/src/int.rs +++ b/python/src/int.rs @@ -1,5 +1,6 @@ use std::collections::hash_map::DefaultHasher; use std::hash::Hasher; +use std::os::raw::c_longlong; use dashu_base::BitTest; use pyo3::exceptions::{PyIndexError, PyNotImplementedError}; @@ -12,18 +13,22 @@ use pyo3::{ }; use crate::types::{IPy, PyWords, UPy}; +use crate::utils::parse_signed_index; use dashu_int::{IBig, UBig}; use num_order::NumHash; // error messages const ERRMSG_LENGTH_TOO_LARGE: &'static str = "the integer has too many bits for indexing"; -const ERRMSG_STEPSIZE_TOO_LARGE: &'static str = "the integer has too many bits for indexing"; -const ERRMSG_UBIG_WRONG_SRC_TYPE: &'static str = "only integers or strings can be converted to a UBig instance"; +const ERRMSG_STEPSIZE_TOO_LARGE: &'static str = + "bit slicing with step size larger than 1 is not supported yet"; +const ERRMSG_UBIG_WRONG_SRC_TYPE: &'static str = + "only integers or strings can be converted to a UBig instance"; const ERRMSG_INT_WITH_RADIX: &'static str = "can't convert non-string with explicit base"; const ERRMSG_WRONG_INDEX_TYPE: &'static str = "indices must be integers or slices"; const ERRMSG_UBIG_FROM_NEG: &'static str = "can't convert negative int to unsigned"; +const ERRMSG_UBIG_BITS_OOR: &'static str = "bits index out of range"; -fn py_to_long_or_big(ob: &PyAny) -> PyResult<(i64, bool)> { +fn py_to_long_or_big(ob: &PyAny) -> PyResult<(c_longlong, bool)> { let py = ob.py(); unsafe { @@ -114,11 +119,16 @@ impl UPy { } fn __getitem__(&self, index: &PyAny) -> PyResult { let py = index.py(); - if let Ok(mut i) = ::extract(index) { - let i = if i <= 0 { self.0.bit_len() + i.unsigned_abs() } else { i as usize }; + if let Ok(i) = ::extract(index) { + let i = parse_signed_index(i, self.0.bit_len(), true) + .ok_or(PyIndexError::new_err(ERRMSG_UBIG_BITS_OOR))?; Ok(self.0.bit(i).into_py(py)) } else if let Ok(range) = index.downcast::() { - let len = self.0.bit_len().try_into().map_err(|_| PyNotImplementedError::new_err(ERRMSG_STEPSIZE_TOO_LARGE))?; + let len = self + .0 + .bit_len() + .try_into() + .map_err(|_| PyNotImplementedError::new_err(ERRMSG_LENGTH_TOO_LARGE))?; let indices = range.indices(len)?; if indices.step != 1 { return Err(PyNotImplementedError::new_err(ERRMSG_STEPSIZE_TOO_LARGE)); @@ -131,11 +141,11 @@ impl UPy { } else { Err(PyTypeError::new_err(ERRMSG_WRONG_INDEX_TYPE)) } - } fn __setitem__(&mut self, index: &PyAny, set: bool) -> PyResult<()> { if let Ok(i) = ::extract(index) { - let i = if i <= 0 { self.0.bit_len() + i.unsigned_abs() } else { i as usize }; + let i = parse_signed_index(i, self.0.bit_len(), true) + .ok_or(PyIndexError::new_err(ERRMSG_UBIG_BITS_OOR))?; if set { self.0.set_bit(i) } else { @@ -143,7 +153,11 @@ impl UPy { } Ok(()) } else if let Ok(range) = index.downcast::() { - let len = self.0.bit_len().try_into().map_err(|_| PyNotImplementedError::new_err(ERRMSG_STEPSIZE_TOO_LARGE))?; + let len = self + .0 + .bit_len() + .try_into() + .map_err(|_| PyNotImplementedError::new_err(ERRMSG_LENGTH_TOO_LARGE))?; let indices = range.indices(len)?; if indices.step != 1 { return Err(PyNotImplementedError::new_err(ERRMSG_STEPSIZE_TOO_LARGE)); @@ -170,16 +184,53 @@ impl UPy { Err(PyTypeError::new_err(ERRMSG_WRONG_INDEX_TYPE)) } } - fn __delitem__(&mut self, index: &PyAny) { - todo!() // TODO: allow remove low or top bits + fn __delitem__(&mut self, index: &PyAny) -> PyResult<()> { + fn remove_bits_in_middle(u: &mut UBig, start: usize, end: usize) { + let (mut left, right) = core::mem::take(u).split_bits(end); + left.clear_high_bits(end - start); + *u = (right << start) | left; + } + + if let Ok(i) = ::extract(index) { + let i = parse_signed_index(i, self.0.bit_len(), true) + .ok_or(PyIndexError::new_err(ERRMSG_UBIG_BITS_OOR))?; + remove_bits_in_middle(&mut self.0, i, i + 1); + Ok(()) + } else if let Ok(range) = index.downcast::() { + let len = self + .0 + .bit_len() + .try_into() + .map_err(|_| PyNotImplementedError::new_err(ERRMSG_LENGTH_TOO_LARGE))?; + let indices = range.indices(len)?; + if indices.step != 1 { + return Err(PyNotImplementedError::new_err(ERRMSG_STEPSIZE_TOO_LARGE)); + } + + // shortcut for clearing high bits + if indices.stop == len as _ { + self.0.clear_high_bits(indices.start as _); + } else if indices.start == 0 { + self.0 >>= indices.stop as usize; + } else { + remove_bits_in_middle(&mut self.0, indices.start as _, indices.stop as _); + } + Ok(()) + } else { + Err(PyTypeError::new_err(ERRMSG_WRONG_INDEX_TYPE)) + } } - // interop + /********** interop **********/ + + /// Get the underlying words representing this integer fn to_words(&self) -> PyWords { PyWords(self.0.as_words().to_vec()) } + /// Create an integer from a list of words #[staticmethod] fn from_words(words: &PyWords) -> Self { + // TODO: accept a list of integers, using Vec::extract UPy(UBig::from_words(&words.0)) } fn to_bytes(&self, py: Python) -> PyObject { @@ -240,7 +291,7 @@ impl IPy { Ok(IPy(n.map_err(crate::utils::parse_error_to_py)?)) } else { Err(PyTypeError::new_err( - "only Python integers can be automatically converted to an IBig instance" + "only Python integers can be automatically converted to an IBig instance", )) } } diff --git a/python/src/types.rs b/python/src/types.rs index ce5479e..8b8215a 100644 --- a/python/src/types.rs +++ b/python/src/types.rs @@ -1,4 +1,5 @@ use pyo3::prelude::*; +use std::os::raw::{c_double, c_longlong}; use dashu_base::Sign; use dashu_float::DBig; @@ -79,3 +80,13 @@ impl From for RPy { #[pyclass] pub struct PyWords(pub std::vec::Vec); + +/// An input type that accepts all possible numeric types from Python +pub enum UniInput<'a> { + SmallInt(c_longlong), // from int + BigInt(&'a IBig), // from UPy + BigIntOwned(IBig), // from int + SmallFloat(c_double), // from float + BigFloat(&'a FBig), // from FPy + BigDecimal(&'a DBig), // from decimal.Decimal or DPy +} diff --git a/python/src/utils.rs b/python/src/utils.rs index 3581426..ed7b1bf 100644 --- a/python/src/utils.rs +++ b/python/src/utils.rs @@ -1,7 +1,25 @@ use dashu_base::ParseError; use pyo3::exceptions::PySyntaxError; use pyo3::ffi::PyObject; -use pyo3::{PyErr, PyAny}; +use pyo3::{PyAny, PyErr}; + +pub fn parse_signed_index(index: isize, length: usize, unlimited: bool) -> Option { + if index >= 0 { + let i = index as usize; + if unlimited || i <= length { + Some(i) + } else { + None + } + } else { + let i = index.unsigned_abs(); + if i <= length { + Some(length - i) + } else { + None + } + } +} pub fn parse_error_to_py(error: ParseError) -> PyErr { let expl = match error { diff --git a/python/src/words.rs b/python/src/words.rs index bbc745f..6ae2b77 100644 --- a/python/src/words.rs +++ b/python/src/words.rs @@ -1,6 +1,10 @@ use crate::types::PyWords; -use pyo3::{prelude::*, exceptions::{PyIndexError, PyTypeError}, types::PySlice}; +use pyo3::{ + exceptions::{PyIndexError, PyTypeError}, + prelude::*, + types::PySlice, +}; use dashu_int::Word; @@ -17,12 +21,15 @@ impl PyWords { } fn __getitem__(&self, index: &PyAny) -> PyResult { if let Ok(n) = ::extract(index) { - self.0.get(n).copied().ok_or(PyIndexError::new_err("words index out of range")) + self.0 + .get(n) + .copied() + .ok_or(PyIndexError::new_err("words index out of range")) } else if let Ok(slice) = index.downcast::() { todo!() } else { Err(PyTypeError::new_err("words indices must be integers or slices")) - } + } } fn __setitem__(&mut self, index: &PyAny, value: Word) { todo!() diff --git a/python/tests/test_int.py b/python/tests/test_int.py new file mode 100644 index 0000000..90854aa --- /dev/null +++ b/python/tests/test_int.py @@ -0,0 +1,49 @@ +from dashu import * + +def test_bit_ops(): + ##### getters ##### + n = UBig(12) # 0b1100 + assert not n[0] and not n[1] and n[2] and n[3] + assert int(n[:2]) == 0 and int(n[2:]) == 3 + assert int(n[:3]) == 4 and int(n[3:]) == 1 + + ##### setters ##### + n = UBig(12) + n[0] = True + assert int(n) == 0b1101 + n[10] = True + assert int(n) == 0b10000001101 + + n = UBig(12) + n[:2] = True + assert int(n) == 0b1111 + n[2:] = False + assert int(n) == 0b0011 + + n = UBig(12) + n[1:3] = True + assert int(n) == 0b1110 + + ##### delete ##### + n = UBig(12) + del n[0] + assert int(n) == 0b110 + + n = UBig(12) + del n[2] + assert int(n) == 0b100 + + n = UBig(12) + del n[:2] + assert int(n) == 0b11 + + n = UBig(12) + del n[2:] + assert int(n) == 0b00 + + n = UBig(12) + del n[1:3] + assert int(n) == 0b10 + +if __name__ == "__main__": + test_bit_ops() From 00e89e43acaadf472de0f35d027719f204ad0542 Mon Sep 17 00:00:00 2001 From: Jacob Date: Sat, 27 Jan 2024 22:47:50 +0800 Subject: [PATCH 06/15] Implement basic wrappers --- integer/CHANGELOG.md | 1 + integer/src/fmt/mod.rs | 6 +- python/src/convert.rs | 199 ++++++++++++++++++++++++++++++ python/src/float.rs | 107 +++++++++++++++++ python/src/int.rs | 266 +++++++++++++++++++++++------------------ python/src/lib.rs | 5 + python/src/ratio.rs | 57 +++++++++ python/src/types.rs | 10 +- python/src/utils.rs | 61 ++++------ 9 files changed, 553 insertions(+), 159 deletions(-) create mode 100644 python/src/convert.rs diff --git a/integer/CHANGELOG.md b/integer/CHANGELOG.md index 8ddc1cb..f8ff783 100644 --- a/integer/CHANGELOG.md +++ b/integer/CHANGELOG.md @@ -5,6 +5,7 @@ - Add `UBig::ones`. - Implement `TryFrom` and `TryFrom` for `f32`/`f64`. - Implement `IBig::{from_le_bytes, from_be_bytes}` and `IBig::{to_le_bytes, to_be_bytes}`. +- The alterative `Debug` output of `UBig` and `IBig` will include `(digits: x, bits: y)` instead of `(x digits, y bits)`. ## 0.4.1 diff --git a/integer/src/fmt/mod.rs b/integer/src/fmt/mod.rs index 48a86f7..311574b 100644 --- a/integer/src/fmt/mod.rs +++ b/integer/src/fmt/mod.rs @@ -417,11 +417,11 @@ impl DoubleEnd<'_> { } if self.verbose { - f.write_str(" (")?; + f.write_str(" (digits: ")?; non_power_two::write_usize_decimals(f, digits)?; - f.write_str(" digits, ")?; + f.write_str(", bits: ")?; non_power_two::write_usize_decimals(f, self.magnitude.bit_len())?; - f.write_str(" bits)")?; + f.write_str(")")?; } Ok(()) diff --git a/python/src/convert.rs b/python/src/convert.rs new file mode 100644 index 0000000..1612fdf --- /dev/null +++ b/python/src/convert.rs @@ -0,0 +1,199 @@ +//! Methods related to type conversions. +//! +//! Ideally the implementations in the module should be moved to each `dashu-*` crates, +//! but it should happen when both PyO3 and this crate have a relatively stable API. + +use pyo3::{ + exceptions::{PySyntaxError, PyTypeError, PyValueError}, + ffi, intern, + prelude::*, + types::{PyBytes, PyDict, PyFloat, PyLong}, + FromPyObject, PyAny, PyErr, PyObject, +}; +use std::os::raw::c_longlong; + +use crate::types::*; +use dashu_base::{ConversionError, ParseError}; +use dashu_float::DBig; +use dashu_int::{IBig, UBig}; +use dashu_ratio::RBig; + +pub fn parse_signed_index(index: isize, length: usize, unlimited: bool) -> Option { + if index >= 0 { + let i = index as usize; + if unlimited || i <= length { + Some(i) + } else { + None + } + } else { + let i = index.unsigned_abs(); + if i <= length { + Some(length - i) + } else { + None + } + } +} + +pub fn conversion_error_to_py(error: ConversionError) -> PyErr { + let expl = match error { + ConversionError::OutOfBounds => "the input is out of the representable range", + ConversionError::LossOfPrecision => "precision loss happened during converison", + }; + + PyValueError::new_err(expl).into() +} + +pub fn parse_error_to_py(error: ParseError) -> PyErr { + let expl = match error { + ParseError::NoDigits => "no valid digits in the string", + ParseError::InvalidDigit => "invalid digit for the given radix", + ParseError::UnsupportedRadix => "the radix is not supported", + ParseError::InconsistentRadix => { + "the radices of different components of the number are different" + } + }; + + PySyntaxError::new_err(expl).into() +} + +/// Conversion from python integer object to rust int, without type checking. +/// Returns the parsed number (when success) and the overflow flag. +pub fn parse_to_long(ob: &PyAny) -> PyResult<(c_longlong, bool)> { + let py = ob.py(); + + unsafe { + let ptr = ob.as_ptr(); + let mut overflow: i32 = 0; + let v = ffi::PyLong_AsLongLongAndOverflow(ptr, &mut overflow); + + if v == -1 && PyErr::occurred(py) { + Err(PyErr::fetch(py)) + } else { + Ok((v, overflow != 0)) + } + } +} + +/// Conversion from python integer object to UBig instance, without type checking. +pub fn parse_to_ubig(ob: &PyAny) -> PyResult { + let py = ob.py(); + let bit_len: usize = ob.call_method0(intern!(py, "bit_length"))?.extract()?; + let byte_len = (bit_len + 7) / 8; + + // The most efficient way here is to use ffi::_PyLong_AsByteArray. + // However, the conversion should not performed frequently, so the stable + // API `to_bytes` is preferred here. + let bytes: &PyBytes = ob + .call_method1(intern!(py, "to_bytes"), (byte_len, intern!(py, "little")))? + .downcast()?; + Ok(UBig::from_le_bytes(bytes.as_bytes())) +} + +/// Conversion from UBig instance to python integer object +pub fn convert_from_ubig(ob: &UBig, py: Python) -> PyResult { + let bytes = ob.to_le_bytes(); + let bytes_obj = PyBytes::new(py, &bytes); + py.get_type::() + .call_method1(intern!(py, "from_bytes"), (bytes_obj, intern!(py, "little"))) + .map(PyObject::from) +} + +/// Conversion from python integer object to IBig instance, without type checking. +pub fn parse_to_ibig(ob: &PyAny) -> PyResult { + let py = ob.py(); + let bit_len: usize = ob.call_method0(intern!(py, "bit_length"))?.extract()?; + let byte_len = (bit_len + 7) / 8; + + // The stable API `to_bytes` is also chosen over ffi::_PyLong_AsByteArray here. + let kwargs = PyDict::new(py); + kwargs.set_item(intern!(py, "signed"), true).unwrap(); + let bytes: &PyBytes = ob + .call_method(intern!(py, "to_bytes"), (byte_len, intern!(py, "little")), Some(kwargs))? + .downcast()?; + Ok(IBig::from_le_bytes(bytes.as_bytes())) +} + +/// Conversion from IBig instance to python integer object +pub fn convert_from_ibig(ob: &IBig, py: Python) -> PyResult { + let bytes = ob.to_le_bytes(); + let bytes_obj = PyBytes::new(py, &bytes); + + let kwargs = PyDict::new(py); + kwargs.set_item(intern!(py, "signed"), true).unwrap(); + py.get_type::() + .call_method(intern!(py, "from_bytes"), (bytes_obj, intern!(py, "little")), Some(kwargs)) + .map(PyObject::from) +} + +/// Conversion from decimal.Decimal object to DBig instance, without type checking. +pub fn parse_to_dbig(ob: &PyAny) -> PyResult { + // use string to convert Decimal to DBig is okay, because Decimal.__format__ will + // produce string in scientific notation. It will not produce many zeros when the + // exponent is large. + let s = ob.str()?; + Ok(DBig::from_str_native(s.to_str()?).map_err(parse_error_to_py)?) +} + +/// Conversion from fractions.Fraction object to RBig instance, without type checking. +pub fn parse_to_rbig(ob: &PyAny) -> PyResult { + let py = ob.py(); + let num = parse_to_ibig(ob.getattr(intern!(py, "numerator"))?)?; + let den = parse_to_ibig(ob.getattr(intern!(py, "denominator"))?)?; + let den: UBig = den.try_into().unwrap(); // this should be ensured by the Fraction type. + Ok(RBig::from_parts(num, den)) +} + +/// Conversion from RBig instance to fractions.Fraction object +pub fn convert_from_rbig(ob: &RBig, py: Python<'_>) -> PyResult { + let fractions = py.import(intern!(py, "fractions"))?; + let fraction_type = fractions.getattr(intern!(py, "Fraction"))?; + + let num = convert_from_ibig(ob.numerator(), py)?; + let den = convert_from_ubig(ob.denominator(), py)?; + fraction_type.call1((num, den)).map(PyObject::from) +} + +impl<'source> FromPyObject<'source> for UniInput<'source> { + fn extract(ob: &'source PyAny) -> PyResult { + if ob.is_instance_of::() { + let (v, overflow) = parse_to_long(ob)?; + if overflow { + Ok(Self::BigIntOwned(parse_to_ibig(ob)?)) + } else { + Ok(Self::SmallInt(v)) + } + } else if ob.is_instance_of::() { + let f = ob.extract()?; + Ok(Self::SmallFloat(f)) + } else if let Ok(u) = as FromPyObject>::extract(ob) { + Ok(Self::BigUint(u)) + } else if let Ok(i) = as FromPyObject>::extract(ob) { + Ok(Self::BigInt(i)) + } else if let Ok(f) = as FromPyObject>::extract(ob) { + Ok(Self::BigFloat(f)) + } else if let Ok(d) = as FromPyObject>::extract(ob) { + Ok(Self::BigDecimal(d)) + } else if let Ok(r) = as FromPyObject>::extract(ob) { + Ok(Self::BigRational(r)) + } else { + // slow path: + // get relevant Python types + let py = ob.py(); + let decimal = py.import(intern!(py, "decimal"))?; + let decimal_type = decimal.getattr(intern!(py, "Decimal"))?; + let fractions = py.import(intern!(py, "fractions"))?; + let fraction_type = fractions.getattr(intern!(py, "Fraction"))?; + + // and check whether the input is an instance of them + if ob.is_instance(decimal_type)? { + Ok(Self::BigDecimalOwned(parse_to_dbig(ob)?)) + } else if ob.is_instance(fraction_type)? { + Ok(Self::BigRationalOwned(parse_to_rbig(ob)?)) + } else { + Err(PyTypeError::new_err("the input is an invalid number or unsupported")) + } + } + } +} diff --git a/python/src/float.rs b/python/src/float.rs index e69de29..1c18cd8 100644 --- a/python/src/float.rs +++ b/python/src/float.rs @@ -0,0 +1,107 @@ +use std::collections::hash_map::DefaultHasher; +use std::hash::Hasher; + +use dashu_float::DBig; +use num_order::NumHash; +use pyo3::{exceptions::PyTypeError, intern, prelude::*, types::PyFloat}; +type FBig = dashu_float::FBig; + +use crate::{ + convert::{conversion_error_to_py, parse_error_to_py, parse_to_dbig}, + types::{DPy, FPy, IPy}, +}; + +const ERRMSG_FBIG_WRONG_SRC_TYPE: &'static str = + "only floats or strings can be used to construct an FBig instance"; +const ERRMSG_DBIG_WRONG_SRC_TYPE: &'static str = + "only Decimal instances or strings can be used to construct a DBig instance"; + +#[pymethods] +impl FPy { + #[new] + fn __new__(ob: &PyAny) -> PyResult { + if ob.is_instance_of::() { + // create from float + let f: f64 = ob.extract()?; + let f = FBig::try_from(f).map_err(conversion_error_to_py)?; + Ok(FPy(f)) + } else if let Ok(s) = ob.extract() { + // create from string + let f = FBig::from_str_native(s); + Ok(FPy(f.map_err(parse_error_to_py)?)) + } else if let Ok(obj) = as FromPyObject>::extract(ob) { + Ok(FPy(obj.0.clone())) + } else { + Err(PyTypeError::new_err(ERRMSG_FBIG_WRONG_SRC_TYPE)) + } + } + fn unwrap(&self, py: Python) -> PyObject { + let (signif, exp) = self.0.repr().clone().into_parts(); + (IPy(signif), exp).into_py(py) + } + + fn __repr__(&self) -> String { + format!("", self.0) + } + fn __str__(&self) -> String { + format!("{}", self.0) + } + fn __format__(&self) { + todo!() + } + fn __hash__(&self) -> u64 { + let mut hasher = DefaultHasher::new(); + self.0.num_hash(&mut hasher); + hasher.finish() + } + + fn __float__(&self) -> f64 { + todo!() + } +} + +#[pymethods] +impl DPy { + #[new] + fn __new__(ob: &PyAny) -> PyResult { + let py = ob.py(); + let decimal = py.import(intern!(py, "decimal"))?; + if ob.is_instance(decimal.getattr(intern!(py, "Decimal"))?)? { + // create from decimal.Decimal + Ok(DPy(parse_to_dbig(ob)?)) + } else if let Ok(s) = ob.extract() { + // create from string + let d = DBig::from_str_native(s); + Ok(DPy(d.map_err(parse_error_to_py)?)) + } else if let Ok(obj) = as FromPyObject>::extract(ob) { + Ok(DPy(obj.0.clone())) + } else { + Err(PyTypeError::new_err(ERRMSG_DBIG_WRONG_SRC_TYPE)) + } + } + fn unwrap(&self, py: Python) -> PyResult { + // TODO(next): use scientific notation to convert + let decimal = py.import(intern!(py, "decimal"))?; + let decimal_type = decimal.getattr(intern!(py, "Decimal"))?; + Ok(decimal_type.call1((self.0.to_string(),))?.into()) + } + + fn __repr__(&self) -> String { + format!("", self.0) + } + fn __str__(&self) -> String { + format!("{}", self.0) + } + fn __format__(&self) { + todo!() + } + fn __hash__(&self) -> u64 { + let mut hasher = DefaultHasher::new(); + self.0.num_hash(&mut hasher); + hasher.finish() + } + + fn __float__(&self) -> f64 { + self.0.to_f64().value() + } +} diff --git a/python/src/int.rs b/python/src/int.rs index cd341bd..58e4190 100644 --- a/python/src/int.rs +++ b/python/src/int.rs @@ -1,20 +1,24 @@ use std::collections::hash_map::DefaultHasher; use std::hash::Hasher; -use std::os::raw::c_longlong; +use std::vec::Vec; -use dashu_base::BitTest; -use pyo3::exceptions::{PyIndexError, PyNotImplementedError}; +use dashu_base::{BitTest, Signed}; +use pyo3::exceptions::{PyIndexError, PyNotImplementedError, PyValueError}; use pyo3::prelude::*; use pyo3::types::PySlice; use pyo3::{ exceptions::{PyOverflowError, PyTypeError}, - ffi, intern, - types::{PyBytes, PyDict, PyLong}, + types::{PyBytes, PyLong}, }; -use crate::types::{IPy, PyWords, UPy}; -use crate::utils::parse_signed_index; -use dashu_int::{IBig, UBig}; +use crate::{ + convert::{ + convert_from_ibig, convert_from_ubig, parse_error_to_py, parse_signed_index, parse_to_ibig, + parse_to_long, parse_to_ubig, + }, + types::{IPy, PyWords, UPy}, +}; +use dashu_int::{IBig, UBig, Word}; use num_order::NumHash; // error messages @@ -22,75 +26,56 @@ const ERRMSG_LENGTH_TOO_LARGE: &'static str = "the integer has too many bits for const ERRMSG_STEPSIZE_TOO_LARGE: &'static str = "bit slicing with step size larger than 1 is not supported yet"; const ERRMSG_UBIG_WRONG_SRC_TYPE: &'static str = - "only integers or strings can be converted to a UBig instance"; + "only integers or strings can be used to construct a UBig instance"; +const ERRMSG_IBIG_WRONG_SRC_TYPE: &'static str = + "only integers or strings can be used to construct an IBig instance"; +const ERRMSG_FROM_WORDS_WRONG_TYPE: &'static str = + "only list of integers or Words instance can be used in UBig.from_words()"; +const ERRMSG_WRONG_ENDIANNESS: &'static str = "byteorder must be either 'little' or 'big'"; +const ERRMSG_NEGATIVE_TO_UNSIGNED: &'static str = "can't convert negative int to unsigned"; const ERRMSG_INT_WITH_RADIX: &'static str = "can't convert non-string with explicit base"; const ERRMSG_WRONG_INDEX_TYPE: &'static str = "indices must be integers or slices"; const ERRMSG_UBIG_FROM_NEG: &'static str = "can't convert negative int to unsigned"; const ERRMSG_UBIG_BITS_OOR: &'static str = "bits index out of range"; -fn py_to_long_or_big(ob: &PyAny) -> PyResult<(c_longlong, bool)> { - let py = ob.py(); - - unsafe { - let ptr = ob.as_ptr(); - let mut overflow: i32 = 0; - let v = ffi::PyLong_AsLongLongAndOverflow(ptr, &mut overflow); - - if v == -1 && PyErr::occurred(py) { - Err(PyErr::fetch(py)) - } else { - Ok((v, overflow != 0)) - } - } -} - -impl UPy { - // Conversion from python integer object, without type checking. - // - // The most efficient way here is to use ffi::_PyLong_AsByteArray. - // However, the conversion should not performed frequently, so the stable - // API `to_bytes` is preferred here. - fn wrap(ob: &PyAny) -> PyResult { - let (v, overflow) = py_to_long_or_big(ob)?; - if !overflow { - if let Ok(n) = u64::try_from(v) { - Ok(UPy(UBig::from(n))) - } else { - Err(PyOverflowError::new_err(ERRMSG_UBIG_FROM_NEG)) - } - } else { - let py = ob.py(); - let bit_len: usize = ob.call_method0(intern!(py, "bit_length"))?.extract()?; - let byte_len = (bit_len + 7) / 8; - let bytes: &PyBytes = ob - .call_method1(intern!(py, "to_bytes"), (byte_len, intern!(py, "little")))? - .downcast()?; - Ok(UPy(UBig::from_le_bytes(bytes.as_bytes()))) - } - } -} - #[pymethods] impl UPy { #[new] fn __new__(ob: &PyAny, radix: Option) -> PyResult { if ob.is_instance_of::() { + // create from int if radix.is_some() { - Err(PyTypeError::new_err(ERRMSG_INT_WITH_RADIX)) + return Err(PyTypeError::new_err(ERRMSG_INT_WITH_RADIX)); + } + + let (v, overflow) = parse_to_long(ob)?; + if !overflow { + if let Ok(n) = u64::try_from(v) { + Ok(UPy(UBig::from(n))) + } else { + Err(PyOverflowError::new_err(ERRMSG_UBIG_FROM_NEG)) + } } else { - Self::wrap(ob) + Ok(UPy(parse_to_ubig(ob)?)) } } else if let Ok(s) = ob.extract() { + // create from string let n = if let Some(r) = radix { UBig::from_str_radix(s, r) } else { UBig::from_str_with_radix_prefix(s).map(|v| v.0) }; - Ok(UPy(n.map_err(crate::utils::parse_error_to_py)?)) + Ok(UPy(n.map_err(parse_error_to_py)?)) + } else if let Ok(obj) = as FromPyObject>::extract(ob) { + Ok(UPy(obj.0.clone())) } else { Err(PyTypeError::new_err(ERRMSG_UBIG_WRONG_SRC_TYPE)) } } + fn unwrap(&self, py: Python) -> PyResult { + convert_from_ubig(&self.0, py) + } + fn __repr__(&self) -> String { format!("", self.0) } @@ -105,13 +90,6 @@ impl UPy { self.0.num_hash(&mut hasher); hasher.finish() } - fn __int__(&self, py: Python) -> PyResult { - let bytes = self.0.to_le_bytes(); - let bytes_obj = PyBytes::new(py, &bytes); - py.get_type::() - .call_method1(intern!(py, "from_bytes"), (bytes_obj, intern!(py, "little"))) - .map(PyObject::from) - } // use as a bit vector fn __len__(&self) -> usize { @@ -223,51 +201,49 @@ impl UPy { /********** interop **********/ + fn __int__(&self, py: Python) -> PyResult { + convert_from_ubig(&self.0, py) + } /// Get the underlying words representing this integer fn to_words(&self) -> PyWords { PyWords(self.0.as_words().to_vec()) } /// Create an integer from a list of words #[staticmethod] - fn from_words(words: &PyWords) -> Self { - // TODO: accept a list of integers, using Vec::extract - UPy(UBig::from_words(&words.0)) + fn from_words(ob: &PyAny) -> PyResult { + if let Ok(vec) = as FromPyObject>::extract(ob) { + Ok(UPy(UBig::from_words(&vec))) + } else if let Ok(words) = as FromPyObject>::extract(ob) { + Ok(UPy(UBig::from_words(&words.0))) + } else { + Err(PyTypeError::new_err(ERRMSG_FROM_WORDS_WRONG_TYPE)) + } } - fn to_bytes(&self, py: Python) -> PyObject { - PyBytes::new(py, &self.0.to_le_bytes()).into() + /// Convert the integer to bytes, like int.to_bytes(). + // TODO: add the length option, just like the python int, and use the same logic + fn to_bytes(&self, byteorder: Option<&str>, py: Python) -> PyResult { + let byteorder = byteorder.unwrap_or(&"little"); + let bytes = match byteorder { + "little" => PyBytes::new(py, &self.0.to_le_bytes()), + "big" => PyBytes::new(py, &self.0.to_be_bytes()), + _ => { + return Err(PyValueError::new_err(ERRMSG_WRONG_ENDIANNESS)); + } + }; + Ok(bytes.into()) } + /// Create UBig from bytes, like int.from_bytes(). #[staticmethod] - fn from_bytes(bytes: &PyBytes) -> Self { - todo!() - } -} - -impl IPy { - // Conversion from python integer object, without type checking. - // - // The most efficient way here is to use ffi::_PyLong_AsByteArray. - // However, the conversion should not performed frequently, so the stable - // API `to_bytes` is preferred here. - fn wrap(ob: &PyAny) -> PyResult { - let (v, overflow) = py_to_long_or_big(ob)?; - if !overflow { - Ok(IPy(IBig::from(v))) - } else { - let py = ob.py(); - let bit_len: usize = ob.call_method0(intern!(py, "bit_length"))?.extract()?; - let byte_len = (bit_len + 7) / 8; - - let kwargs = PyDict::new(py); - kwargs.set_item(intern!(py, "signed"), true).unwrap(); - let bytes: &PyBytes = ob - .call_method( - intern!(py, "to_bytes"), - (byte_len, intern!(py, "little")), - Some(kwargs), - )? - .downcast()?; - Ok(IPy(IBig::from_le_bytes(bytes.as_bytes()))) - } + fn from_bytes(bytes: &PyBytes, byteorder: Option<&str>) -> PyResult { + let byteorder = byteorder.unwrap_or(&"little"); + let uint = match byteorder { + "little" => UBig::from_le_bytes(bytes.as_bytes()), + "big" => UBig::from_be_bytes(bytes.as_bytes()), + _ => { + return Err(PyValueError::new_err(ERRMSG_WRONG_ENDIANNESS)); + } + }; + Ok(Self(uint)) } } @@ -277,24 +253,35 @@ impl IPy { #[inline] fn __new__(ob: &PyAny, radix: Option) -> PyResult { if ob.is_instance_of::() { + // create from int if radix.is_some() { - Err(PyTypeError::new_err("can't convert non-string with explicit base")) + return Err(PyTypeError::new_err(ERRMSG_INT_WITH_RADIX)); + } + + let (v, overflow) = parse_to_long(ob)?; + if !overflow { + Ok(IPy(IBig::from(v))) } else { - Self::wrap(ob) + Ok(IPy(parse_to_ibig(ob)?)) } } else if let Ok(s) = ob.extract() { + // create from string let n = if let Some(r) = radix { IBig::from_str_radix(s, r) } else { IBig::from_str_with_radix_prefix(s).map(|v| v.0) }; - Ok(IPy(n.map_err(crate::utils::parse_error_to_py)?)) + Ok(IPy(n.map_err(parse_error_to_py)?)) + } else if let Ok(obj) = as FromPyObject>::extract(ob) { + Ok(IPy(obj.0.clone())) } else { - Err(PyTypeError::new_err( - "only Python integers can be automatically converted to an IBig instance", - )) + Err(PyTypeError::new_err(ERRMSG_IBIG_WRONG_SRC_TYPE)) } } + fn unwrap(&self, py: Python) -> PyResult { + convert_from_ibig(&self.0, py) + } + fn __repr__(&self) -> String { format!("", self.0) } @@ -309,20 +296,6 @@ impl IPy { self.0.num_hash(&mut hasher); hasher.finish() } - fn __int__(&self, py: Python) -> PyResult { - let bytes = self.0.to_le_bytes(); - let bytes_obj = PyBytes::new(py, &bytes); - - let kwargs = PyDict::new(py); - kwargs.set_item(intern!(py, "signed"), true).unwrap(); - py.get_type::() - .call_method( - intern!(py, "from_bytes"), - (bytes_obj, intern!(py, "little")), - Some(kwargs), - ) - .map(PyObject::from) - } // use as a bit vector with very limited capabilities fn __len__(&self) -> usize { @@ -331,4 +304,63 @@ impl IPy { fn __getitem__(&self, i: usize) -> bool { self.0.bit(i) } + + /********** interop **********/ + + fn __int__(&self, py: Python) -> PyResult { + convert_from_ibig(&self.0, py) + } + /// Convert the integer to bytes, like int.to_bytes(). + // TODO: add the length option, just like the python int, and use the same logic + fn to_bytes( + &self, + byteorder: Option<&str>, + signed: Option, + py: Python, + ) -> PyResult { + let signed = signed.unwrap_or(false); + if !signed && self.0.is_negative() { + return Err(PyOverflowError::new_err(ERRMSG_NEGATIVE_TO_UNSIGNED)); + } + + let byteorder = byteorder.unwrap_or(&"little"); + let bytes = match byteorder { + "little" => PyBytes::new(py, &self.0.to_le_bytes()), + "big" => PyBytes::new(py, &self.0.to_be_bytes()), + _ => { + return Err(PyValueError::new_err(ERRMSG_WRONG_ENDIANNESS)); + } + }; + Ok(bytes.into()) + } + /// Create IBig from bytes, like int.from_bytes(). + #[staticmethod] + fn from_bytes( + bytes: &PyBytes, + byteorder: Option<&str>, + signed: Option, + ) -> PyResult { + let byteorder = byteorder.unwrap_or(&"little"); + let signed = signed.unwrap_or(false); + let int = match byteorder { + "little" => { + if signed { + IBig::from_le_bytes(bytes.as_bytes()) + } else { + UBig::from_le_bytes(bytes.as_bytes()).into() + } + } + "big" => { + if signed { + IBig::from_be_bytes(bytes.as_bytes()) + } else { + UBig::from_be_bytes(bytes.as_bytes()).into() + } + } + _ => { + return Err(PyValueError::new_err(ERRMSG_WRONG_ENDIANNESS)); + } + }; + Ok(Self(int)) + } } diff --git a/python/src/lib.rs b/python/src/lib.rs index 180b594..8d88f0f 100644 --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -1,4 +1,7 @@ +mod convert; +mod float; mod int; +mod ratio; mod types; mod utils; mod words; @@ -17,5 +20,7 @@ fn dashu(_py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + + m.add_function(wrap_pyfunction!(utils::auto, m)?)?; Ok(()) } diff --git a/python/src/ratio.rs b/python/src/ratio.rs index e69de29..fdd4ff0 100644 --- a/python/src/ratio.rs +++ b/python/src/ratio.rs @@ -0,0 +1,57 @@ +use std::collections::hash_map::DefaultHasher; +use std::hash::Hasher; + +use dashu_ratio::RBig; +use num_order::NumHash; +use pyo3::{exceptions::PyTypeError, intern, prelude::*}; + +use crate::{ + convert::{convert_from_rbig, parse_error_to_py, parse_to_rbig}, + types::RPy, +}; + +const ERRMSG_RBIG_WRONG_SRC_TYPE: &'static str = + "only Fraction instances or strings can be used to construct an RBig instance"; + +#[pymethods] +impl RPy { + #[new] + fn __new__(ob: &PyAny) -> PyResult { + let py = ob.py(); + let fractions = py.import(intern!(py, "fractions"))?; + if ob.is_instance(fractions.getattr(intern!(py, "Fraction"))?)? { + // create from fractions.Fraction + Ok(RPy(parse_to_rbig(ob)?)) + } else if let Ok(s) = ob.extract() { + // create from string + let d = RBig::from_str_with_radix_prefix(s).map(|v| v.0); + Ok(RPy(d.map_err(parse_error_to_py)?)) + } else if let Ok(obj) = as FromPyObject>::extract(ob) { + Ok(RPy(obj.0.clone())) + } else { + Err(PyTypeError::new_err(ERRMSG_RBIG_WRONG_SRC_TYPE)) + } + } + fn unwrap(&self, py: Python) -> PyResult { + convert_from_rbig(&self.0, py) + } + + fn __repr__(&self) -> String { + format!("", self.0) + } + fn __str__(&self) -> String { + format!("{}", self.0) + } + fn __format__(&self) { + todo!() + } + fn __hash__(&self) -> u64 { + let mut hasher = DefaultHasher::new(); + self.0.num_hash(&mut hasher); + hasher.finish() + } + + fn __float__(&self) -> f64 { + self.0.to_f64_fast() + } +} diff --git a/python/src/types.rs b/python/src/types.rs index 8b8215a..4078eeb 100644 --- a/python/src/types.rs +++ b/python/src/types.rs @@ -84,9 +84,13 @@ pub struct PyWords(pub std::vec::Vec); /// An input type that accepts all possible numeric types from Python pub enum UniInput<'a> { SmallInt(c_longlong), // from int - BigInt(&'a IBig), // from UPy + BigUint(PyRef<'a, UPy>), + BigInt(PyRef<'a, IPy>), BigIntOwned(IBig), // from int SmallFloat(c_double), // from float - BigFloat(&'a FBig), // from FPy - BigDecimal(&'a DBig), // from decimal.Decimal or DPy + BigFloat(PyRef<'a, FPy>), + BigDecimal(PyRef<'a, DPy>), + BigDecimalOwned(DBig), // from decimal.Decimal + BigRational(PyRef<'a, RPy>), + BigRationalOwned(RBig), // from fractions.Fraction } diff --git a/python/src/utils.rs b/python/src/utils.rs index ed7b1bf..a4c14dc 100644 --- a/python/src/utils.rs +++ b/python/src/utils.rs @@ -1,42 +1,31 @@ -use dashu_base::ParseError; -use pyo3::exceptions::PySyntaxError; -use pyo3::ffi::PyObject; -use pyo3::{PyAny, PyErr}; +use crate::{convert::conversion_error_to_py, types::*}; +use pyo3::prelude::*; -pub fn parse_signed_index(index: isize, length: usize, unlimited: bool) -> Option { - if index >= 0 { - let i = index as usize; - if unlimited || i <= length { - Some(i) - } else { - None - } - } else { - let i = index.unsigned_abs(); - if i <= length { - Some(length - i) - } else { - None - } - } -} +/// Convert input automatically to corresponding dashu type: +/// (int -> IBig, float -> FBig, decimal -> DBig, fraction -> RBig) +#[pyfunction] +pub fn auto(ob: UniInput, py: Python<'_>) -> PyResult { + // TODO: accept str input and detect the best representation -pub fn parse_error_to_py(error: ParseError) -> PyErr { - let expl = match error { - ParseError::NoDigits => "no valid digits in the string.", - ParseError::InvalidDigit => "invalid digit for the given radix.", - ParseError::UnsupportedRadix => "the radix is not supported.", - ParseError::InconsistentRadix => { - "the radices of different components of the number are different." - } + use UniInput::*; + let obj = match ob { + SmallInt(v) => IPy(v.into()).into_py(py), + BigUint(v) => IPy(v.0.clone().into()).into_py(py), + BigInt(v) => v.clone().into_py(py), + BigIntOwned(v) => IPy(v).into_py(py), + SmallFloat(v) => match v.try_into() { + Ok(big) => FPy(big).into_py(py), + Err(e) => { + return Err(conversion_error_to_py(e)); + } + }, + BigFloat(v) => v.clone().into_py(py), + BigDecimal(v) => v.clone().into_py(py), + BigDecimalOwned(v) => DPy(v).into_py(py), + BigRational(v) => v.clone().into_py(py), + BigRationalOwned(v) => RPy(v).into_py(py), }; - - PySyntaxError::new_err(expl).into() -} - -pub fn auto(ob: PyAny) -> PyObject { - // convert input automatically to corresponding type (int -> IBig, float -> FBig, decimal -> DBig, fraction -> RBig) - todo!() + Ok(obj) } // TODO: split_dword, double_word, etc. From a968d145156c4f36ed806e9ea6f79e70588c1c1c Mon Sep 17 00:00:00 2001 From: Jacob Date: Tue, 30 Jan 2024 21:56:55 +0800 Subject: [PATCH 07/15] [WIP] Initial implementation of LowerExp on FBig --- float/src/fmt.rs | 176 +++++++++++++++++++++++++- float/src/lib.rs | 2 + float/src/third_party/postgres/mod.rs | 1 - float/src/third_party/serde.rs | 2 - float/tests/io.rs | 38 ++++++ integer/src/third_party/serde.rs | 1 + python/CHANGELOG.md | 4 + python/src/int.rs | 24 ++-- python/src/utils.rs | 8 +- 9 files changed, 233 insertions(+), 23 deletions(-) create mode 100644 python/CHANGELOG.md diff --git a/float/src/fmt.rs b/float/src/fmt.rs index 268a6f7..8bb2ee5 100644 --- a/float/src/fmt.rs +++ b/float/src/fmt.rs @@ -7,6 +7,7 @@ use crate::{ utils::{digit_len, split_digits_ref}, }; use core::fmt::{self, Alignment, Display, Formatter, Write}; +use alloc::string::String; use dashu_base::{Sign, UnsignedAbs}; use dashu_int::{IBig, Word}; @@ -67,8 +68,8 @@ impl fmt::Debug for Context { } impl Repr { - /// Print the float number with given rounding mode. The rounding may happen if the precision option - /// of the formatter is set. + /// Print the float number with given rounding mode. + /// The rounding may happen if the precision option of the formatter is set. fn fmt_round(&self, f: &mut Formatter<'_>) -> fmt::Result { // shortcut for infinities if self.is_infinite() { @@ -96,6 +97,8 @@ impl Repr { (&self.significand, self.exponent) }; + // TODO: use String to simplify this. We don't have to calculate everything in advance. + // calculate padding if necessary let (left_pad, right_pad) = if let Some(min_width) = f.width() { // first calculate the with of the formatted digits without padding @@ -259,6 +262,109 @@ impl Repr { Ok(()) } + + /// Print the float number in scientific notation with given rounding mode. + /// The rounding may happen if the precision option of the formatter is set. + /// + /// When `use_hexadecimal` is True and base B is 2, the output will be represented + /// in the hexadecimal format 0xaaa.bbbpcc. + fn fmt_round_scientific(&self, f: &mut Formatter<'_>, upper: bool, use_hexadecimal: bool, exp_marker: Option) -> fmt::Result { + assert!(!(B != 2 && use_hexadecimal), "hexadecimal is only relevant for base 2"); + + // shortcut for infinities + if self.is_infinite() { + return match self.sign() { + Sign::Positive => f.write_str("inf"), + Sign::Negative => f.write_str("-inf"), + }; + } + + // first perform rounding before actual printing if necessary + let negative: bool = self.significand.sign() == Sign::Negative; + let rounded_signif; + let (signif, exp) = if let Some(prec) = f.precision() { + let prec = (prec + 1) as isize; // always have one extra digit before the radix point + let diff = prec - self.digits() as isize; + if diff < 0 { + let shift = -diff as usize; + let (signif, rem) = split_digits_ref::(&self.significand, shift); + let adjust = R::round_fract::(&signif, rem, shift); + rounded_signif = signif + adjust; + (&rounded_signif, self.exponent - diff) + } else { + (&self.significand, self.exponent) + } + } else { + (&self.significand, self.exponent) + }; + + let (mut signif_str, mut exp_str) = (String::new(), String::new()); + match (upper, use_hexadecimal) { + (false, false) => write!(&mut signif_str, "{}", signif.in_radix(B as _)), + (true, false) => write!(&mut signif_str, "{:#}", signif.in_radix(B as _)), + (false, true) => { + // f.write_str("0x")?; + write!(&mut signif_str, "{:#x}", signif) + }, + (true, true) => { + // f.write_str("0x")?; + write!(&mut signif_str, "{:#X}", signif) + }, + }?; + write!(&mut exp_str, "{}", exp)?; + + let has_point = signif_str.len() > 1 && f.precision().unwrap_or(0) > 0; // whether print the radix point + let has_sign = negative || f.sign_plus(); + + // calculate padding if necessary + let (left_pad, right_pad) = if let Some(min_width) = f.width() { + let width = signif_str.len() + exp_str.len() + + /* exponent marker */ 1 + + has_sign as usize + + has_point as usize + + use_hexadecimal as usize * 2; + if width >= min_width { + (0, 0) + } else { + match f.align() { + Some(Alignment::Left) => (0, min_width - width), + Some(Alignment::Right) | None => (min_width - width, 0), + Some(Alignment::Center) => { + let diff = min_width - width; + (diff / 2, diff - diff / 2) + } + } + } + } else { + (0, 0) + }; + + // print left padding + for _ in 0..left_pad { + f.write_char(f.fill())?; + } + + // print the body + if !negative && f.sign_plus() { + f.write_char('+')?; + } + let split_loc = 1 + negative as usize + use_hexadecimal as usize * 2; + let (int, fract) = signif_str.split_at(split_loc); + f.write_str(int)?; + if fract.len() != 0 { + f.write_char('.')?; + f.write_str(fract)?; + } + f.write_char(exp_marker.unwrap_or('@'))?; + f.write_str(&exp_str)?; + + // print right padding + for _ in 0..right_pad { + f.write_char(f.fill())?; + } + + Ok(()) + } } impl Display for Repr { @@ -303,3 +409,69 @@ impl Display for FBig { self.repr.fmt_round::(f) } } + +macro_rules! impl_fmt_with_base { + ($base:literal, $trait:ident, $upper: literal, $hex:literal, $marker:literal) => { + impl fmt::$trait for Repr<$base> { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.fmt_round_scientific::(f, $upper, $hex, Some($marker)) + } + } + + impl fmt::$trait for FBig { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.repr.fmt_round_scientific::(f, $upper, $hex, Some($marker)) + } + } + }; +} + +impl_fmt_with_base!(2, LowerHex, false, true, 'p'); +impl_fmt_with_base!(2, UpperHex, true, true, 'P'); +impl_fmt_with_base!(2, Binary, false, false, 'b'); +impl_fmt_with_base!(8, Octal, false, false, 'o'); +impl_fmt_with_base!(16, LowerHex, false, false, 'h'); +impl_fmt_with_base!(16, UpperHex, true, false, 'H'); + +impl fmt::LowerExp for Repr { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let marker = match B { + 10 => Some('e'), + _ => None + }; + self.fmt_round_scientific::(f, false, false, marker) + } +} +impl fmt::UpperExp for Repr { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let marker = match B { + 10 => Some('E'), + _ => None + }; + self.fmt_round_scientific::(f, true, false, marker) + } +} +impl fmt::LowerExp for FBig { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let marker = match B { + 10 => Some('e'), + _ => None + }; + self.repr.fmt_round_scientific::(f, false, false, marker) + } +} +impl fmt::UpperExp for FBig { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let marker = match B { + 10 => Some('E'), + _ => None + }; + self.repr.fmt_round_scientific::(f, true, false, marker) + } +} diff --git a/float/src/lib.rs b/float/src/lib.rs index adab637..a2e60a5 100644 --- a/float/src/lib.rs +++ b/float/src/lib.rs @@ -62,6 +62,8 @@ #![cfg_attr(not(feature = "std"), no_std)] +extern crate alloc; + mod add; mod cmp; mod convert; diff --git a/float/src/third_party/postgres/mod.rs b/float/src/third_party/postgres/mod.rs index abc9644..c9c42b1 100644 --- a/float/src/third_party/postgres/mod.rs +++ b/float/src/third_party/postgres/mod.rs @@ -17,7 +17,6 @@ use crate::{ repr::{Context, Repr}, round::Round, }; -extern crate alloc; use alloc::vec::Vec; /// Represents the NUMERIC value in PostgreSQL, closely mirroring the PG wire protocol without NaN. diff --git a/float/src/third_party/serde.rs b/float/src/third_party/serde.rs index bb91a22..e5a62c8 100644 --- a/float/src/third_party/serde.rs +++ b/float/src/third_party/serde.rs @@ -1,7 +1,5 @@ //! Implement serde traits. -extern crate alloc; - use core::marker::PhantomData; use crate::{fbig::FBig, repr::Repr, round::Round, Context}; diff --git a/float/tests/io.rs b/float/tests/io.rs index 73361a2..a6a4d61 100644 --- a/float/tests/io.rs +++ b/float/tests/io.rs @@ -375,6 +375,39 @@ fn test_format_binary() { assert_eq!(format!("{:=>+16.8}", fbig!(0x123p-4)), "=+10010.00110000"); } +#[test] +fn test_scientific_format_binary() { + assert_eq!(format!("{:b}", fbig!(0x0)), "0b0"); + assert_eq!(format!("{:b}", fbig!(0x1)), "1b0"); + assert_eq!(format!("{:b}", fbig!(-0x1)), "-1b0"); + assert_eq!(format!("{:b}", fbig!(0x1p4)), "1b4"); + assert_eq!(format!("{:b}", fbig!(-0x1p4)), "-1b4"); + assert_eq!(format!("{:b}", fbig!(0x1p-1)), "1b-1"); + assert_eq!(format!("{:b}", fbig!(-0x1p-1)), "-1b-1"); + assert_eq!(format!("{:b}", fbig!(0x1p-4)), "1b-4"); + assert_eq!(format!("{:b}", fbig!(-0x1p-4)), "-1b-4"); + + assert_eq!(format!("{:e}", fbig!(0x0)), "0@0"); + assert_eq!(format!("{:e}", fbig!(0x1)), "1@0"); + assert_eq!(format!("{:e}", fbig!(-0x1)), "-1@0"); + assert_eq!(format!("{:e}", fbig!(0x1p4)), "1@4"); + assert_eq!(format!("{:e}", fbig!(-0x1p4)), "-1@4"); + assert_eq!(format!("{:e}", fbig!(0x1p-1)), "1@-1"); + assert_eq!(format!("{:e}", fbig!(-0x1p-1)), "-1@-1"); + assert_eq!(format!("{:e}", fbig!(0x1p-4)), "1@-4"); + assert_eq!(format!("{:e}", fbig!(-0x1p-4)), "-1@-4"); + + assert_eq!(format!("{:x}", fbig!(0x0)), "0x0p0"); + assert_eq!(format!("{:x}", fbig!(0x1)), "0x1p0"); + assert_eq!(format!("{:x}", fbig!(-0x1)), "-0x1p0"); + assert_eq!(format!("{:x}", fbig!(0x1p4)), "0x1p4"); + assert_eq!(format!("{:x}", fbig!(-0x1p4)), "-0x1p4"); + assert_eq!(format!("{:x}", fbig!(0x1p-1)), "0x1p-1"); + assert_eq!(format!("{:x}", fbig!(-0x1p-1)), "-0x1p-1"); + assert_eq!(format!("{:x}", fbig!(0x1p-4)), "0x1p-4"); + assert_eq!(format!("{:x}", fbig!(-0x1p-4)), "-0x1p-4"); +} + #[test] fn test_format_decimal() { assert_eq!(format!("{}", dbig!(0)), "0"); @@ -577,3 +610,8 @@ fn test_format_debug() { }"# ); } + +#[test] +fn test_format_roundtrip() { + todo!() // TODO: specifically test special bases +} diff --git a/integer/src/third_party/serde.rs b/integer/src/third_party/serde.rs index 4c8b153..9c7aac5 100644 --- a/integer/src/third_party/serde.rs +++ b/integer/src/third_party/serde.rs @@ -60,6 +60,7 @@ impl Serialize for IBig { } else if self.is_zero() { serializer.serialize_bytes(&[]) } else { + // TODO(v0.5): Change to IBig::to_le_bytes(), which provides better interop robustness let (sign, words) = self.as_sign_words(); let mut bytes = words_to_le_bytes(words); diff --git a/python/CHANGELOG.md b/python/CHANGELOG.md new file mode 100644 index 0000000..9f1789f --- /dev/null +++ b/python/CHANGELOG.md @@ -0,0 +1,4 @@ +# Unreleased + +- TODO: support pickle through __reduce__ +- TODO: support as much dunder methods as possible: https://docs.cython.org/en/latest/src/userguide/special_methods.html#special-methods \ No newline at end of file diff --git a/python/src/int.rs b/python/src/int.rs index 58e4190..4904312 100644 --- a/python/src/int.rs +++ b/python/src/int.rs @@ -220,7 +220,6 @@ impl UPy { } } /// Convert the integer to bytes, like int.to_bytes(). - // TODO: add the length option, just like the python int, and use the same logic fn to_bytes(&self, byteorder: Option<&str>, py: Python) -> PyResult { let byteorder = byteorder.unwrap_or(&"little"); let bytes = match byteorder { @@ -311,7 +310,6 @@ impl IPy { convert_from_ibig(&self.0, py) } /// Convert the integer to bytes, like int.to_bytes(). - // TODO: add the length option, just like the python int, and use the same logic fn to_bytes( &self, byteorder: Option<&str>, @@ -343,20 +341,14 @@ impl IPy { let byteorder = byteorder.unwrap_or(&"little"); let signed = signed.unwrap_or(false); let int = match byteorder { - "little" => { - if signed { - IBig::from_le_bytes(bytes.as_bytes()) - } else { - UBig::from_le_bytes(bytes.as_bytes()).into() - } - } - "big" => { - if signed { - IBig::from_be_bytes(bytes.as_bytes()) - } else { - UBig::from_be_bytes(bytes.as_bytes()).into() - } - } + "little" => match signed { + false => UBig::from_le_bytes(bytes.as_bytes()).into(), + true => IBig::from_le_bytes(bytes.as_bytes()), + }, + "big" => match signed { + false => UBig::from_be_bytes(bytes.as_bytes()).into(), + true => IBig::from_be_bytes(bytes.as_bytes()), + }, _ => { return Err(PyValueError::new_err(ERRMSG_WRONG_ENDIANNESS)); } diff --git a/python/src/utils.rs b/python/src/utils.rs index a4c14dc..df9a2d4 100644 --- a/python/src/utils.rs +++ b/python/src/utils.rs @@ -5,8 +5,6 @@ use pyo3::prelude::*; /// (int -> IBig, float -> FBig, decimal -> DBig, fraction -> RBig) #[pyfunction] pub fn auto(ob: UniInput, py: Python<'_>) -> PyResult { - // TODO: accept str input and detect the best representation - use UniInput::*; let obj = match ob { SmallInt(v) => IPy(v.into()).into_py(py), @@ -28,4 +26,10 @@ pub fn auto(ob: UniInput, py: Python<'_>) -> PyResult { Ok(obj) } +#[pyfunction] +pub fn autos(s: &str, py: Python<'_>) -> PyResult { + // TODO: accept str input and detect the best representation + todo!() +} + // TODO: split_dword, double_word, etc. From fddde890379f998db7d74fb302ba590004ef4758 Mon Sep 17 00:00:00 2001 From: Jacob Date: Wed, 31 Jan 2024 23:17:43 +0800 Subject: [PATCH 08/15] Complete FBig fmt traits implementations --- float/CHANGELOG.md | 1 + float/src/fmt.rs | 203 +++++++++++++++++++++------------- float/tests/io.rs | 262 +++++++++++++++++++++++++++++++++++++++++++- integer/tests/io.rs | 74 +++++++++++++ 4 files changed, 461 insertions(+), 79 deletions(-) diff --git a/float/CHANGELOG.md b/float/CHANGELOG.md index 914c5a9..6230bad 100644 --- a/float/CHANGELOG.md +++ b/float/CHANGELOG.md @@ -5,6 +5,7 @@ - Implement `TryFrom` and `TryFrom` for primitive integers. - Implement `TryFrom>` and `TryFrom>` for primitive floats. - Implement `From` and `From` for `Repr`. +- Implement `core::fmt::{Binary, Oct, LowerExp, UpperExp, LowerHex, UpperHex}` for `Repr`, `FBig` (some are limited to certain bases). ## 0.4.2 diff --git a/float/src/fmt.rs b/float/src/fmt.rs index 8bb2ee5..ba87a04 100644 --- a/float/src/fmt.rs +++ b/float/src/fmt.rs @@ -6,9 +6,9 @@ use crate::{ round::{mode::Zero, Round}, utils::{digit_len, split_digits_ref}, }; -use core::fmt::{self, Alignment, Display, Formatter, Write}; use alloc::string::String; -use dashu_base::{Sign, UnsignedAbs}; +use core::fmt::{self, Alignment, Display, Formatter, Write}; +use dashu_base::Sign; use dashu_int::{IBig, Word}; trait DebugStructHelper { @@ -97,15 +97,20 @@ impl Repr { (&self.significand, self.exponent) }; - // TODO: use String to simplify this. We don't have to calculate everything in advance. + // then print the digits to a buffer, without the sign + let mut signif_str = String::new(); + write!(&mut signif_str, "{}", signif.in_radix(B as _))?; + let signif_str = if negative { + &signif_str[1..] + } else { + signif_str.as_str() + }; // calculate padding if necessary let (left_pad, right_pad) = if let Some(min_width) = f.width() { - // first calculate the with of the formatted digits without padding - - let mut signif_digits = digit_len::(signif); + let mut signif_digits = signif_str.len(); // the leading zeros needs to be printed (when the exponent of the number is very small). - let leading_zeros = -(exp + signif_digits as isize - 1).min(0) as usize; + let leading_zeros = -(exp + signif_str.len() as isize - 1).min(0) as usize; // the trailing zeros needs to be printed (when the exponent of the number is very large) let mut trailing_zeros = exp.max(0) as usize; @@ -122,7 +127,7 @@ impl Repr { } let has_sign = (negative || f.sign_plus()) as usize; - let has_float_point = if exp > 0 { + let has_radix_point = if exp > 0 { // if there's no fractional part, the result has the floating point // only if the precision is set to be non-zero f.precision().unwrap_or(0) > 0 @@ -132,7 +137,7 @@ impl Repr { f.precision() != Some(0) // non-zero or none } as usize; - let width = signif_digits + has_sign + has_float_point + leading_zeros + trailing_zeros; + let width = signif_digits + has_sign + has_radix_point + leading_zeros + trailing_zeros; // check alignment and calculate padding if width >= min_width { @@ -153,40 +158,40 @@ impl Repr { (0, 0) }; - // print left padding - let fill = if f.sign_aware_zero_pad() { - '0' - } else { - f.fill() - }; - for _ in 0..left_pad { - f.write_char(fill)?; + // print sign and left padding + if !f.sign_aware_zero_pad() { + for _ in 0..left_pad { + f.write_char(f.fill())?; + } + } + if negative { + f.write_char('-')?; + } else if f.sign_plus() { + f.write_char('+')?; + } + if f.sign_aware_zero_pad() { + for _ in 0..left_pad { + f.write_char('0')?; + } } // print the actual digits if exp < 0 { // If the exponent is negative, then the float number has fractional part let exp = -exp as usize; - let (int, fract) = split_digits_ref::(signif, exp); + let (int, fract) = signif_str.split_at(signif_str.len().saturating_sub(exp)); - let frac_digits = digit_len::(&fract); + let frac_digits = fract.len(); debug_assert!(frac_digits <= exp); - // print the integral part. - if !negative && f.sign_plus() { - f.write_char('+')?; - } - if int.is_zero() { - if negative { - f.write_char('-')?; - } + // print the integral part, at least print a zero. + if int.is_empty() { f.write_char('0')?; } else { - f.write_fmt(format_args!("{}", int.in_radix(B as u32)))?; + f.write_str(int)?; } // print the fractional part, it has exactly `exp` digits (with left zero padding) - let fract = fract.unsigned_abs(); // don't print sign for fractional part if let Some(prec) = f.precision() { // don't print any fractional part if precision is zero if prec != 0 { @@ -202,14 +207,14 @@ impl Repr { } } if frac_digits > 0 { - f.write_fmt(format_args!("{}", fract.in_radix(B as u32)))?; + f.write_str(fract)?; } } else { // append zeros if the required precision is larger for _ in 0..exp - frac_digits { f.write_char('0')?; } - f.write_fmt(format_args!("{}", fract.in_radix(B as u32)))?; + f.write_str(fract)?; for _ in 0..prec - exp { f.write_char('0')?; } @@ -220,26 +225,19 @@ impl Repr { for _ in 0..(exp - frac_digits) { f.write_char('0')?; } - f.write_fmt(format_args!("{}", fract.in_radix(B as u32)))?; + f.write_str(fract)?; } } else { // In this case, the number is actually an integer and it can be trivially formatted. // However, when the precision option is set, we need to append zeros. - // print the significand - if !negative && f.sign_plus() { - f.write_char('+')?; - } - if signif.is_zero() { - if negative { - f.write_char('-')?; - } + // print the significand and append zeros if needed + if signif_str.is_empty() { + // this branch can happend when a negative float is rounded to zero. f.write_char('0')?; } else { - f.write_fmt(format_args!("{}", signif.in_radix(B as u32)))?; + f.write_str(signif_str)?; } - - // append zeros if needed for _ in 0..exp { f.write_char('0')?; } @@ -265,10 +263,16 @@ impl Repr { /// Print the float number in scientific notation with given rounding mode. /// The rounding may happen if the precision option of the formatter is set. - /// + /// /// When `use_hexadecimal` is True and base B is 2, the output will be represented /// in the hexadecimal format 0xaaa.bbbpcc. - fn fmt_round_scientific(&self, f: &mut Formatter<'_>, upper: bool, use_hexadecimal: bool, exp_marker: Option) -> fmt::Result { + fn fmt_round_scientific( + &self, + f: &mut Formatter<'_>, + upper: bool, + use_hexadecimal: bool, + exp_marker: Option, + ) -> fmt::Result { assert!(!(B != 2 && use_hexadecimal), "hexadecimal is only relevant for base 2"); // shortcut for infinities @@ -280,10 +284,15 @@ impl Repr { } // first perform rounding before actual printing if necessary - let negative: bool = self.significand.sign() == Sign::Negative; + let negative = self.significand.sign() == Sign::Negative; let rounded_signif; let (signif, exp) = if let Some(prec) = f.precision() { - let prec = (prec + 1) as isize; // always have one extra digit before the radix point + // add one because always have one extra digit before the radix point + let prec = if use_hexadecimal { + (prec * 4 + 4) as isize + } else { + (prec + 1) as isize + }; let diff = prec - self.digits() as isize; if diff < 0 { let shift = -diff as usize; @@ -298,31 +307,48 @@ impl Repr { (&self.significand, self.exponent) }; + // then print the digits to a buffer, without the prefix or sign let (mut signif_str, mut exp_str) = (String::new(), String::new()); match (upper, use_hexadecimal) { (false, false) => write!(&mut signif_str, "{}", signif.in_radix(B as _)), (true, false) => write!(&mut signif_str, "{:#}", signif.in_radix(B as _)), - (false, true) => { - // f.write_str("0x")?; - write!(&mut signif_str, "{:#x}", signif) - }, - (true, true) => { - // f.write_str("0x")?; - write!(&mut signif_str, "{:#X}", signif) - }, + (false, true) => write!(&mut signif_str, "{:}", signif.in_radix(16)), + (true, true) => write!(&mut signif_str, "{:#}", signif.in_radix(16)), }?; - write!(&mut exp_str, "{}", exp)?; - - let has_point = signif_str.len() > 1 && f.precision().unwrap_or(0) > 0; // whether print the radix point - let has_sign = negative || f.sign_plus(); + let signif_str = if negative { + &signif_str[1..] + } else { + signif_str.as_str() + }; + // adjust exp because the radix point is put after the first digit + let exp_adjust = if use_hexadecimal { + exp + (signif_str.len() as isize - 1) * 4 + } else { + exp + signif_str.len() as isize - 1 + }; + write!(&mut exp_str, "{}", exp_adjust)?; + let exp_str = exp_str.as_str(); // calculate padding if necessary let (left_pad, right_pad) = if let Some(min_width) = f.width() { + let prec = f.precision().unwrap_or(0); + let has_point = signif_str.len() > 1 || prec > 0; // whether print the radix point + let has_sign = negative || f.sign_plus(); + + // if the precision option is set, there might be extra trailing zeros + let trailing_zeros = if prec > signif_str.len() - 1 { + prec - (signif_str.len() - 1) + } else { + 0 + }; + let width = signif_str.len() + exp_str.len() + /* exponent marker */ 1 + has_sign as usize + has_point as usize - + use_hexadecimal as usize * 2; + + use_hexadecimal as usize * 2 + + trailing_zeros; + if width >= min_width { (0, 0) } else { @@ -339,22 +365,43 @@ impl Repr { (0, 0) }; - // print left padding - for _ in 0..left_pad { - f.write_char(f.fill())?; + // print sign and left padding + if !f.sign_aware_zero_pad() { + for _ in 0..left_pad { + f.write_char(f.fill())?; + } } - - // print the body - if !negative && f.sign_plus() { + if negative { + f.write_char('-')?; + } else if f.sign_plus() { f.write_char('+')?; } - let split_loc = 1 + negative as usize + use_hexadecimal as usize * 2; - let (int, fract) = signif_str.split_at(split_loc); + if use_hexadecimal { + f.write_str("0x")?; + } + if f.sign_aware_zero_pad() { + for _ in 0..left_pad { + f.write_char('0')?; + } + } + + // print the body + let (int, fract) = signif_str.split_at(1); f.write_str(int)?; - if fract.len() != 0 { + if !fract.is_empty() { f.write_char('.')?; f.write_str(fract)?; } + let prec = f.precision().unwrap_or(0); + if prec > 0 { + if fract.is_empty() { + f.write_char('.')? + } + for _ in fract.len()..prec { + f.write_char('0')?; + } + } + f.write_char(exp_marker.unwrap_or('@'))?; f.write_str(&exp_str)?; @@ -422,25 +469,29 @@ macro_rules! impl_fmt_with_base { impl fmt::$trait for FBig { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - self.repr.fmt_round_scientific::(f, $upper, $hex, Some($marker)) + self.repr + .fmt_round_scientific::(f, $upper, $hex, Some($marker)) } } }; } +// TODO(v1.0): Alternate flags can be used to print upper separator, for example 'p' -> 'P'. +// In case of base ten, it can be used to switch between '@' and 'e'/'E'. +// Need to investigate what is the best way to utilize the alternate flag before implementing. impl_fmt_with_base!(2, LowerHex, false, true, 'p'); -impl_fmt_with_base!(2, UpperHex, true, true, 'P'); +impl_fmt_with_base!(2, UpperHex, true, true, 'p'); impl_fmt_with_base!(2, Binary, false, false, 'b'); impl_fmt_with_base!(8, Octal, false, false, 'o'); impl_fmt_with_base!(16, LowerHex, false, false, 'h'); -impl_fmt_with_base!(16, UpperHex, true, false, 'H'); +impl_fmt_with_base!(16, UpperHex, true, false, 'h'); impl fmt::LowerExp for Repr { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let marker = match B { 10 => Some('e'), - _ => None + _ => None, }; self.fmt_round_scientific::(f, false, false, marker) } @@ -450,7 +501,7 @@ impl fmt::UpperExp for Repr { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let marker = match B { 10 => Some('E'), - _ => None + _ => None, }; self.fmt_round_scientific::(f, true, false, marker) } @@ -460,7 +511,7 @@ impl fmt::LowerExp for FBig { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let marker = match B { 10 => Some('e'), - _ => None + _ => None, }; self.repr.fmt_round_scientific::(f, false, false, marker) } @@ -470,7 +521,7 @@ impl fmt::UpperExp for FBig { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let marker = match B { 10 => Some('E'), - _ => None + _ => None, }; self.repr.fmt_round_scientific::(f, true, false, marker) } diff --git a/float/tests/io.rs b/float/tests/io.rs index a6a4d61..479cf1b 100644 --- a/float/tests/io.rs +++ b/float/tests/io.rs @@ -292,6 +292,15 @@ fn test_format_binary() { assert_eq!(format!("{:8.0}", fbig!(-0x1p-1)), " -0"); assert_eq!(format!("{:8.0}", fbig!(0x1p-4)), " 0"); assert_eq!(format!("{:8.0}", fbig!(-0x1p-4)), " -0"); + assert_eq!(format!("{:08.0}", fbig!(0x0)), "00000000"); + assert_eq!(format!("{:08.0}", fbig!(0x1)), "00000001"); + assert_eq!(format!("{:08.0}", fbig!(-0x1)), "-0000001"); + assert_eq!(format!("{:08.0}", fbig!(0x1p4)), "00010000"); + assert_eq!(format!("{:08.0}", fbig!(-0x1p4)), "-0010000"); + assert_eq!(format!("{:08.0}", fbig!(0x1p-1)), "00000000"); + assert_eq!(format!("{:08.0}", fbig!(-0x1p-1)), "-0000000"); + assert_eq!(format!("{:08.0}", fbig!(0x1p-4)), "00000000"); + assert_eq!(format!("{:08.0}", fbig!(-0x1p-4)), "-0000000"); assert_eq!(format!("{:.8}", fbig!(0x0)), "0.00000000"); assert_eq!(format!("{:.8}", fbig!(0x1)), "1.00000000"); @@ -313,11 +322,26 @@ fn test_format_binary() { assert_eq!(format!("{:8.4}", fbig!(-0x1p-4)), " -0.0001"); assert_eq!(format!("{:8.4}", fbig!(0x1p-5)), " 0.0000"); assert_eq!(format!("{:8.4}", fbig!(-0x1p-5)), " -0.0000"); + assert_eq!(format!("{:08.4}", fbig!(0x0)), "000.0000"); + assert_eq!(format!("{:08.4}", fbig!(0x1)), "001.0000"); + assert_eq!(format!("{:08.4}", fbig!(-0x1)), "-01.0000"); + assert_eq!(format!("{:08.4}", fbig!(0x1p4)), "10000.0000"); + assert_eq!(format!("{:08.4}", fbig!(-0x1p4)), "-10000.0000"); + assert_eq!(format!("{:08.4}", fbig!(0x1p-1)), "000.1000"); + assert_eq!(format!("{:08.4}", fbig!(-0x1p-1)), "-00.1000"); + assert_eq!(format!("{:08.4}", fbig!(0x1p-4)), "000.0001"); + assert_eq!(format!("{:08.4}", fbig!(-0x1p-4)), "-00.0001"); + assert_eq!(format!("{:08.4}", fbig!(0x1p-5)), "000.0000"); + assert_eq!(format!("{:08.4}", fbig!(-0x1p-5)), "-00.0000"); assert_eq!(format!("{:16}", fbig!(0x123p-4)), " 10010.0011"); assert_eq!(format!("{:16}", fbig!(-0x123p-4)), " -10010.0011"); assert_eq!(format!("{:+16}", fbig!(0x123p-4)), " +10010.0011"); assert_eq!(format!("{:+16}", fbig!(-0x123p-4)), " -10010.0011"); + assert_eq!(format!("{:016}", fbig!(0x123p-4)), "00000010010.0011"); + assert_eq!(format!("{:016}", fbig!(-0x123p-4)), "-0000010010.0011"); + assert_eq!(format!("{:+016}", fbig!(0x123p-4)), "+0000010010.0011"); + assert_eq!(format!("{:+016}", fbig!(-0x123p-4)), "-0000010010.0011"); assert_eq!(format!("{:<16}", fbig!(0x123p-4)), "10010.0011 "); assert_eq!(format!("{:<16}", fbig!(-0x123p-4)), "-10010.0011 "); assert_eq!(format!("{:<+16}", fbig!(0x123p-4)), "+10010.0011 "); @@ -338,6 +362,10 @@ fn test_format_binary() { assert_eq!(format!("{:16.0}", fbig!(-0x123p-4)), " -10010"); assert_eq!(format!("{:+16.0}", fbig!(0x123p-4)), " +10010"); assert_eq!(format!("{:+16.0}", fbig!(-0x123p-4)), " -10010"); + assert_eq!(format!("{:016.0}", fbig!(0x123p-4)), "0000000000010010"); + assert_eq!(format!("{:016.0}", fbig!(-0x123p-4)), "-000000000010010"); + assert_eq!(format!("{:+016.0}", fbig!(0x123p-4)), "+000000000010010"); + assert_eq!(format!("{:+016.0}", fbig!(-0x123p-4)), "-000000000010010"); assert_eq!(format!("{:<16.0}", fbig!(0x123p-4)), "10010 "); assert_eq!(format!("{:<16.0}", fbig!(-0x123p-4)), "-10010 "); assert_eq!(format!("{:<+16.0}", fbig!(0x123p-4)), "+10010 "); @@ -358,6 +386,10 @@ fn test_format_binary() { assert_eq!(format!("{:16.8}", fbig!(-0x123p-4)), " -10010.00110000"); assert_eq!(format!("{:+16.8}", fbig!(0x123p-4)), " +10010.00110000"); assert_eq!(format!("{:+16.8}", fbig!(-0x123p-4)), " -10010.00110000"); + assert_eq!(format!("{:016.8}", fbig!(0x123p-4)), "0010010.00110000"); + assert_eq!(format!("{:016.8}", fbig!(-0x123p-4)), "-010010.00110000"); + assert_eq!(format!("{:+016.8}", fbig!(0x123p-4)), "+010010.00110000"); + assert_eq!(format!("{:+016.8}", fbig!(-0x123p-4)), "-010010.00110000"); assert_eq!(format!("{:<16.8}", fbig!(0x123p-4)), "10010.00110000 "); assert_eq!(format!("{:<16.8}", fbig!(-0x123p-4)), "-10010.00110000 "); assert_eq!(format!("{:<+16.8}", fbig!(0x123p-4)), "+10010.00110000 "); @@ -376,6 +408,7 @@ fn test_format_binary() { } #[test] +#[rustfmt::skip::macros(fbig)] fn test_scientific_format_binary() { assert_eq!(format!("{:b}", fbig!(0x0)), "0b0"); assert_eq!(format!("{:b}", fbig!(0x1)), "1b0"); @@ -386,7 +419,14 @@ fn test_scientific_format_binary() { assert_eq!(format!("{:b}", fbig!(-0x1p-1)), "-1b-1"); assert_eq!(format!("{:b}", fbig!(0x1p-4)), "1b-4"); assert_eq!(format!("{:b}", fbig!(-0x1p-4)), "-1b-4"); - + + assert_eq!(format!("{:b}", FBin::INFINITY), "inf"); + assert_eq!(format!("{:b}", FBin::NEG_INFINITY), "-inf"); + assert_eq!(format!("{:b}", FBin::from_parts(i8::MAX.into(), -4)), "1.111111b2"); + assert_eq!(format!("{:b}", FBin::from_parts(i8::MIN.into(), -4)), "-1b3"); + assert_eq!(format!("{:b}", FBin::from_parts(i16::MAX.into(), -8)), "1.11111111111111b6"); + assert_eq!(format!("{:b}", FBin::from_parts(i16::MIN.into(), -8)), "-1b7"); + assert_eq!(format!("{:e}", fbig!(0x0)), "0@0"); assert_eq!(format!("{:e}", fbig!(0x1)), "1@0"); assert_eq!(format!("{:e}", fbig!(-0x1)), "-1@0"); @@ -397,6 +437,13 @@ fn test_scientific_format_binary() { assert_eq!(format!("{:e}", fbig!(0x1p-4)), "1@-4"); assert_eq!(format!("{:e}", fbig!(-0x1p-4)), "-1@-4"); + assert_eq!(format!("{:e}", FBin::INFINITY), "inf"); + assert_eq!(format!("{:e}", FBin::NEG_INFINITY), "-inf"); + assert_eq!(format!("{:e}", FBin::from_parts(i8::MAX.into(), -4)), "1.111111@2"); + assert_eq!(format!("{:e}", FBin::from_parts(i8::MIN.into(), -4)), "-1@3"); + assert_eq!(format!("{:e}", FBin::from_parts(i16::MAX.into(), -8)), "1.11111111111111@6"); + assert_eq!(format!("{:e}", FBin::from_parts(i16::MIN.into(), -8)), "-1@7"); + assert_eq!(format!("{:x}", fbig!(0x0)), "0x0p0"); assert_eq!(format!("{:x}", fbig!(0x1)), "0x1p0"); assert_eq!(format!("{:x}", fbig!(-0x1)), "-0x1p0"); @@ -406,6 +453,62 @@ fn test_scientific_format_binary() { assert_eq!(format!("{:x}", fbig!(-0x1p-1)), "-0x1p-1"); assert_eq!(format!("{:x}", fbig!(0x1p-4)), "0x1p-4"); assert_eq!(format!("{:x}", fbig!(-0x1p-4)), "-0x1p-4"); + + assert_eq!(format!("{:x}", FBin::INFINITY), "inf"); + assert_eq!(format!("{:x}", FBin::NEG_INFINITY), "-inf"); + assert_eq!(format!("{:x}", FBin::from_parts(i8::MAX.into(), -4)), "0x7.fp0"); + assert_eq!(format!("{:x}", FBin::from_parts(i8::MIN.into(), -4)), "-0x1p3"); + assert_eq!(format!("{:x}", FBin::from_parts(i16::MAX.into(), -8)), "0x7.fffp4"); + assert_eq!(format!("{:x}", FBin::from_parts(i16::MIN.into(), -8)), "-0x1p7"); + + assert_eq!(format!("{:X}", fbig!(0x0)), "0x0p0"); + assert_eq!(format!("{:X}", fbig!(0x1)), "0x1p0"); + assert_eq!(format!("{:X}", fbig!(-0x1)), "-0x1p0"); + assert_eq!(format!("{:X}", fbig!(0x1p4)), "0x1p4"); + assert_eq!(format!("{:X}", fbig!(-0x1p4)), "-0x1p4"); + assert_eq!(format!("{:X}", fbig!(0x1p-1)), "0x1p-1"); + assert_eq!(format!("{:X}", fbig!(-0x1p-1)), "-0x1p-1"); + assert_eq!(format!("{:X}", fbig!(0x1p-4)), "0x1p-4"); + assert_eq!(format!("{:X}", fbig!(-0x1p-4)), "-0x1p-4"); + + assert_eq!(format!("{:X}", FBin::INFINITY), "inf"); + assert_eq!(format!("{:X}", FBin::NEG_INFINITY), "-inf"); + assert_eq!(format!("{:X}", FBin::from_parts(i8::MAX.into(), -4)), "0x7.Fp0"); + assert_eq!(format!("{:X}", FBin::from_parts(i8::MIN.into(), -4)), "-0x1p3"); + assert_eq!(format!("{:X}", FBin::from_parts(i16::MAX.into(), -8)), "0x7.FFFp4"); + assert_eq!(format!("{:X}", FBin::from_parts(i16::MIN.into(), -8)), "-0x1p7"); + + assert_eq!(format!("{:.0x}", fbig!(0x0)), "0x0p0"); + assert_eq!(format!("{:.0x}", fbig!(0x1)), "0x1p0"); + assert_eq!(format!("{:.0x}", fbig!(-0x1)), "-0x1p0"); + assert_eq!(format!("{:.0x}", fbig!(0x1p-1)), "0x1p-1"); + assert_eq!(format!("{:.0x}", fbig!(-0x1p-1)), "-0x1p-1"); + assert_eq!(format!("{:8.0x}", fbig!(0x0)), " 0x0p0"); + assert_eq!(format!("{:8.0x}", fbig!(0x1)), " 0x1p0"); + assert_eq!(format!("{:8.0x}", fbig!(-0x1)), " -0x1p0"); + assert_eq!(format!("{:8.0x}", fbig!(0x1p-1)), " 0x1p-1"); + assert_eq!(format!("{:8.0x}", fbig!(-0x1p-1)), " -0x1p-1"); + assert_eq!(format!("{:08.0x}", fbig!(0x0)), "0x0000p0"); + assert_eq!(format!("{:08.0x}", fbig!(0x1)), "0x0001p0"); + assert_eq!(format!("{:08.0x}", fbig!(-0x1)), "-0x001p0"); + assert_eq!(format!("{:08.0x}", fbig!(0x1p-1)), "0x001p-1"); + assert_eq!(format!("{:08.0x}", fbig!(-0x1p-1)), "-0x01p-1"); + + assert_eq!(format!("{:.8x}", fbig!(0x0)), "0x0.00000000p0"); + assert_eq!(format!("{:.8x}", fbig!(0x1)), "0x1.00000000p0"); + assert_eq!(format!("{:.8x}", fbig!(-0x1)), "-0x1.00000000p0"); + assert_eq!(format!("{:.8x}", fbig!(0x1p-1)), "0x1.00000000p-1"); + assert_eq!(format!("{:.8x}", fbig!(-0x1p-1)), "-0x1.00000000p-1"); + assert_eq!(format!("{:12.4x}", fbig!(0x0)), " 0x0.0000p0"); + assert_eq!(format!("{:12.4x}", fbig!(0x1)), " 0x1.0000p0"); + assert_eq!(format!("{:12.4x}", fbig!(-0x1)), " -0x1.0000p0"); + assert_eq!(format!("{:12.4x}", fbig!(0x1p-1)), " 0x1.0000p-1"); + assert_eq!(format!("{:12.4x}", fbig!(-0x1p-1)), "-0x1.0000p-1"); + assert_eq!(format!("{:012.4x}", fbig!(0x0)), "0x000.0000p0"); + assert_eq!(format!("{:012.4x}", fbig!(0x1)), "0x001.0000p0"); + assert_eq!(format!("{:012.4x}", fbig!(-0x1)), "-0x01.0000p0"); + assert_eq!(format!("{:012.4x}", fbig!(0x1p-1)), "0x01.0000p-1"); + assert_eq!(format!("{:012.4x}", fbig!(-0x1p-1)), "-0x1.0000p-1"); } #[test] @@ -453,6 +556,19 @@ fn test_format_decimal() { assert_eq!(format!("{:8.0}", dbig!(-9e-1)), " -1"); assert_eq!(format!("{:8.0}", dbig!(99e-1)), " 10"); assert_eq!(format!("{:8.0}", dbig!(-99e-1)), " -10"); + assert_eq!(format!("{:08.0}", dbig!(0)), "00000000"); + assert_eq!(format!("{:08.0}", dbig!(1)), "00000001"); + assert_eq!(format!("{:08.0}", dbig!(-1)), "-0000001"); + assert_eq!(format!("{:08.0}", dbig!(1e4)), "00010000"); + assert_eq!(format!("{:08.0}", dbig!(-1e4)), "-0010000"); + assert_eq!(format!("{:08.0}", dbig!(1e-1)), "00000000"); + assert_eq!(format!("{:08.0}", dbig!(-1e-1)), "-0000000"); + assert_eq!(format!("{:08.0}", dbig!(1e-4)), "00000000"); + assert_eq!(format!("{:08.0}", dbig!(-1e-4)), "-0000000"); + assert_eq!(format!("{:08.0}", dbig!(9e-1)), "00000001"); + assert_eq!(format!("{:08.0}", dbig!(-9e-1)), "-0000001"); + assert_eq!(format!("{:08.0}", dbig!(99e-1)), "00000010"); + assert_eq!(format!("{:08.0}", dbig!(-99e-1)), "-0000010"); assert_eq!(format!("{:.8}", dbig!(0)), "0.00000000"); assert_eq!(format!("{:.8}", dbig!(1)), "1.00000000"); @@ -478,11 +594,30 @@ fn test_format_decimal() { assert_eq!(format!("{:8.4}", dbig!(-9e-5)), " -0.0001"); assert_eq!(format!("{:8.4}", dbig!(99e-5)), " 0.0010"); assert_eq!(format!("{:8.4}", dbig!(-99e-5)), " -0.0010"); + assert_eq!(format!("{:08.4}", dbig!(0)), "000.0000"); + assert_eq!(format!("{:08.4}", dbig!(1)), "001.0000"); + assert_eq!(format!("{:08.4}", dbig!(-1)), "-01.0000"); + assert_eq!(format!("{:08.4}", dbig!(1e4)), "10000.0000"); + assert_eq!(format!("{:08.4}", dbig!(-1e4)), "-10000.0000"); + assert_eq!(format!("{:08.4}", dbig!(1e-1)), "000.1000"); + assert_eq!(format!("{:08.4}", dbig!(-1e-1)), "-00.1000"); + assert_eq!(format!("{:08.4}", dbig!(1e-4)), "000.0001"); + assert_eq!(format!("{:08.4}", dbig!(-1e-4)), "-00.0001"); + assert_eq!(format!("{:08.4}", dbig!(1e-5)), "000.0000"); + assert_eq!(format!("{:08.4}", dbig!(-1e-5)), "-00.0000"); + assert_eq!(format!("{:08.4}", dbig!(9e-5)), "000.0001"); + assert_eq!(format!("{:08.4}", dbig!(-9e-5)), "-00.0001"); + assert_eq!(format!("{:08.4}", dbig!(99e-5)), "000.0010"); + assert_eq!(format!("{:08.4}", dbig!(-99e-5)), "-00.0010"); assert_eq!(format!("{:8}", dbig!(123e-2)), " 1.23"); assert_eq!(format!("{:8}", dbig!(-123e-2)), " -1.23"); assert_eq!(format!("{:+8}", dbig!(123e-2)), " +1.23"); assert_eq!(format!("{:+8}", dbig!(-123e-2)), " -1.23"); + assert_eq!(format!("{:08}", dbig!(123e-2)), "00001.23"); + assert_eq!(format!("{:08}", dbig!(-123e-2)), "-0001.23"); + assert_eq!(format!("{:+08}", dbig!(123e-2)), "+0001.23"); + assert_eq!(format!("{:+08}", dbig!(-123e-2)), "-0001.23"); assert_eq!(format!("{:<8}", dbig!(123e-2)), "1.23 "); assert_eq!(format!("{:<8}", dbig!(-123e-2)), "-1.23 "); assert_eq!(format!("{:<+8}", dbig!(123e-2)), "+1.23 "); @@ -503,6 +638,10 @@ fn test_format_decimal() { assert_eq!(format!("{:8.0}", dbig!(-123e-2)), " -1"); assert_eq!(format!("{:+8.0}", dbig!(123e-2)), " +1"); assert_eq!(format!("{:+8.0}", dbig!(-123e-2)), " -1"); + assert_eq!(format!("{:08.0}", dbig!(123e-2)), "00000001"); + assert_eq!(format!("{:08.0}", dbig!(-123e-2)), "-0000001"); + assert_eq!(format!("{:+08.0}", dbig!(123e-2)), "+0000001"); + assert_eq!(format!("{:+08.0}", dbig!(-123e-2)), "-0000001"); assert_eq!(format!("{:<8.0}", dbig!(123e-2)), "1 "); assert_eq!(format!("{:<8.0}", dbig!(-123e-2)), "-1 "); assert_eq!(format!("{:<+8.0}", dbig!(123e-2)), "+1 "); @@ -523,6 +662,10 @@ fn test_format_decimal() { assert_eq!(format!("{:8.4}", dbig!(-123e-2)), " -1.2300"); assert_eq!(format!("{:+8.4}", dbig!(123e-2)), " +1.2300"); assert_eq!(format!("{:+8.4}", dbig!(-123e-2)), " -1.2300"); + assert_eq!(format!("{:08.4}", dbig!(123e-2)), "001.2300"); + assert_eq!(format!("{:08.4}", dbig!(-123e-2)), "-01.2300"); + assert_eq!(format!("{:+08.4}", dbig!(123e-2)), "+01.2300"); + assert_eq!(format!("{:+08.4}", dbig!(-123e-2)), "-01.2300"); assert_eq!(format!("{:<8.4}", dbig!(123e-2)), "1.2300 "); assert_eq!(format!("{:<8.4}", dbig!(-123e-2)), "-1.2300 "); assert_eq!(format!("{:<+8.4}", dbig!(123e-2)), "+1.2300 "); @@ -540,6 +683,92 @@ fn test_format_decimal() { assert_eq!(format!("{:=>+8.4}", dbig!(123e-2)), "=+1.2300"); } +#[test] +#[rustfmt::skip::macros(fbig)] +fn test_scientific_format_decimal() { + assert_eq!(format!("{:e}", dbig!(0)), "0e0"); + assert_eq!(format!("{:e}", dbig!(1)), "1e0"); + assert_eq!(format!("{:e}", dbig!(-1)), "-1e0"); + assert_eq!(format!("{:e}", dbig!(1e4)), "1e4"); + assert_eq!(format!("{:e}", dbig!(-1e4)), "-1e4"); + assert_eq!(format!("{:e}", dbig!(1e-1)), "1e-1"); + assert_eq!(format!("{:e}", dbig!(-1e-1)), "-1e-1"); + assert_eq!(format!("{:e}", dbig!(1e-4)), "1e-4"); + assert_eq!(format!("{:e}", dbig!(-1e-4)), "-1e-4"); + + assert_eq!(format!("{:e}", DBig::INFINITY), "inf"); + assert_eq!(format!("{:e}", DBig::NEG_INFINITY), "-inf"); + assert_eq!(format!("{:e}", DBig::from_parts(i8::MAX.into(), -1)), "1.27e1"); + assert_eq!(format!("{:e}", DBig::from_parts(i8::MIN.into(), -1)), "-1.28e1"); + assert_eq!(format!("{:e}", DBig::from_parts(i16::MAX.into(), -2)), "3.2767e2"); + assert_eq!(format!("{:e}", DBig::from_parts(i16::MIN.into(), -2)), "-3.2768e2"); + + assert_eq!(format!("{:E}", dbig!(0)), "0E0"); + assert_eq!(format!("{:E}", dbig!(1)), "1E0"); + assert_eq!(format!("{:E}", dbig!(-1)), "-1E0"); + assert_eq!(format!("{:E}", dbig!(1e4)), "1E4"); + assert_eq!(format!("{:E}", dbig!(-1e4)), "-1E4"); + assert_eq!(format!("{:E}", dbig!(1e-1)), "1E-1"); + assert_eq!(format!("{:E}", dbig!(-1e-1)), "-1E-1"); + assert_eq!(format!("{:E}", dbig!(1e-4)), "1E-4"); + assert_eq!(format!("{:E}", dbig!(-1e-4)), "-1E-4"); + + assert_eq!(format!("{:E}", DBig::INFINITY), "inf"); + assert_eq!(format!("{:E}", DBig::NEG_INFINITY), "-inf"); + assert_eq!(format!("{:E}", DBig::from_parts(i8::MAX.into(), -1)), "1.27E1"); + assert_eq!(format!("{:E}", DBig::from_parts(i8::MIN.into(), -1)), "-1.28E1"); + assert_eq!(format!("{:E}", DBig::from_parts(i16::MAX.into(), -2)), "3.2767E2"); + assert_eq!(format!("{:E}", DBig::from_parts(i16::MIN.into(), -2)), "-3.2768E2"); + + assert_eq!(format!("{:.0e}", dbig!(0)), "0e0"); + assert_eq!(format!("{:.0e}", dbig!(1)), "1e0"); + assert_eq!(format!("{:.0e}", dbig!(-1)), "-1e0"); + assert_eq!(format!("{:.0e}", dbig!(1e-1)), "1e-1"); + assert_eq!(format!("{:.0e}", dbig!(-1e-1)), "-1e-1"); + assert_eq!(format!("{:.0e}", dbig!(1e-4)), "1e-4"); + assert_eq!(format!("{:.0e}", dbig!(-1e-4)), "-1e-4"); + assert_eq!(format!("{:8.0e}", dbig!(0)), " 0e0"); + assert_eq!(format!("{:8.0e}", dbig!(1)), " 1e0"); + assert_eq!(format!("{:8.0e}", dbig!(-1)), " -1e0"); + assert_eq!(format!("{:8.0e}", dbig!(1e-1)), " 1e-1"); + assert_eq!(format!("{:8.0e}", dbig!(-1e-1)), " -1e-1"); + assert_eq!(format!("{:08.0e}", dbig!(0)), "000000e0"); + assert_eq!(format!("{:08.0e}", dbig!(1)), "000001e0"); + assert_eq!(format!("{:08.0e}", dbig!(-1)), "-00001e0"); + assert_eq!(format!("{:08.0e}", dbig!(1e-1)), "00001e-1"); + assert_eq!(format!("{:08.0e}", dbig!(-1e-1)), "-0001e-1"); + + assert_eq!(format!("{:.8e}", dbig!(0)), "0.00000000e0"); + assert_eq!(format!("{:.8e}", dbig!(1)), "1.00000000e0"); + assert_eq!(format!("{:.8e}", dbig!(-1)), "-1.00000000e0"); + assert_eq!(format!("{:.8e}", dbig!(1e-1)), "1.00000000e-1"); + assert_eq!(format!("{:.8e}", dbig!(-1e-1)), "-1.00000000e-1"); + assert_eq!(format!("{:10.4e}", dbig!(0)), " 0.0000e0"); + assert_eq!(format!("{:10.4e}", dbig!(1)), " 1.0000e0"); + assert_eq!(format!("{:10.4e}", dbig!(-1)), " -1.0000e0"); + assert_eq!(format!("{:10.4e}", dbig!(1e-1)), " 1.0000e-1"); + assert_eq!(format!("{:10.4e}", dbig!(-1e-1)), "-1.0000e-1"); + assert_eq!(format!("{:010.4e}", dbig!(0)), "000.0000e0"); + assert_eq!(format!("{:010.4e}", dbig!(1)), "001.0000e0"); + assert_eq!(format!("{:010.4e}", dbig!(-1)), "-01.0000e0"); + assert_eq!(format!("{:010.4e}", dbig!(1e-1)), "01.0000e-1"); + assert_eq!(format!("{:010.4e}", dbig!(-1e-1)), "-1.0000e-1"); +} + +#[test] +fn test_format_other_bases() { + assert_eq!(format!("{}", FBig::::from_parts(ibig!(52), -2)), "12.21"); + assert_eq!(format!("{:o}", FBig::::from_parts(ibig!(100), -2)), "1.44o0"); + assert_eq!( + format!("{:e}", FBig::::from_parts(ibig!(134757), 10)), + "g.ghh@13" + ); + assert_eq!( + format!("{:e}", FBig::::from_parts(ibig!(446927), -14)), + "g.ghh@-11" + ); +} + #[test] #[rustfmt::skip::macros(fbig)] fn test_format_debug() { @@ -612,6 +841,33 @@ fn test_format_debug() { } #[test] -fn test_format_roundtrip() { - todo!() // TODO: specifically test special bases +fn test_string_roundtrip() { + let fbig_cases = [ + fbig!(0x12345p-67), + fbig!(-0x123456789abcdef0123p45), + FBin::try_from(f32::MAX).unwrap(), + FBin::try_from(f32::MIN).unwrap(), + FBin::try_from(f32::EPSILON).unwrap(), + FBin::try_from(f64::MAX).unwrap(), + FBin::try_from(f64::MIN).unwrap(), + FBin::try_from(f64::EPSILON).unwrap(), + ]; + + for case in fbig_cases { + assert_eq!(FBin::from_str(case.to_string().as_str()).unwrap(), case); + assert_eq!(FBin::from_str(format!("{:b}", case).as_str()).unwrap(), case); + assert_eq!(FBin::from_str(format!("{:010x}", case).as_str()).unwrap(), case); + assert_eq!(FBin::from_str(format!("{:+010X}", case).as_str()).unwrap(), case); + } + + let dbig_cases = [ + dbig!(0.00012345), + dbig!(-1234567890.12345678901234567890123456789) + ]; + + for case in dbig_cases { + assert_eq!(DBig::from_str(case.to_string().as_str()).unwrap(), case); + assert_eq!(DBig::from_str(format!("{:e}", case).as_str()).unwrap(), case); + assert_eq!(DBig::from_str(format!("{:+010E}", case).as_str()).unwrap(), case); + } } diff --git a/integer/tests/io.rs b/integer/tests/io.rs index 3e08db9..422cd6b 100644 --- a/integer/tests/io.rs +++ b/integer/tests/io.rs @@ -453,3 +453,77 @@ fn test_ibig_debug() { ); } } + +#[test] +fn test_string_roundtrip() { + use std::str::FromStr; + + let ubig_cases = [ + ubig!(12345), + ubig!(1234567890123), + UBig::from(u32::MAX), + UBig::from(u32::MAX) + UBig::ONE, + UBig::from(u64::MAX), + UBig::from(u64::MAX) + UBig::ONE, + UBig::from(u128::MAX), + UBig::from(u128::MAX) + UBig::ONE, + UBig::from(u128::MAX) << 128 | UBig::from(u128::MAX), + ]; + + for case in ubig_cases { + assert_eq!(UBig::from_str(case.to_string().as_str()).unwrap(), case); + assert_eq!(UBig::from_str_radix(case.in_radix(2).to_string().as_str(), 2).unwrap(), case); + assert_eq!(UBig::from_str_radix(case.in_radix(36).to_string().as_str(), 36).unwrap(), case); + assert_eq!( + UBig::from_str_with_radix_prefix(format!("{:#b}", case).as_str()), + Ok((case.clone(), 2)) + ); + assert_eq!( + UBig::from_str_with_radix_prefix(format!("{:#010x}", case).as_str()), + Ok((case.clone(), 16)) + ); + assert_eq!( + UBig::from_str_with_radix_prefix(format!("{:+#010X}", case).as_str()), + Ok((case.clone(), 16)) + ); + assert_eq!( + UBig::from_str_radix(format!("{:#}", case.in_radix(36)).as_str(), 36), + Ok(case) + ); + } + + let ibig_cases = [ + ibig!(-12345), + ibig!(-1234567890123), + IBig::from(i32::MIN), + IBig::from(i32::MIN) + UBig::ONE, + IBig::from(i64::MIN), + IBig::from(i64::MIN) + UBig::ONE, + IBig::from(i128::MIN), + IBig::from(i128::MIN) + UBig::ONE, + IBig::from(i128::MIN) << 128 | IBig::from(i128::MIN), + ]; + + for case in ibig_cases { + assert_eq!(IBig::from_str(case.to_string().as_str()).unwrap(), case); + assert_eq!(IBig::from_str_radix(case.in_radix(2).to_string().as_str(), 2).unwrap(), case); + assert_eq!(IBig::from_str_radix(case.in_radix(16).to_string().as_str(), 16).unwrap(), case); + assert_eq!(IBig::from_str_radix(case.in_radix(36).to_string().as_str(), 36).unwrap(), case); + assert_eq!( + IBig::from_str_with_radix_prefix(format!("{:#b}", case).as_str()), + Ok((case.clone(), 2)) + ); + assert_eq!( + IBig::from_str_with_radix_prefix(format!("{:#010x}", case).as_str()), + Ok((case.clone(), 16)) + ); + assert_eq!( + IBig::from_str_with_radix_prefix(format!("{:+#010X}", case).as_str()), + Ok((case.clone(), 16)) + ); + assert_eq!( + IBig::from_str_radix(format!("{:#}", case.in_radix(36)).as_str(), 36), + Ok(case) + ); + } +} From 8a7a90823d021cb6259fe4ad16113d011f1ba2ff Mon Sep 17 00:00:00 2001 From: Jacob Date: Sat, 3 Feb 2024 10:49:51 +0800 Subject: [PATCH 09/15] Basic operations for Words --- python/src/float.rs | 6 +- python/src/lib.rs | 1 + python/src/utils.rs | 56 ++++++++-- python/src/words.rs | 217 +++++++++++++++++++++++++++++++++++-- python/tests/test_words.py | 85 +++++++++++++++ 5 files changed, 345 insertions(+), 20 deletions(-) create mode 100644 python/tests/test_words.py diff --git a/python/src/float.rs b/python/src/float.rs index 1c18cd8..4aa78a2 100644 --- a/python/src/float.rs +++ b/python/src/float.rs @@ -56,7 +56,7 @@ impl FPy { } fn __float__(&self) -> f64 { - todo!() + self.0.to_f64().value() } } @@ -80,10 +80,10 @@ impl DPy { } } fn unwrap(&self, py: Python) -> PyResult { - // TODO(next): use scientific notation to convert let decimal = py.import(intern!(py, "decimal"))?; let decimal_type = decimal.getattr(intern!(py, "Decimal"))?; - Ok(decimal_type.call1((self.0.to_string(),))?.into()) + let decimal_str = format!("{:e}", self.0); + Ok(decimal_type.call1((decimal_str,))?.into()) } fn __repr__(&self) -> String { diff --git a/python/src/lib.rs b/python/src/lib.rs index 8d88f0f..3905af2 100644 --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -22,5 +22,6 @@ fn dashu(_py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_class::()?; m.add_function(wrap_pyfunction!(utils::auto, m)?)?; + m.add_function(wrap_pyfunction!(utils::autos, m)?)?; Ok(()) } diff --git a/python/src/utils.rs b/python/src/utils.rs index df9a2d4..fafecb5 100644 --- a/python/src/utils.rs +++ b/python/src/utils.rs @@ -1,16 +1,35 @@ -use crate::{convert::conversion_error_to_py, types::*}; +use std::str::FromStr; + +use crate::{ + convert::{conversion_error_to_py, parse_error_to_py}, + types::*, +}; +use dashu_base::{Signed, UnsignedAbs}; +use dashu_float::{DBig, FBig}; +use dashu_int::{IBig, UBig}; +use dashu_ratio::RBig; use pyo3::prelude::*; /// Convert input automatically to corresponding dashu type: -/// (int -> IBig, float -> FBig, decimal -> DBig, fraction -> RBig) +/// (int -> UBig/IBig, float -> FBig, decimal -> DBig, fraction -> RBig) #[pyfunction] pub fn auto(ob: UniInput, py: Python<'_>) -> PyResult { use UniInput::*; + + // shrink IBig to UBig if necessary + let fit_ibig = |i: IBig| { + if i.is_negative() { + IPy(i).into_py(py) + } else { + UPy(i.unsigned_abs()).into_py(py) + } + }; + let obj = match ob { - SmallInt(v) => IPy(v.into()).into_py(py), - BigUint(v) => IPy(v.0.clone().into()).into_py(py), - BigInt(v) => v.clone().into_py(py), - BigIntOwned(v) => IPy(v).into_py(py), + SmallInt(v) => fit_ibig(v.into()), + BigUint(v) => v.clone().into_py(py), + BigInt(v) => fit_ibig(v.0.clone()), + BigIntOwned(v) => fit_ibig(v), SmallFloat(v) => match v.try_into() { Ok(big) => FPy(big).into_py(py), Err(e) => { @@ -26,10 +45,31 @@ pub fn auto(ob: UniInput, py: Python<'_>) -> PyResult { Ok(obj) } +/// Convert input string to corresponding dashu type. +/// The type is heuristically determined #[pyfunction] pub fn autos(s: &str, py: Python<'_>) -> PyResult { - // TODO: accept str input and detect the best representation - todo!() + let obj = if s.contains('/') { + RPy(RBig::from_str_with_radix_prefix(s) + .map_err(parse_error_to_py)? + .0) + .into_py(py) + } else if s.contains(&['p', 'P']) { + FPy(FBig::from_str(s).map_err(parse_error_to_py)?).into_py(py) + } else if s.contains('.') || (!s.contains("0x") && s.contains(&['e', 'E'])) { + DPy(DBig::from_str(s).map_err(parse_error_to_py)?).into_py(py) + } else if s.contains('-') { + IPy(IBig::from_str_with_radix_prefix(s) + .map_err(parse_error_to_py)? + .0) + .into_py(py) + } else { + UPy(UBig::from_str_with_radix_prefix(s) + .map_err(parse_error_to_py)? + .0) + .into_py(py) + }; + Ok(obj) } // TODO: split_dword, double_word, etc. diff --git a/python/src/words.rs b/python/src/words.rs index 6ae2b77..2f8bd2f 100644 --- a/python/src/words.rs +++ b/python/src/words.rs @@ -1,15 +1,33 @@ use crate::types::PyWords; use pyo3::{ - exceptions::{PyIndexError, PyTypeError}, + exceptions::{PyIndexError, PyTypeError, PyValueError}, prelude::*, types::PySlice, }; use dashu_int::Word; +const ERRMSG_WORDS_WRONG_SRC_TYPE: &'static str = + "only a list of word-length integers can be used to construct a Words instance"; +const ERRMSG_WORDS_OOR: &str = "words index out of range"; +const ERRMSG_WORDS_INVALID_INDEX: &str = "words indices must be integers or slices"; +const ERRMSG_WORDS_UNMATCH_INDEX: &str = + "attempt to assign sequence to an extended slice with different length"; +const ERRMSG_WORDS_INVALID_VALUE: &str = "words can only contain word-length integers"; + #[pymethods] impl PyWords { + #[new] + fn __new__(ob: &PyAny) -> PyResult { + if let Ok(list) = as FromPyObject>::extract(ob) { + Ok(PyWords(list)) + } else if let Ok(obj) = as FromPyObject>::extract(ob) { + Ok(PyWords(obj.0.clone())) + } else { + Err(PyTypeError::new_err(ERRMSG_WORDS_WRONG_SRC_TYPE)) + } + } fn __repr__(&self) -> String { format!("", self.0.len()) } @@ -19,19 +37,200 @@ impl PyWords { fn __len__(&self) -> usize { self.0.len() } - fn __getitem__(&self, index: &PyAny) -> PyResult { - if let Ok(n) = ::extract(index) { - self.0 + fn __getitem__(&self, index: &PyAny) -> PyResult { + let py = index.py(); + if let Ok(n) = ::extract(index) { + // parse negative index + let n = if n < 0 { + (self.0.len() as isize + n) as usize + } else { + n as usize + }; + + // get value + let word = self + .0 .get(n) .copied() - .ok_or(PyIndexError::new_err("words index out of range")) + .ok_or(PyIndexError::new_err(ERRMSG_WORDS_OOR))?; + Ok(word.into_py(py)) + } else if let Ok(slice) = index.downcast::() { + let indices = slice.indices(self.0.len() as _)?; + + let new_vec = if indices.step >= 0 { + let (span, skip) = (indices.stop - indices.start, indices.start); + if span > 0 { + self.0 + .iter() + .skip(skip as _) + .take(span as _) + .step_by(indices.step as _) + .copied() + .collect() + } else { + Vec::new() + } + } else { + let (span, skip) = + (indices.start - indices.stop, self.0.len() as isize - 1 - indices.start); + if span > 0 { + self.0 + .iter() + .rev() + .skip(skip as _) + .take(span as _) + .step_by(-indices.step as _) + .copied() + .collect() + } else { + Vec::new() + } + }; + Ok(Self(new_vec).into_py(py)) + } else { + Err(PyTypeError::new_err(ERRMSG_WORDS_INVALID_INDEX)) + } + } + fn __setitem__(&mut self, index: &PyAny, value: &PyAny) -> PyResult<()> { + if let Ok(n) = ::extract(index) { + let value: Word = value.extract()?; + + // parse negative index + let n = if n < 0 { + (self.0.len() as isize + n) as usize + } else { + n as usize + }; + + // assign value + if let Some(w) = self.0.get_mut(n) { + *w = value; + Ok(()) + } else { + Err(PyIndexError::new_err(ERRMSG_WORDS_OOR)) + } + } else if let Ok(slice) = index.downcast::() { + // parse inputs + let indices = slice.indices(self.0.len() as _)?; + let value: Vec = if let Ok(v) = as FromPyObject>::extract(value) { + v + } else if let Ok(v) = as FromPyObject>::extract(value) { + v.0.clone() // FIXME: how to prevent copy here? + } else { + return Err(PyTypeError::new_err(ERRMSG_WORDS_INVALID_VALUE)); + }; + + // check that the indices and the values have the same length + if indices.slicelength as usize != value.len() { + return Err(PyValueError::new_err(ERRMSG_WORDS_UNMATCH_INDEX)); + } + + // assign the values + if indices.step >= 0 { + let (span, skip) = (indices.stop - indices.start, indices.start); + if span > 0 { + self.0 + .iter_mut() + .skip(skip as _) + .take(span as _) + .step_by(indices.step as _) + .zip(value.iter()) + .for_each(|(l, r)| *l = *r); + } + } else { + let (span, skip) = + (indices.start - indices.stop, self.0.len() as isize - 1 - indices.start); + if span > 0 { + self.0 + .iter_mut() + .rev() + .skip(skip as _) + .take(span as _) + .step_by(-indices.step as _) + .zip(value.iter()) + .for_each(|(l, r)| *l = *r); + } + }; + Ok(()) + } else { + Err(PyTypeError::new_err(ERRMSG_WORDS_INVALID_INDEX)) + } + } + fn __delitem__(&mut self, index: &PyAny) -> PyResult<()> { + if let Ok(n) = ::extract(index) { + // parse negative index + let n = if n < 0 { + (self.0.len() as isize + n) as usize + } else { + n as usize + }; + + // remove value + if n < self.0.len() { + self.0.remove(n); + Ok(()) + } else { + Err(PyIndexError::new_err(ERRMSG_WORDS_OOR)) + } } else if let Ok(slice) = index.downcast::() { - todo!() + let indices = slice.indices(self.0.len() as _)?; + + if indices.step >= 0 { + let (span, skip) = (indices.stop - indices.start, indices.start); + if span > 0 { + let (span, skip, step) = (span as usize, skip as usize, indices.step as usize); + self.0 = core::mem::take(&mut self.0) + .into_iter() + .enumerate() + .filter_map(|(i, v)| { + let in_slice = i >= skip && i < (skip + span) && (i - skip) % step == 0; + (!in_slice).then(|| v) + }) + .collect(); + } + } else { + let (span, skip) = + (indices.start - indices.stop, self.0.len() as isize - 1 - indices.start); + if span > 0 { + let (span, skip, step) = (span as usize, skip as usize, -indices.step as usize); + self.0 = core::mem::take(&mut self.0) + .into_iter() + .rev() + .enumerate() + .filter_map(|(i, v)| { + let in_slice = i >= skip && i < (skip + span) && (i - skip) % step == 0; + (!in_slice).then(|| v) + }) + .rev() + .collect(); + } + }; + Ok(()) } else { - Err(PyTypeError::new_err("words indices must be integers or slices")) + Err(PyTypeError::new_err(ERRMSG_WORDS_INVALID_INDEX)) } } - fn __setitem__(&mut self, index: &PyAny, value: Word) { - todo!() + + fn __add__(&self, other: &PyAny) -> PyResult { + let mut out = self.0.clone(); + if let Ok(list) = as FromPyObject>::extract(other) { + out.extend(list.into_iter()); + } else if let Ok(obj) = as FromPyObject>::extract(other) { + out.extend(obj.0.iter()); + } else { + return Err(PyTypeError::new_err(ERRMSG_WORDS_WRONG_SRC_TYPE)); + } + Ok(Self(out)) + } + fn __mul__(&self, count: isize) -> Self { + if count <= 0 { + Self(Vec::new()) + } else { + let mut out = Vec::with_capacity(count as usize * self.0.len()); + for _ in 0..count { + out.extend(self.0.iter()); + } + Self(out) + } } } diff --git a/python/tests/test_words.py b/python/tests/test_words.py new file mode 100644 index 0000000..5fd176c --- /dev/null +++ b/python/tests/test_words.py @@ -0,0 +1,85 @@ +from dashu import * + +slice_list = [ + slice(None, 0), # [:0] + slice(None, 1), # [:1] + slice(None, -1), # [:-1] + slice(0, None), # [0:] + slice(1, None), # [1:] + slice(-1, None), # [-1:] + + slice(None, None, 2), # [::2] + slice(None, 0, 2), # [:0:2] + slice(None, 1, 2), # [:1:2] + slice(None, -1, 2), # [:-1:2] + slice(0, None, 2), # [0::2] + slice(1, None, 2), # [1::2] + slice(-1, None, 2), # [-1::2] + + slice(None, None, -2), # [::-2] + slice(None, 0, -2), # [:0:-2] + slice(None, 1, -2), # [:1:-2] + slice(None, -1, -2), # [:-1:-2] + slice(0, None, -2), # [0::-2] + slice(1, None, -2), # [1::-2] + slice(-1, None, -2), # [-1::-2] +] + +def test_words_get(): + n = UBig(3 ** 300) + words = n.to_words() + words_list = list(words) + + # single index + assert words[0] == words_list[0] + assert words[1] == words_list[1] + assert words[-1] == words_list[-1] + + # slice index + for sl in slice_list: + assert list(words[sl]) == words_list[sl], "{} => {}, {}".format(sl, words, words_list) + +def test_words_set(): + n = UBig(3 ** 300) + + # single index + words = n.to_words() + words_list = list(words) + words[0], words_list[0] = 0, 0 + words[1], words_list[1] = 1, 1 + words[-1], words_list[-1] = 2, 2 + assert list(words) == words_list + + # slice index + for sl in slice_list: + words = n.to_words() + words_list = list(words) + values = list(range(len(words))) + + words[sl] = values[sl] + words_list[sl] = values[sl] + assert list(words) == words_list, "{} => {}, {}".format(sl, words, words_list) + +def test_words_del(): + n = UBig(3 ** 300) + + # single index + words = n.to_words() + words_list = list(words) + del words[0]; del words_list[0] + del words[1]; del words_list[1] + del words[-1]; del words_list[-1] + assert list(words) == words_list + + # slice index + for sl in slice_list: + words = n.to_words() + words_list = list(words) + + del words[sl]; del words_list[sl] + assert list(words) == words_list, "{} => {}, {}".format(sl, words, words_list) + +if __name__ == "__main__": + test_words_get() + test_words_set() + test_words_del() From 62378fc84750bb6390ca7424ffca8c46fc882566 Mon Sep 17 00:00:00 2001 From: Jacob Date: Tue, 13 Feb 2024 21:54:15 +0800 Subject: [PATCH 10/15] Implement Div for UBig and IBig --- python/src/convert.rs | 30 ++++++++++++--------- python/src/int.rs | 63 ++++++++++++++++++++++++++++++++++++++++++- python/src/types.rs | 27 +++++++++++-------- python/src/utils.rs | 21 ++++++++------- rational/CHANGELOG.md | 5 ++++ rational/src/add.rs | 14 +++++----- rational/src/div.rs | 38 ++++++++++++++++++++++++-- rational/tests/add.rs | 21 +++++++++++++++ rational/tests/div.rs | 21 +++++++++++++++ rational/tests/mul.rs | 21 +++++++++++++++ 10 files changed, 218 insertions(+), 43 deletions(-) diff --git a/python/src/convert.rs b/python/src/convert.rs index 1612fdf..5aeef6d 100644 --- a/python/src/convert.rs +++ b/python/src/convert.rs @@ -10,7 +10,7 @@ use pyo3::{ types::{PyBytes, PyDict, PyFloat, PyLong}, FromPyObject, PyAny, PyErr, PyObject, }; -use std::os::raw::c_longlong; +use std::os::raw::{c_double, c_longlong}; use crate::types::*; use dashu_base::{ConversionError, ParseError}; @@ -160,23 +160,29 @@ impl<'source> FromPyObject<'source> for UniInput<'source> { if ob.is_instance_of::() { let (v, overflow) = parse_to_long(ob)?; if overflow { - Ok(Self::BigIntOwned(parse_to_ibig(ob)?)) + Ok(Self::OBInt(parse_to_ibig(ob)?)) + } else if v < 0 { + Ok(Self::Int(v)) } else { - Ok(Self::SmallInt(v)) + Ok(Self::Uint(v as _)) } } else if ob.is_instance_of::() { - let f = ob.extract()?; - Ok(Self::SmallFloat(f)) + let f: c_double = ob.extract()?; + if f.is_nan() { + Err(PyValueError::new_err("nan values are not supported by dashu types")) + } else { + Ok(Self::Float(f)) + } } else if let Ok(u) = as FromPyObject>::extract(ob) { - Ok(Self::BigUint(u)) + Ok(Self::BUint(u)) } else if let Ok(i) = as FromPyObject>::extract(ob) { - Ok(Self::BigInt(i)) + Ok(Self::BInt(i)) } else if let Ok(f) = as FromPyObject>::extract(ob) { - Ok(Self::BigFloat(f)) + Ok(Self::BFloat(f)) } else if let Ok(d) = as FromPyObject>::extract(ob) { - Ok(Self::BigDecimal(d)) + Ok(Self::BDecimal(d)) } else if let Ok(r) = as FromPyObject>::extract(ob) { - Ok(Self::BigRational(r)) + Ok(Self::BRational(r)) } else { // slow path: // get relevant Python types @@ -188,9 +194,9 @@ impl<'source> FromPyObject<'source> for UniInput<'source> { // and check whether the input is an instance of them if ob.is_instance(decimal_type)? { - Ok(Self::BigDecimalOwned(parse_to_dbig(ob)?)) + Ok(Self::OBDecimal(parse_to_dbig(ob)?)) } else if ob.is_instance(fraction_type)? { - Ok(Self::BigRationalOwned(parse_to_rbig(ob)?)) + Ok(Self::OBRational(parse_to_rbig(ob)?)) } else { Err(PyTypeError::new_err("the input is an invalid number or unsupported")) } diff --git a/python/src/int.rs b/python/src/int.rs index 4904312..2862095 100644 --- a/python/src/int.rs +++ b/python/src/int.rs @@ -1,6 +1,7 @@ use std::collections::hash_map::DefaultHasher; use std::hash::Hasher; use std::vec::Vec; +use std::ops::{Add, Sub, Mul, Div}; use dashu_base::{BitTest, Signed}; use pyo3::exceptions::{PyIndexError, PyNotImplementedError, PyValueError}; @@ -16,10 +17,11 @@ use crate::{ convert_from_ibig, convert_from_ubig, parse_error_to_py, parse_signed_index, parse_to_ibig, parse_to_long, parse_to_ubig, }, - types::{IPy, PyWords, UPy}, + types::{IPy, PyWords, UPy, UniInput, FPy, RPy, DPy}, }; use dashu_int::{IBig, UBig, Word}; use num_order::NumHash; +type FBig = dashu_float::FBig; // error messages const ERRMSG_LENGTH_TOO_LARGE: &'static str = "the integer has too many bits for indexing"; @@ -38,6 +40,31 @@ const ERRMSG_WRONG_INDEX_TYPE: &'static str = "indices must be integers or slice const ERRMSG_UBIG_FROM_NEG: &'static str = "can't convert negative int to unsigned"; const ERRMSG_UBIG_BITS_OOR: &'static str = "bits index out of range"; +macro_rules! impl_ubig_binops { + ($py_method:ident, $rs_method:ident) => { + fn $py_method(slf: &UPy, other: UniInput<'_>, py: Python) -> PyObject { + match other { + UniInput::Uint(x) => UPy((&slf.0).$rs_method(x)).into_py(py), + UniInput::Int(x) => IPy(IBig::from((&slf.0).clone()).$rs_method(x)).into_py(py), + UniInput::BUint(x) => UPy((&slf.0).$rs_method(&x.0)).into_py(py), + UniInput::BInt(x) => IPy((&slf.0).$rs_method(&x.0)).into_py(py), + UniInput::OBInt(x) => IPy((&slf.0).$rs_method(x)).into_py(py), + UniInput::Float(x) => FPy((&slf.0).$rs_method(FBig::try_from(x).unwrap())).into_py(py), + UniInput::BFloat(x) => FPy((&slf.0).$rs_method(&x.0)).into_py(py), + UniInput::BDecimal(x) => DPy((&slf.0).$rs_method(&x.0)).into_py(py), + UniInput::OBDecimal(x) => DPy((&slf.0).$rs_method(x)).into_py(py), + UniInput::BRational(x) => RPy((&slf.0).$rs_method(&x.0)).into_py(py), + UniInput::OBRational(x) => RPy((&slf.0).$rs_method(x)).into_py(py), + } + } + }; +} + +impl_ubig_binops!(upy_add, add); +impl_ubig_binops!(upy_sub, sub); +impl_ubig_binops!(upy_mul, mul); +impl_ubig_binops!(upy_div, div); + #[pymethods] impl UPy { #[new] @@ -244,6 +271,40 @@ impl UPy { }; Ok(Self(uint)) } + + /********** operators **********/ + // fn __add__(&self, other: UniInput<'_>, py: Python) -> PyObject { + // match other { + // UniInput::Uint(x) => UPy(&self.0 + x).into_py(py), + // UniInput::Int(x) => IPy(IBig::from(self.0.clone()) + x).into_py(py), + // UniInput::BUint(x) => UPy(&self.0 + &x.0).into_py(py), + // UniInput::BInt(x) => IPy(&self.0 + &x.0).into_py(py), + // UniInput::OBInt(x) => IPy(&self.0 + x).into_py(py), + // UniInput::Float(x) => FPy(&self.0 + FBig::try_from(x).unwrap()).into_py(py), + // UniInput::BFloat(x) => FPy(&self.0 + &x.0).into_py(py), + // UniInput::BDecimal(x) => DPy(&self.0 + &x.0).into_py(py), + // UniInput::OBDecimal(x) => DPy(&self.0 + x).into_py(py), + // UniInput::BRational(x) => RPy(&self.0 + &x.0).into_py(py), + // UniInput::OBRational(x) => RPy(&self.0 + x).into_py(py), + // } + // } + + #[inline] + fn __add__(&self, other: UniInput<'_>, py: Python) -> PyObject { + upy_add(&self, other, py) + } + #[inline] + fn __sub__(&self, other: UniInput<'_>, py: Python) -> PyObject { + upy_sub(&self, other, py) + } + #[inline] + fn __mul__(&self, other: UniInput<'_>, py: Python) -> PyObject { + upy_mul(&self, other, py) + } + #[inline] + fn __div__(&self, other: UniInput<'_>, py: Python) -> PyObject { + upy_div(&self, other, py) + } } #[pymethods] diff --git a/python/src/types.rs b/python/src/types.rs index 4078eeb..dfbadd6 100644 --- a/python/src/types.rs +++ b/python/src/types.rs @@ -1,5 +1,5 @@ use pyo3::prelude::*; -use std::os::raw::{c_double, c_longlong}; +use std::os::raw::{c_double, c_longlong, c_ulonglong}; use dashu_base::Sign; use dashu_float::DBig; @@ -82,15 +82,20 @@ impl From for RPy { pub struct PyWords(pub std::vec::Vec); /// An input type that accepts all possible numeric types from Python +/// +/// Notes: +/// - Variants starting with 'B': big numbers +/// - Variants starting with 'OB': owned big numbers pub enum UniInput<'a> { - SmallInt(c_longlong), // from int - BigUint(PyRef<'a, UPy>), - BigInt(PyRef<'a, IPy>), - BigIntOwned(IBig), // from int - SmallFloat(c_double), // from float - BigFloat(PyRef<'a, FPy>), - BigDecimal(PyRef<'a, DPy>), - BigDecimalOwned(DBig), // from decimal.Decimal - BigRational(PyRef<'a, RPy>), - BigRationalOwned(RBig), // from fractions.Fraction + Uint(c_ulonglong), // from int + Int(c_longlong), // from int + BUint(PyRef<'a, UPy>), + BInt(PyRef<'a, IPy>), + OBInt(IBig), // from int + Float(c_double), // from float + BFloat(PyRef<'a, FPy>), + BDecimal(PyRef<'a, DPy>), + OBDecimal(DBig), // from decimal.Decimal + BRational(PyRef<'a, RPy>), + OBRational(RBig), // from fractions.Fraction } diff --git a/python/src/utils.rs b/python/src/utils.rs index fafecb5..e74d8df 100644 --- a/python/src/utils.rs +++ b/python/src/utils.rs @@ -26,21 +26,22 @@ pub fn auto(ob: UniInput, py: Python<'_>) -> PyResult { }; let obj = match ob { - SmallInt(v) => fit_ibig(v.into()), - BigUint(v) => v.clone().into_py(py), - BigInt(v) => fit_ibig(v.0.clone()), - BigIntOwned(v) => fit_ibig(v), - SmallFloat(v) => match v.try_into() { + Uint(v) => UPy(v.into()).into_py(py), + Int(v) => fit_ibig(v.into()), + BUint(v) => v.clone().into_py(py), + BInt(v) => fit_ibig(v.0.clone()), + OBInt(v) => fit_ibig(v), + Float(v) => match v.try_into() { Ok(big) => FPy(big).into_py(py), Err(e) => { return Err(conversion_error_to_py(e)); } }, - BigFloat(v) => v.clone().into_py(py), - BigDecimal(v) => v.clone().into_py(py), - BigDecimalOwned(v) => DPy(v).into_py(py), - BigRational(v) => v.clone().into_py(py), - BigRationalOwned(v) => RPy(v).into_py(py), + BFloat(v) => v.clone().into_py(py), + BDecimal(v) => v.clone().into_py(py), + OBDecimal(v) => DPy(v).into_py(py), + BRational(v) => v.clone().into_py(py), + OBRational(v) => RPy(v).into_py(py), }; Ok(obj) } diff --git a/rational/CHANGELOG.md b/rational/CHANGELOG.md index 2353c03..0e7d2d9 100644 --- a/rational/CHANGELOG.md +++ b/rational/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## Unreleased + +- Implement `Div` and `Div` for `UBig`/`IBig` +- Bugfix of division between big rational and big integers. + ## 0.4.1 - Implement `AbsOrd` between `RBig`/`Relaxed` and `UBig`/`IBig`/`FBig`. diff --git a/rational/src/add.rs b/rational/src/add.rs index 14a3ab2..c53fafb 100644 --- a/rational/src/add.rs +++ b/rational/src/add.rs @@ -59,7 +59,7 @@ impl_binop_with_macro!(impl Sub for Relaxed, sub, impl_addsub_with_relaxed); impl_binop_assign_by_taking!(impl AddAssign for Relaxed, add_assign, add); impl_binop_assign_by_taking!(impl SubAssign for Relaxed, sub_assign, sub); -macro_rules! impl_add_or_sub_int_with_rbig { +macro_rules! impl_addsub_int_with_rbig { ( $a:ident, $b:ident, $i:ident, $ra:ident, $rb:ident, $ri:ident, $method:ident @@ -84,12 +84,12 @@ macro_rules! impl_int_sub_rbig { }) }}; } -impl_binop_with_int!(impl Add, add, impl_add_or_sub_int_with_rbig); -impl_binop_with_int!(impl Add, add, impl_add_or_sub_int_with_rbig); -impl_binop_with_int!(impl Sub, sub, impl_add_or_sub_int_with_rbig); -impl_binop_with_int!(impl Sub, sub, impl_add_or_sub_int_with_rbig); -impl_binop_with_int!(impl Add for UBig, add, impl_add_or_sub_int_with_rbig); -impl_binop_with_int!(impl Add for IBig, add, impl_add_or_sub_int_with_rbig); +impl_binop_with_int!(impl Add, add, impl_addsub_int_with_rbig); +impl_binop_with_int!(impl Add, add, impl_addsub_int_with_rbig); +impl_binop_with_int!(impl Sub, sub, impl_addsub_int_with_rbig); +impl_binop_with_int!(impl Sub, sub, impl_addsub_int_with_rbig); +impl_binop_with_int!(impl Add for UBig, add, impl_addsub_int_with_rbig); +impl_binop_with_int!(impl Add for IBig, add, impl_addsub_int_with_rbig); impl_binop_with_int!(impl Sub for UBig, sub, impl_int_sub_rbig); impl_binop_with_int!(impl Sub for IBig, sub, impl_int_sub_rbig); diff --git a/rational/src/div.rs b/rational/src/div.rs index 29cfab4..64a407f 100644 --- a/rational/src/div.rs +++ b/rational/src/div.rs @@ -110,7 +110,7 @@ macro_rules! impl_rbig_div_ubig { let g = $ra.gcd($ri); RBig(Repr { numerator: $a / &g, - denominator: ($b / g) * $i, + denominator: $b * ($i / g), }) }}; } @@ -127,12 +127,31 @@ macro_rules! impl_rbig_div_ibig { let g = $ra.gcd($ri); RBig(Repr { numerator: $a / &g * $i.sign(), - denominator: ($b / g) * $i.unsigned_abs(), + denominator: $b * ($i.unsigned_abs() / g), + }) + }}; +} +macro_rules! impl_ubig_or_ibig_div_rbig { + ( + $a:ident, $b:ident, $i:ident, + $ra:ident, $rb:ident, $ri:ident, $method:ident + ) => {{ + if $ra.is_zero() { + panic_divide_by_0() + } + + let _unused = $rb; + let g = $ra.gcd($ri); + RBig(Repr { + numerator: $b * ($i / &g) * $a.sign(), + denominator: $a.unsigned_abs() / g, }) }}; } impl_binop_with_int!(impl Div, div, RBig, impl_rbig_div_ubig); impl_binop_with_int!(impl Div, div, RBig, impl_rbig_div_ibig); +impl_binop_with_int!(impl Div for UBig, div, RBig, impl_ubig_or_ibig_div_rbig); +impl_binop_with_int!(impl Div for IBig, div, RBig, impl_ubig_or_ibig_div_rbig); macro_rules! impl_relaxed_div_ibig { ( @@ -160,8 +179,23 @@ macro_rules! impl_relaxed_div_ubig { Relaxed::from_parts($a, $b * $i) }}; } +macro_rules! impl_ubig_or_ibig_div_relaxed { + ( + $a:ident, $b:ident, $i:ident, + $ra:ident, $rb:ident, $ri:ident, $method:ident + ) => {{ + if $ra.is_zero() { + panic_divide_by_0() + } + + let _unused = ($ra, $rb, $ri); + Relaxed::from_parts($b * $i * $a.sign(), $a.unsigned_abs()) + }}; +} impl_binop_with_int!(impl Div, div, Relaxed, impl_relaxed_div_ibig); impl_binop_with_int!(impl Div, div, Relaxed, impl_relaxed_div_ubig); +impl_binop_with_int!(impl Div for UBig, div, Relaxed, impl_ubig_or_ibig_div_relaxed); +impl_binop_with_int!(impl Div for IBig, div, Relaxed, impl_ubig_or_ibig_div_relaxed); macro_rules! impl_euclid_div { ( diff --git a/rational/tests/add.rs b/rational/tests/add.rs index 38ef153..ca70101 100644 --- a/rational/tests/add.rs +++ b/rational/tests/add.rs @@ -92,3 +92,24 @@ fn test_add_relaxed() { test_add_sub(b, a, c); } } + +#[test] +fn test_add_with_int() { + assert_eq!(rbig!(0) + ubig!(1), rbig!(1)); + assert_eq!(rbig!(~0) + ubig!(1), rbig!(~1)); + assert_eq!(rbig!(1) + ubig!(1), rbig!(2)); + assert_eq!(rbig!(~1) + ubig!(1), rbig!(~2)); + assert_eq!(rbig!(-1/2) + ibig!(10), rbig!(19/2)); + assert_eq!(rbig!(~-1/2) + ibig!(10), rbig!(~19/2)); + assert_eq!(rbig!(9/5) + ibig!(-3), rbig!(-6/5)); + assert_eq!(rbig!(~9/5) + ibig!(-3), rbig!(~-6/5)); + + assert_eq!(ubig!(0) + rbig!(1), rbig!(1)); + assert_eq!(ubig!(0) + rbig!(~1), rbig!(~1)); + assert_eq!(ubig!(1) + rbig!(1), rbig!(2)); + assert_eq!(ubig!(1) + rbig!(~1), rbig!(~2)); + assert_eq!(ibig!(-10) + rbig!(1/2), rbig!(-19/2)); + assert_eq!(ibig!(-10) + rbig!(~1/2), rbig!(~-19/2)); + assert_eq!(ibig!(3) + rbig!(-9/5), rbig!(6/5)); + assert_eq!(ibig!(3) + rbig!(~-9/5), rbig!(~6/5)); +} \ No newline at end of file diff --git a/rational/tests/div.rs b/rational/tests/div.rs index e67882b..cb9886b 100644 --- a/rational/tests/div.rs +++ b/rational/tests/div.rs @@ -175,3 +175,24 @@ fn test_div_rem_euclid_relaxed() { assert_eq!(n.div_rem_euclid(d), (q.clone(), r.clone())); } } + +#[test] +fn test_div_with_int() { + assert_eq!(rbig!(0) / ubig!(1), rbig!(0)); + assert_eq!(rbig!(~0) / ubig!(1), rbig!(~0)); + assert_eq!(rbig!(1) / ubig!(1), rbig!(1)); + assert_eq!(rbig!(~1) / ubig!(1), rbig!(~1)); + assert_eq!(rbig!(-1) / ibig!(10), rbig!(-1/10)); + assert_eq!(rbig!(~-1) / ibig!(10), rbig!(~-1/10)); + assert_eq!(rbig!(9/5) / ibig!(-3), rbig!(-3/5)); + assert_eq!(rbig!(~9/5) / ibig!(-3), rbig!(~-3/5)); + + assert_eq!(ubig!(0) / rbig!(1), rbig!(0)); + assert_eq!(ubig!(0) / rbig!(~1), rbig!(~0)); + assert_eq!(ubig!(1) / rbig!(1), rbig!(1)); + assert_eq!(ubig!(1) / rbig!(~1), rbig!(~1)); + assert_eq!(ibig!(-1) / rbig!(10), rbig!(-1/10)); + assert_eq!(ibig!(-1) / rbig!(~10), rbig!(~-1/10)); + assert_eq!(ibig!(-3) / rbig!(9/5), rbig!(-5/3)); + assert_eq!(ibig!(-3) / rbig!(~9/5), rbig!(~-5/3)); +} diff --git a/rational/tests/mul.rs b/rational/tests/mul.rs index 32055ab..72bd09e 100644 --- a/rational/tests/mul.rs +++ b/rational/tests/mul.rs @@ -85,3 +85,24 @@ fn test_add_with_ibig() { assert_eq!(b * r, c.clone().relax()); } } + +#[test] +fn test_mul_with_int() { + assert_eq!(rbig!(0) * ubig!(1), rbig!(0)); + assert_eq!(rbig!(~0) * ubig!(1), rbig!(~0)); + assert_eq!(rbig!(1) * ubig!(1), rbig!(1)); + assert_eq!(rbig!(~1) * ubig!(1), rbig!(~1)); + assert_eq!(rbig!(-1/2) * ibig!(-2), rbig!(1)); + assert_eq!(rbig!(~-1/2) * ibig!(-2), rbig!(~1)); + assert_eq!(rbig!(5/12) * ibig!(-3), rbig!(-5/4)); + assert_eq!(rbig!(~5/12) * ibig!(-3), rbig!(~-5/4)); + + assert_eq!(ubig!(0) * rbig!(1), rbig!(0)); + assert_eq!(ubig!(0) * rbig!(~1), rbig!(~0)); + assert_eq!(ubig!(1) * rbig!(1), rbig!(1)); + assert_eq!(ubig!(1) * rbig!(~1), rbig!(~1)); + assert_eq!(ibig!(-2) * rbig!(-1/2), rbig!(1)); + assert_eq!(ibig!(-2) * rbig!(~-1/2), rbig!(~1)); + assert_eq!(ibig!(-3) * rbig!(5/12), rbig!(-5/4)); + assert_eq!(ibig!(-3) * rbig!(~5/12), rbig!(~-5/4)); +} From b27a04628516288107b0ad7ab21ca859765c9377 Mon Sep 17 00:00:00 2001 From: Jacob Date: Thu, 15 Feb 2024 18:09:50 +0800 Subject: [PATCH 11/15] Python implementations --- integer/CHANGELOG.md | 1 + integer/src/convert.rs | 24 +++++ python/src/convert.rs | 2 +- python/src/int.rs | 174 +++++++++++++++++++++++++++-------- python/tests/test_convert.py | 27 ++++++ 5 files changed, 187 insertions(+), 41 deletions(-) create mode 100644 python/tests/test_convert.py diff --git a/integer/CHANGELOG.md b/integer/CHANGELOG.md index f8ff783..9e403de 100644 --- a/integer/CHANGELOG.md +++ b/integer/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Add `UBig::ones`. +- Add `IBig::as_ubig`. - Implement `TryFrom` and `TryFrom` for `f32`/`f64`. - Implement `IBig::{from_le_bytes, from_be_bytes}` and `IBig::{to_le_bytes, to_be_bytes}`. - The alterative `Debug` output of `UBig` and `IBig` will include `(digits: x, bits: y)` instead of `(x digits, y bits)`. diff --git a/integer/src/convert.rs b/integer/src/convert.rs index 5aea007..bb907d2 100644 --- a/integer/src/convert.rs +++ b/integer/src/convert.rs @@ -481,6 +481,30 @@ impl IBig { Inexact(val, diff) => Inexact(sign * val, sign * diff), } } + + /// Regard the number as a [UBig] number and return a reference of [UBig] type. + /// + /// The conversion is only successful when the number is positive + /// + /// # Examples + /// + /// ``` + /// # use dashu_int::{IBig, UBig}; + /// assert_eq!(IBig::from(123).as_ibig(), &UBig::from(123)); + /// assert_eq!(IBig::from(-123).as_ibig(), None); + /// ``` + #[inline] + pub const fn as_ubig(&self) -> Option<&UBig> { + match self.sign() { + Sign::Positive => { + // SAFETY: UBig and IBig are both transparent wrapper around the Repr type. + // This conversion is only available for immutable references and + // positive numbers, so that the sign will not be messed up. + unsafe { Some(core::mem::transmute(self)) } + }, + Sign::Negative => None + } + } } macro_rules! ubig_unsigned_conversions { diff --git a/python/src/convert.rs b/python/src/convert.rs index 5aeef6d..39b10ab 100644 --- a/python/src/convert.rs +++ b/python/src/convert.rs @@ -104,7 +104,7 @@ pub fn convert_from_ubig(ob: &UBig, py: Python) -> PyResult { pub fn parse_to_ibig(ob: &PyAny) -> PyResult { let py = ob.py(); let bit_len: usize = ob.call_method0(intern!(py, "bit_length"))?.extract()?; - let byte_len = (bit_len + 7) / 8; + let byte_len = bit_len / 8 + 1; // extra byte for sign // The stable API `to_bytes` is also chosen over ffi::_PyLong_AsByteArray here. let kwargs = PyDict::new(py); diff --git a/python/src/int.rs b/python/src/int.rs index 2862095..cbd9dae 100644 --- a/python/src/int.rs +++ b/python/src/int.rs @@ -1,9 +1,9 @@ use std::collections::hash_map::DefaultHasher; use std::hash::Hasher; use std::vec::Vec; -use std::ops::{Add, Sub, Mul, Div}; +use std::ops::*; -use dashu_base::{BitTest, Signed}; +use dashu_base::{BitTest, Sign, Signed, UnsignedAbs}; use pyo3::exceptions::{PyIndexError, PyNotImplementedError, PyValueError}; use pyo3::prelude::*; use pyo3::types::PySlice; @@ -19,7 +19,7 @@ use crate::{ }, types::{IPy, PyWords, UPy, UniInput, FPy, RPy, DPy}, }; -use dashu_int::{IBig, UBig, Word}; +use dashu_int::{IBig, UBig, Word, fast_div}; use num_order::NumHash; type FBig = dashu_float::FBig; @@ -37,33 +37,48 @@ const ERRMSG_WRONG_ENDIANNESS: &'static str = "byteorder must be either 'little' const ERRMSG_NEGATIVE_TO_UNSIGNED: &'static str = "can't convert negative int to unsigned"; const ERRMSG_INT_WITH_RADIX: &'static str = "can't convert non-string with explicit base"; const ERRMSG_WRONG_INDEX_TYPE: &'static str = "indices must be integers or slices"; -const ERRMSG_UBIG_FROM_NEG: &'static str = "can't convert negative int to unsigned"; const ERRMSG_UBIG_BITS_OOR: &'static str = "bits index out of range"; macro_rules! impl_ubig_binops { - ($py_method:ident, $rs_method:ident) => { - fn $py_method(slf: &UPy, other: UniInput<'_>, py: Python) -> PyObject { - match other { - UniInput::Uint(x) => UPy((&slf.0).$rs_method(x)).into_py(py), - UniInput::Int(x) => IPy(IBig::from((&slf.0).clone()).$rs_method(x)).into_py(py), - UniInput::BUint(x) => UPy((&slf.0).$rs_method(&x.0)).into_py(py), - UniInput::BInt(x) => IPy((&slf.0).$rs_method(&x.0)).into_py(py), - UniInput::OBInt(x) => IPy((&slf.0).$rs_method(x)).into_py(py), - UniInput::Float(x) => FPy((&slf.0).$rs_method(FBig::try_from(x).unwrap())).into_py(py), - UniInput::BFloat(x) => FPy((&slf.0).$rs_method(&x.0)).into_py(py), - UniInput::BDecimal(x) => DPy((&slf.0).$rs_method(&x.0)).into_py(py), - UniInput::OBDecimal(x) => DPy((&slf.0).$rs_method(x)).into_py(py), - UniInput::BRational(x) => RPy((&slf.0).$rs_method(&x.0)).into_py(py), - UniInput::OBRational(x) => RPy((&slf.0).$rs_method(x)).into_py(py), + ($py_method:ident, $py_method_rev:ident, $rs_method:ident) => { + fn $py_method(lhs: &UPy, rhs: UniInput<'_>, py: Python) -> PyObject { + match rhs { + UniInput::Uint(x) => UPy((&lhs.0).$rs_method(x)).into_py(py), + UniInput::Int(x) => IPy(lhs.0.as_ibig().$rs_method(x)).into_py(py), + UniInput::BUint(x) => UPy((&lhs.0).$rs_method(&x.0)).into_py(py), + UniInput::BInt(x) => IPy((&lhs.0).$rs_method(&x.0)).into_py(py), + UniInput::OBInt(x) => IPy((&lhs.0).$rs_method(x)).into_py(py), + UniInput::Float(x) => FPy((&lhs.0).$rs_method(FBig::try_from(x).unwrap())).into_py(py), + UniInput::BFloat(x) => FPy((&lhs.0).$rs_method(&x.0)).into_py(py), + UniInput::BDecimal(x) => DPy((&lhs.0).$rs_method(&x.0)).into_py(py), + UniInput::OBDecimal(x) => DPy((&lhs.0).$rs_method(x)).into_py(py), + UniInput::BRational(x) => RPy((&lhs.0).$rs_method(&x.0)).into_py(py), + UniInput::OBRational(x) => RPy((&lhs.0).$rs_method(x)).into_py(py), + } + } + + fn $py_method_rev(lhs: UniInput<'_>, rhs: &UPy, py: Python) -> PyObject { + match lhs { + UniInput::Uint(x) => UPy(x.$rs_method(&rhs.0).into()).into_py(py), + UniInput::Int(x) => IPy(x.$rs_method(rhs.0.as_ibig()).into()).into_py(py), + UniInput::BUint(x) => UPy((&x.0).$rs_method(&rhs.0)).into_py(py), + UniInput::BInt(x) => IPy((&x.0).$rs_method(&rhs.0)).into_py(py), + UniInput::OBInt(x) => IPy(x.$rs_method(&rhs.0)).into_py(py), + UniInput::Float(x) => FPy(FBig::try_from(x).unwrap().$rs_method(&rhs.0)).into_py(py), + UniInput::BFloat(x) => FPy((&x.0).$rs_method(&rhs.0)).into_py(py), + UniInput::BDecimal(x) => DPy((&x.0).$rs_method(&rhs.0)).into_py(py), + UniInput::OBDecimal(x) => DPy(x.$rs_method(&rhs.0)).into_py(py), + UniInput::BRational(x) => RPy((&x.0).$rs_method(&rhs.0)).into_py(py), + UniInput::OBRational(x) => RPy(x.$rs_method(&rhs.0)).into_py(py), } } }; } -impl_ubig_binops!(upy_add, add); -impl_ubig_binops!(upy_sub, sub); -impl_ubig_binops!(upy_mul, mul); -impl_ubig_binops!(upy_div, div); +impl_ubig_binops!(upy_add, upy_radd, add); +impl_ubig_binops!(upy_sub, upy_rsub, sub); +impl_ubig_binops!(upy_mul, upy_rmul, mul); +impl_ubig_binops!(upy_div, upy_rdiv, div); #[pymethods] impl UPy { @@ -80,7 +95,7 @@ impl UPy { if let Ok(n) = u64::try_from(v) { Ok(UPy(UBig::from(n))) } else { - Err(PyOverflowError::new_err(ERRMSG_UBIG_FROM_NEG)) + Err(PyOverflowError::new_err(ERRMSG_NEGATIVE_TO_UNSIGNED)) } } else { Ok(UPy(parse_to_ubig(ob)?)) @@ -273,38 +288,117 @@ impl UPy { } /********** operators **********/ - // fn __add__(&self, other: UniInput<'_>, py: Python) -> PyObject { - // match other { - // UniInput::Uint(x) => UPy(&self.0 + x).into_py(py), - // UniInput::Int(x) => IPy(IBig::from(self.0.clone()) + x).into_py(py), - // UniInput::BUint(x) => UPy(&self.0 + &x.0).into_py(py), - // UniInput::BInt(x) => IPy(&self.0 + &x.0).into_py(py), - // UniInput::OBInt(x) => IPy(&self.0 + x).into_py(py), - // UniInput::Float(x) => FPy(&self.0 + FBig::try_from(x).unwrap()).into_py(py), - // UniInput::BFloat(x) => FPy(&self.0 + &x.0).into_py(py), - // UniInput::BDecimal(x) => DPy(&self.0 + &x.0).into_py(py), - // UniInput::OBDecimal(x) => DPy(&self.0 + x).into_py(py), - // UniInput::BRational(x) => RPy(&self.0 + &x.0).into_py(py), - // UniInput::OBRational(x) => RPy(&self.0 + x).into_py(py), - // } - // } - #[inline] fn __add__(&self, other: UniInput<'_>, py: Python) -> PyObject { upy_add(&self, other, py) } #[inline] + fn __radd__(&self, other: UniInput<'_>, py: Python) -> PyObject { + upy_radd(other, &self, py) + } + #[inline] fn __sub__(&self, other: UniInput<'_>, py: Python) -> PyObject { upy_sub(&self, other, py) } #[inline] + fn __rsub__(&self, other: UniInput<'_>, py: Python) -> PyObject { + upy_rsub(other, &self, py) + } + #[inline] fn __mul__(&self, other: UniInput<'_>, py: Python) -> PyObject { upy_mul(&self, other, py) } #[inline] - fn __div__(&self, other: UniInput<'_>, py: Python) -> PyObject { + fn __rmul__(&self, other: UniInput<'_>, py: Python) -> PyObject { + upy_rmul(other, &self, py) + } + #[inline] + fn __truediv__(&self, other: UniInput<'_>, py: Python) -> PyObject { upy_div(&self, other, py) } + #[inline] + fn __rtruediv__(&self, other: UniInput<'_>, py: Python) -> PyObject { + upy_rdiv(other, &self, py) + } + #[inline] + fn __mod__(&self, other: UniInput<'_>, py: Python) -> PyObject { + fn upy_mod(lhs: &UPy, rhs: UniInput<'_>, py: Python) -> PyObject { + match rhs { + UniInput::Uint(x) => (&lhs.0).rem(x).into_py(py), + UniInput::Int(x) => lhs.0.as_ibig().rem(x).into_py(py), + UniInput::BUint(x) => UPy((&lhs.0).rem(&x.0)).into_py(py), + UniInput::BInt(x) => UPy((&lhs.0).rem(&x.0)).into_py(py), + UniInput::OBInt(x) => UPy((&lhs.0).rem(x)).into_py(py), + _ => todo!() + } + } + upy_mod(self, other, py) + } + #[inline] + fn __pow__(&self, other: UniInput, modulus: Option, py: Python) -> PyResult { + use fast_div::ConstDivisor; + + if let Some(m) = modulus { + // first parse the modulus + let (_sign, ring) = match m { + UniInput::Uint(x) => (Sign::Positive, ConstDivisor::new(x.into())), + UniInput::BUint(x) => (Sign::Positive, ConstDivisor::new(x.0.clone())), + UniInput::Int(x) => (x.sign(), ConstDivisor::new(x.unsigned_abs().into())), + UniInput::BInt(x) => (x.0.sign(), ConstDivisor::new((&x.0).unsigned_abs())), + UniInput::OBInt(x) => { + let (sign, m) = x.into_parts(); + (sign, ConstDivisor::new(m)) + } + _ => todo!() + }; + + match other { + UniInput::Uint(x) => Ok(UPy(ring.reduce(self.0.clone()).pow(&x.into()).residue()).into_py(py)), + _ => todo!() + } + } else { + match other { + UniInput::Uint(x) => Ok(UPy(self.0.pow(x as _)).into_py(py)), + _ => todo!() + } + } + } + #[inline] + fn __pos__(&self, py: Python) -> PyObject { + todo!() + } + #[inline] + fn __neg__(&self, py: Python) -> PyObject { + todo!() + } + #[inline] + fn __abs__(&self, py: Python) -> PyObject { + todo!() + } + #[inline] + fn __nonzero__(&self, py: Python) -> PyObject { + todo!() + } + #[inline] + fn __lshift__(&self, other: usize, py: Python) -> PyObject { + todo!() + } + #[inline] + fn __rshift__(&self, other: usize, py: Python) -> PyObject { + todo!() + } + #[inline] + fn __and__(&self, other: &PyAny, py: Python) -> PyObject { + todo!() + } + #[inline] + fn __or__(&self, other: &PyAny, py: Python) -> PyObject { + todo!() + } + #[inline] + fn __xor__(&self, other: &PyAny, py: Python) -> PyObject { + todo!() + } } #[pymethods] diff --git a/python/tests/test_convert.py b/python/tests/test_convert.py new file mode 100644 index 0000000..ec7969b --- /dev/null +++ b/python/tests/test_convert.py @@ -0,0 +1,27 @@ +from dashu import * + +def test_int_conversions(): + # small cases + test_cases = [ + (0, UBig), + (1, UBig), + (-1, IBig), + (0xffffffffffffffffffff, UBig), + (-0xffffffffffffffffffff, IBig), + ] + + # large cases + for i in range(6): + v = (-3)**(9**i) + test_cases.append((v, UBig if v >= 0 else IBig)) + + # testing + for v, t in test_cases: + if v < 0: # test constructors + _ = IBig(v) + else: + _ = UBig(v), IBig(v) + assert type(auto(v)) == t # test result type of auto + +if __name__ == "__main__": + test_int_conversions() \ No newline at end of file From a9eb9abca2713e0249c171e36b6ecb0e58e3d969 Mon Sep 17 00:00:00 2001 From: Jacob Date: Fri, 16 Feb 2024 14:40:46 +0800 Subject: [PATCH 12/15] Fix fmt and tests --- float/src/consts.rs | 2 +- float/src/convert.rs | 2 ++ float/src/math/mod.rs | 31 +++++++++++++++++++++++++++++ float/tests/io.rs | 8 ++++---- integer/src/convert.rs | 10 +++++----- integer/src/div_ops.rs | 42 ++++++++++++++++++++++++++++++++++++++++ integer/src/lib.rs | 6 +++--- integer/tests/div.rs | 10 ++++++++++ integer/tests/io.rs | 36 ++++++++++++++-------------------- integer/tests/modular.rs | 8 ++++---- python/src/int.rs | 33 ++++++++++++++++++++----------- python/src/types.rs | 4 ++-- rational/tests/add.rs | 14 +++++++------- rational/tests/div.rs | 10 +++++----- rational/tests/mul.rs | 12 ++++++------ 15 files changed, 159 insertions(+), 69 deletions(-) create mode 100644 float/src/math/mod.rs diff --git a/float/src/consts.rs b/float/src/consts.rs index 23247a2..7c57967 100644 --- a/float/src/consts.rs +++ b/float/src/consts.rs @@ -1,3 +1,3 @@ //! Constants calculation -// to be implemented +// to be implemented, should be associated with the Context type diff --git a/float/src/convert.rs b/float/src/convert.rs index ca222c8..d87c34c 100644 --- a/float/src/convert.rs +++ b/float/src/convert.rs @@ -351,6 +351,8 @@ impl FBig { .map(|repr| FBig::new(repr, context)) } + // TODO: implement quantize() (like the python decimal) + /// Convert the float number to integer with the given rounding mode. /// /// # Warning diff --git a/float/src/math/mod.rs b/float/src/math/mod.rs new file mode 100644 index 0000000..c642ffb --- /dev/null +++ b/float/src/math/mod.rs @@ -0,0 +1,31 @@ +//! Implementations of advanced math functions + +// TODO: implement the math functions as associated methods, and add them to FBig through a trait +// REF: https://pkg.go.dev/github.com/ericlagergren/decimal + +enum FpResult { + Normal(Repr), + Overflow, + Underflow, + NaN, + + /// An exact infinite result is obtained from finite inputs, such as + /// divide by zero, logarithm on zero. + Infinite, +} + +impl Context { + fn sin(&self, repr: Repr) -> FpResult { + todo!() + } +} + +trait ContextOps { + fn context(&self) -> &Context; + fn repr(&self) -> &Repr; + + #[inline] + fn sin(&self) -> FpResult { + self.context().sin(self.repr()) + } +} \ No newline at end of file diff --git a/float/tests/io.rs b/float/tests/io.rs index 479cf1b..eebf4f1 100644 --- a/float/tests/io.rs +++ b/float/tests/io.rs @@ -818,7 +818,7 @@ fn test_format_debug() { assert_eq!( format!("{:#?}", dbig!(1234e-2).repr()), r#"Repr { - significand: 1234 (4 digits, 11 bits), + significand: 1234 (digits: 4, bits: 11), exponent: 10 ^ -2, }"# ); @@ -832,7 +832,7 @@ fn test_format_debug() { assert_eq!( format!("{:#?}", dbig!(1234e-2)), r#"FBig { - significand: 1234 (4 digits, 11 bits), + significand: 1234 (digits: 4, bits: 11), exponent: 10 ^ -2, precision: 4, rounding: HalfAway, @@ -843,7 +843,7 @@ fn test_format_debug() { #[test] fn test_string_roundtrip() { let fbig_cases = [ - fbig!(0x12345p-67), + fbig!(0x12345p - 67), fbig!(-0x123456789abcdef0123p45), FBin::try_from(f32::MAX).unwrap(), FBin::try_from(f32::MIN).unwrap(), @@ -862,7 +862,7 @@ fn test_string_roundtrip() { let dbig_cases = [ dbig!(0.00012345), - dbig!(-1234567890.12345678901234567890123456789) + dbig!(-1234567890.12345678901234567890123456789), ]; for case in dbig_cases { diff --git a/integer/src/convert.rs b/integer/src/convert.rs index bb907d2..ed6c2a9 100644 --- a/integer/src/convert.rs +++ b/integer/src/convert.rs @@ -483,15 +483,15 @@ impl IBig { } /// Regard the number as a [UBig] number and return a reference of [UBig] type. - /// + /// /// The conversion is only successful when the number is positive /// /// # Examples /// /// ``` /// # use dashu_int::{IBig, UBig}; - /// assert_eq!(IBig::from(123).as_ibig(), &UBig::from(123)); - /// assert_eq!(IBig::from(-123).as_ibig(), None); + /// assert_eq!(IBig::from(123).as_ubig(), Some(&UBig::from(123u8))); + /// assert_eq!(IBig::from(-123).as_ubig(), None); /// ``` #[inline] pub const fn as_ubig(&self) -> Option<&UBig> { @@ -501,8 +501,8 @@ impl IBig { // This conversion is only available for immutable references and // positive numbers, so that the sign will not be messed up. unsafe { Some(core::mem::transmute(self)) } - }, - Sign::Negative => None + } + Sign::Negative => None, } } } diff --git a/integer/src/div_ops.rs b/integer/src/div_ops.rs index c92eb55..0a9fd12 100644 --- a/integer/src/div_ops.rs +++ b/integer/src/div_ops.rs @@ -241,9 +241,50 @@ macro_rules! impl_divrem_with_primitive { }; } +macro_rules! impl_div_by_primitive { + (impl <$target:ty> for $t:ty) => { + impl Div<$t> for $target { + type Output = $target; + + #[inline] + fn div(self, rhs: $t) -> $target { + <$t>::from(self).div(rhs).try_into().unwrap() + } + } + + impl<'l> Div<$t> for &'l $target { + type Output = $target; + + #[inline] + fn div(self, rhs: $t) -> $target { + <$t>::from(*self).div(rhs).try_into().unwrap() + } + } + + impl<'r> Div<&'r $t> for $target { + type Output = $target; + + #[inline] + fn div(self, rhs: &$t) -> $target { + <$t>::from(self).div(rhs).try_into().unwrap() + } + } + + impl<'l, 'r> Div<&'r $t> for &'l $target { + type Output = $target; + + #[inline] + fn div(self, rhs: &$t) -> $target { + <$t>::from(*self).div(rhs).try_into().unwrap() + } + } + }; +} + macro_rules! impl_div_primitive_with_ubig { ($($t:ty)*) => {$( helper_macros::impl_binop_with_primitive!(impl Div<$t> for UBig, div); + impl_div_by_primitive!(impl <$t> for UBig); helper_macros::impl_binop_with_primitive!(impl Rem<$t> for UBig, rem -> $t); helper_macros::impl_binop_assign_with_primitive!(impl DivAssign<$t> for UBig, div_assign); @@ -256,6 +297,7 @@ impl_div_primitive_with_ubig!(u8 u16 u32 u64 u128 usize); macro_rules! impl_div_primitive_with_ibig { ($($t:ty)*) => {$( helper_macros::impl_binop_with_primitive!(impl Div<$t> for IBig, div); + impl_div_by_primitive!(impl <$t> for IBig); helper_macros::impl_binop_with_primitive!(impl Rem<$t> for IBig, rem -> $t); helper_macros::impl_binop_assign_with_primitive!(impl DivAssign<$t> for IBig, div_assign); diff --git a/integer/src/lib.rs b/integer/src/lib.rs index ada34e6..e4e2177 100644 --- a/integer/src/lib.rs +++ b/integer/src/lib.rs @@ -69,9 +69,9 @@ //! * `zeroize`: support traits from crate `zeroize` #![cfg_attr(not(feature = "std"), no_std)] -// TODO: apply these attributes to all crates -// TODO: #![deny(missing_docs)] -// TODO: #![deny(clippy::allow_attributes_without_reason)] +// TODO (v0.5): apply these attributes to all crates +// TODO (v0.5): #![deny(missing_docs)] +// XXX: #![deny(clippy::allow_attributes_without_reason)] #![deny(clippy::dbg_macro)] #![deny(clippy::undocumented_unsafe_blocks)] #![deny(clippy::let_underscore_must_use)] diff --git a/integer/tests/div.rs b/integer/tests/div.rs index ef1773e..4a680e3 100644 --- a/integer/tests/div.rs +++ b/integer/tests/div.rs @@ -297,6 +297,11 @@ fn test_div_rem_ubig_unsigned() { let mut x = ubig!(23); x /= &10u8; assert_eq!(x, ubig!(2)); + + assert_eq!(23u8 / ubig!(10), 2); + assert_eq!(23u8 / &ubig!(10), 2); + assert_eq!(&23u8 / ubig!(10), 2); + assert_eq!(&23u8 / &ubig!(10), 2); assert_eq!(ubig!(23) % 10u8, 3u8); assert_eq!(ubig!(23) % &10u8, 3u8); @@ -330,6 +335,11 @@ fn test_div_rem_ibig_signed() { x /= &(-10i8); assert_eq!(x, ibig!(2)); + assert_eq!((-23i8) / ibig!(-10), 2); + assert_eq!((-23i8) / &ibig!(-10), 2); + assert_eq!(&(-23i8) / ibig!(-10), 2); + assert_eq!(&(-23i8) / &ibig!(-10), 2); + assert_eq!(ibig!(-23) % (-10i8), -3); assert_eq!(ibig!(-23) % &(-10i8), -3); assert_eq!(&ibig!(-23) % (-10i8), -3); diff --git a/integer/tests/io.rs b/integer/tests/io.rs index 422cd6b..a3670e6 100644 --- a/integer/tests/io.rs +++ b/integer/tests/io.rs @@ -385,20 +385,20 @@ fn test_macros() { #[test] fn test_ubig_debug() { assert_eq!(format!("{:?}", ubig!(0)), "0"); - assert_eq!(format!("{:#?}", ubig!(0)), "0 (0 digits, 0 bits)"); + assert_eq!(format!("{:#?}", ubig!(0)), "0 (digits: 0, bits: 0)"); assert_eq!(format!("{:?}", ubig!(100)), "100"); - assert_eq!(format!("{:#?}", ubig!(100)), "100 (3 digits, 7 bits)"); + assert_eq!(format!("{:#?}", ubig!(100)), "100 (digits: 3, bits: 7)"); assert_eq!(format!("{:?}", ubig!(12345678)), "12345678"); - assert_eq!(format!("{:#?}", ubig!(12345678)), "12345678 (8 digits, 24 bits)"); + assert_eq!(format!("{:#?}", ubig!(12345678)), "12345678 (digits: 8, bits: 24)"); assert_eq!(format!("{:?}", (ubig!(1) << 31) - 1u8), "2147483647"); - assert_eq!(format!("{:#?}", (ubig!(1) << 31) - 1u8), "2147483647 (10 digits, 31 bits)"); + assert_eq!(format!("{:#?}", (ubig!(1) << 31) - 1u8), "2147483647 (digits: 10, bits: 31)"); if Word::BITS == 64 { // the number of displayed digits dependends on Word size assert_eq!(format!("{:?}", ubig!(1) << 128), "3402823669209384634..3374607431768211456"); assert_eq!( format!("{:#?}", ubig!(1) << 128), - "3402823669209384634..3374607431768211456 (39 digits, 129 bits)" + "3402823669209384634..3374607431768211456 (digits: 39, bits: 129)" ); assert_eq!( format!("{:?}", (ubig!(1) << 129) - 1u8), @@ -406,7 +406,7 @@ fn test_ubig_debug() { ); assert_eq!( format!("{:#?}", (ubig!(1) << 129) - 1u8), - "6805647338418769269..6749214863536422911 (39 digits, 129 bits)" + "6805647338418769269..6749214863536422911 (digits: 39, bits: 129)" ); assert_eq!( format!("{:?}", (ubig!(1) << 200) - 1u8), @@ -414,7 +414,7 @@ fn test_ubig_debug() { ); assert_eq!( format!("{:#?}", (ubig!(1) << 200) - 1u8), - "1606938044258990275..2993782792835301375 (61 digits, 200 bits)" + "1606938044258990275..2993782792835301375 (digits: 61, bits: 200)" ); assert_eq!( format!("{:?}", (ubig!(1) << 2000) - 1u8), @@ -422,7 +422,7 @@ fn test_ubig_debug() { ); assert_eq!( format!("{:#?}", (ubig!(1) << 2000) - 1u8), - "1148130695274254524..3762184851149029375 (603 digits, 2000 bits)" + "1148130695274254524..3762184851149029375 (digits: 603, bits: 2000)" ); } } @@ -430,18 +430,18 @@ fn test_ubig_debug() { #[test] fn test_ibig_debug() { assert_eq!(format!("{:?}", ibig!(-0)), "0"); - assert_eq!(format!("{:#?}", ibig!(-0)), "0 (0 digits, 0 bits)"); + assert_eq!(format!("{:#?}", ibig!(-0)), "0 (digits: 0, bits: 0)"); assert_eq!(format!("{:?}", ibig!(-100)), "-100"); assert_eq!(format!("{:+?}", ibig!(100)), "+100"); - assert_eq!(format!("{:#?}", ibig!(-100)), "-100 (3 digits, 7 bits)"); - assert_eq!(format!("{:+#?}", ibig!(100)), "+100 (3 digits, 7 bits)"); + assert_eq!(format!("{:#?}", ibig!(-100)), "-100 (digits: 3, bits: 7)"); + assert_eq!(format!("{:+#?}", ibig!(100)), "+100 (digits: 3, bits: 7)"); if Word::BITS == 64 { // the number of displayed digits dependends on Word size assert_eq!(format!("{:?}", ibig!(-1) << 128), "-3402823669209384634..3374607431768211456"); assert_eq!( format!("{:#?}", ibig!(-1) << 128), - "-3402823669209384634..3374607431768211456 (39 digits, 129 bits)" + "-3402823669209384634..3374607431768211456 (digits: 39, bits: 129)" ); assert_eq!( format!("{:?}", (ibig!(-1) << 200) + 1), @@ -449,7 +449,7 @@ fn test_ibig_debug() { ); assert_eq!( format!("{:#?}", (ibig!(-1) << 200) + 1), - "-1606938044258990275..2993782792835301375 (61 digits, 200 bits)" + "-1606938044258990275..2993782792835301375 (digits: 61, bits: 200)" ); } } @@ -486,10 +486,7 @@ fn test_string_roundtrip() { UBig::from_str_with_radix_prefix(format!("{:+#010X}", case).as_str()), Ok((case.clone(), 16)) ); - assert_eq!( - UBig::from_str_radix(format!("{:#}", case.in_radix(36)).as_str(), 36), - Ok(case) - ); + assert_eq!(UBig::from_str_radix(format!("{:#}", case.in_radix(36)).as_str(), 36), Ok(case)); } let ibig_cases = [ @@ -521,9 +518,6 @@ fn test_string_roundtrip() { IBig::from_str_with_radix_prefix(format!("{:+#010X}", case).as_str()), Ok((case.clone(), 16)) ); - assert_eq!( - IBig::from_str_radix(format!("{:#}", case.in_radix(36)).as_str(), 36), - Ok(case) - ); + assert_eq!(IBig::from_str_radix(format!("{:#}", case.in_radix(36)).as_str(), 36), Ok(case)); } } diff --git a/integer/tests/modular.rs b/integer/tests/modular.rs index 746e436..70e4c05 100644 --- a/integer/tests/modular.rs +++ b/integer/tests/modular.rs @@ -398,8 +398,8 @@ fn test_format() { assert_eq!( format!("{:#?}", x), r#"Reduced { - residue: 5 (1 digits, 3 bits), - modulus: 100 (3 digits, 7 bits), + residue: 5 (digits: 1, bits: 3), + modulus: 100 (digits: 3, bits: 7), }"# ); @@ -434,8 +434,8 @@ fn test_format() { assert_eq!( format!("{:#?}", x), r#"Reduced { - residue: 9999999999999999999..9999999999999999999 (39 digits, 130 bits), - modulus: 1000000000000000000..0000000000000000000 (40 digits, 130 bits), + residue: 9999999999999999999..9999999999999999999 (digits: 39, bits: 130), + modulus: 1000000000000000000..0000000000000000000 (digits: 40, bits: 130), }"# ); } diff --git a/python/src/int.rs b/python/src/int.rs index cbd9dae..a68e629 100644 --- a/python/src/int.rs +++ b/python/src/int.rs @@ -1,7 +1,7 @@ use std::collections::hash_map::DefaultHasher; use std::hash::Hasher; -use std::vec::Vec; use std::ops::*; +use std::vec::Vec; use dashu_base::{BitTest, Sign, Signed, UnsignedAbs}; use pyo3::exceptions::{PyIndexError, PyNotImplementedError, PyValueError}; @@ -17,9 +17,9 @@ use crate::{ convert_from_ibig, convert_from_ubig, parse_error_to_py, parse_signed_index, parse_to_ibig, parse_to_long, parse_to_ubig, }, - types::{IPy, PyWords, UPy, UniInput, FPy, RPy, DPy}, + types::{DPy, FPy, IPy, PyWords, RPy, UPy, UniInput}, }; -use dashu_int::{IBig, UBig, Word, fast_div}; +use dashu_int::{fast_div, IBig, UBig, Word}; use num_order::NumHash; type FBig = dashu_float::FBig; @@ -48,7 +48,9 @@ macro_rules! impl_ubig_binops { UniInput::BUint(x) => UPy((&lhs.0).$rs_method(&x.0)).into_py(py), UniInput::BInt(x) => IPy((&lhs.0).$rs_method(&x.0)).into_py(py), UniInput::OBInt(x) => IPy((&lhs.0).$rs_method(x)).into_py(py), - UniInput::Float(x) => FPy((&lhs.0).$rs_method(FBig::try_from(x).unwrap())).into_py(py), + UniInput::Float(x) => { + FPy((&lhs.0).$rs_method(FBig::try_from(x).unwrap())).into_py(py) + } UniInput::BFloat(x) => FPy((&lhs.0).$rs_method(&x.0)).into_py(py), UniInput::BDecimal(x) => DPy((&lhs.0).$rs_method(&x.0)).into_py(py), UniInput::OBDecimal(x) => DPy((&lhs.0).$rs_method(x)).into_py(py), @@ -64,7 +66,9 @@ macro_rules! impl_ubig_binops { UniInput::BUint(x) => UPy((&x.0).$rs_method(&rhs.0)).into_py(py), UniInput::BInt(x) => IPy((&x.0).$rs_method(&rhs.0)).into_py(py), UniInput::OBInt(x) => IPy(x.$rs_method(&rhs.0)).into_py(py), - UniInput::Float(x) => FPy(FBig::try_from(x).unwrap().$rs_method(&rhs.0)).into_py(py), + UniInput::Float(x) => { + FPy(FBig::try_from(x).unwrap().$rs_method(&rhs.0)).into_py(py) + } UniInput::BFloat(x) => FPy((&x.0).$rs_method(&rhs.0)).into_py(py), UniInput::BDecimal(x) => DPy((&x.0).$rs_method(&rhs.0)).into_py(py), UniInput::OBDecimal(x) => DPy(x.$rs_method(&rhs.0)).into_py(py), @@ -329,13 +333,18 @@ impl UPy { UniInput::BUint(x) => UPy((&lhs.0).rem(&x.0)).into_py(py), UniInput::BInt(x) => UPy((&lhs.0).rem(&x.0)).into_py(py), UniInput::OBInt(x) => UPy((&lhs.0).rem(x)).into_py(py), - _ => todo!() + _ => todo!(), } } upy_mod(self, other, py) } #[inline] - fn __pow__(&self, other: UniInput, modulus: Option, py: Python) -> PyResult { + fn __pow__( + &self, + other: UniInput, + modulus: Option, + py: Python, + ) -> PyResult { use fast_div::ConstDivisor; if let Some(m) = modulus { @@ -349,17 +358,19 @@ impl UPy { let (sign, m) = x.into_parts(); (sign, ConstDivisor::new(m)) } - _ => todo!() + _ => todo!(), }; match other { - UniInput::Uint(x) => Ok(UPy(ring.reduce(self.0.clone()).pow(&x.into()).residue()).into_py(py)), - _ => todo!() + UniInput::Uint(x) => { + Ok(UPy(ring.reduce(self.0.clone()).pow(&x.into()).residue()).into_py(py)) + } + _ => todo!(), } } else { match other { UniInput::Uint(x) => Ok(UPy(self.0.pow(x as _)).into_py(py)), - _ => todo!() + _ => todo!(), } } } diff --git a/python/src/types.rs b/python/src/types.rs index dfbadd6..f2bebe0 100644 --- a/python/src/types.rs +++ b/python/src/types.rs @@ -88,10 +88,10 @@ pub struct PyWords(pub std::vec::Vec); /// - Variants starting with 'OB': owned big numbers pub enum UniInput<'a> { Uint(c_ulonglong), // from int - Int(c_longlong), // from int + Int(c_longlong), // from int BUint(PyRef<'a, UPy>), BInt(PyRef<'a, IPy>), - OBInt(IBig), // from int + OBInt(IBig), // from int Float(c_double), // from float BFloat(PyRef<'a, FPy>), BDecimal(PyRef<'a, DPy>), diff --git a/rational/tests/add.rs b/rational/tests/add.rs index ca70101..d5b273e 100644 --- a/rational/tests/add.rs +++ b/rational/tests/add.rs @@ -98,18 +98,18 @@ fn test_add_with_int() { assert_eq!(rbig!(0) + ubig!(1), rbig!(1)); assert_eq!(rbig!(~0) + ubig!(1), rbig!(~1)); assert_eq!(rbig!(1) + ubig!(1), rbig!(2)); - assert_eq!(rbig!(~1) + ubig!(1), rbig!(~2)); - assert_eq!(rbig!(-1/2) + ibig!(10), rbig!(19/2)); + assert_eq!(rbig!(~1) + ubig!(1), rbig!(~2)); + assert_eq!(rbig!(-1 / 2) + ibig!(10), rbig!(19 / 2)); assert_eq!(rbig!(~-1/2) + ibig!(10), rbig!(~19/2)); - assert_eq!(rbig!(9/5) + ibig!(-3), rbig!(-6/5)); + assert_eq!(rbig!(9 / 5) + ibig!(-3), rbig!(-6 / 5)); assert_eq!(rbig!(~9/5) + ibig!(-3), rbig!(~-6/5)); - + assert_eq!(ubig!(0) + rbig!(1), rbig!(1)); assert_eq!(ubig!(0) + rbig!(~1), rbig!(~1)); assert_eq!(ubig!(1) + rbig!(1), rbig!(2)); assert_eq!(ubig!(1) + rbig!(~1), rbig!(~2)); - assert_eq!(ibig!(-10) + rbig!(1/2), rbig!(-19/2)); + assert_eq!(ibig!(-10) + rbig!(1 / 2), rbig!(-19 / 2)); assert_eq!(ibig!(-10) + rbig!(~1/2), rbig!(~-19/2)); - assert_eq!(ibig!(3) + rbig!(-9/5), rbig!(6/5)); + assert_eq!(ibig!(3) + rbig!(-9 / 5), rbig!(6 / 5)); assert_eq!(ibig!(3) + rbig!(~-9/5), rbig!(~6/5)); -} \ No newline at end of file +} diff --git a/rational/tests/div.rs b/rational/tests/div.rs index cb9886b..dec53b8 100644 --- a/rational/tests/div.rs +++ b/rational/tests/div.rs @@ -182,17 +182,17 @@ fn test_div_with_int() { assert_eq!(rbig!(~0) / ubig!(1), rbig!(~0)); assert_eq!(rbig!(1) / ubig!(1), rbig!(1)); assert_eq!(rbig!(~1) / ubig!(1), rbig!(~1)); - assert_eq!(rbig!(-1) / ibig!(10), rbig!(-1/10)); + assert_eq!(rbig!(-1) / ibig!(10), rbig!(-1 / 10)); assert_eq!(rbig!(~-1) / ibig!(10), rbig!(~-1/10)); - assert_eq!(rbig!(9/5) / ibig!(-3), rbig!(-3/5)); + assert_eq!(rbig!(9 / 5) / ibig!(-3), rbig!(-3 / 5)); assert_eq!(rbig!(~9/5) / ibig!(-3), rbig!(~-3/5)); - + assert_eq!(ubig!(0) / rbig!(1), rbig!(0)); assert_eq!(ubig!(0) / rbig!(~1), rbig!(~0)); assert_eq!(ubig!(1) / rbig!(1), rbig!(1)); assert_eq!(ubig!(1) / rbig!(~1), rbig!(~1)); - assert_eq!(ibig!(-1) / rbig!(10), rbig!(-1/10)); + assert_eq!(ibig!(-1) / rbig!(10), rbig!(-1 / 10)); assert_eq!(ibig!(-1) / rbig!(~10), rbig!(~-1/10)); - assert_eq!(ibig!(-3) / rbig!(9/5), rbig!(-5/3)); + assert_eq!(ibig!(-3) / rbig!(9 / 5), rbig!(-5 / 3)); assert_eq!(ibig!(-3) / rbig!(~9/5), rbig!(~-5/3)); } diff --git a/rational/tests/mul.rs b/rational/tests/mul.rs index 72bd09e..4775dea 100644 --- a/rational/tests/mul.rs +++ b/rational/tests/mul.rs @@ -91,18 +91,18 @@ fn test_mul_with_int() { assert_eq!(rbig!(0) * ubig!(1), rbig!(0)); assert_eq!(rbig!(~0) * ubig!(1), rbig!(~0)); assert_eq!(rbig!(1) * ubig!(1), rbig!(1)); - assert_eq!(rbig!(~1) * ubig!(1), rbig!(~1)); - assert_eq!(rbig!(-1/2) * ibig!(-2), rbig!(1)); + assert_eq!(rbig!(~1) * ubig!(1), rbig!(~1)); + assert_eq!(rbig!(-1 / 2) * ibig!(-2), rbig!(1)); assert_eq!(rbig!(~-1/2) * ibig!(-2), rbig!(~1)); - assert_eq!(rbig!(5/12) * ibig!(-3), rbig!(-5/4)); + assert_eq!(rbig!(5 / 12) * ibig!(-3), rbig!(-5 / 4)); assert_eq!(rbig!(~5/12) * ibig!(-3), rbig!(~-5/4)); - + assert_eq!(ubig!(0) * rbig!(1), rbig!(0)); assert_eq!(ubig!(0) * rbig!(~1), rbig!(~0)); assert_eq!(ubig!(1) * rbig!(1), rbig!(1)); assert_eq!(ubig!(1) * rbig!(~1), rbig!(~1)); - assert_eq!(ibig!(-2) * rbig!(-1/2), rbig!(1)); + assert_eq!(ibig!(-2) * rbig!(-1 / 2), rbig!(1)); assert_eq!(ibig!(-2) * rbig!(~-1/2), rbig!(~1)); - assert_eq!(ibig!(-3) * rbig!(5/12), rbig!(-5/4)); + assert_eq!(ibig!(-3) * rbig!(5 / 12), rbig!(-5 / 4)); assert_eq!(ibig!(-3) * rbig!(~5/12), rbig!(~-5/4)); } From d49dd0da58000d7df5008ba2877c82da5dc74c20 Mon Sep 17 00:00:00 2001 From: Jacob Date: Sun, 18 Feb 2024 21:19:32 +0800 Subject: [PATCH 13/15] Implement more bit ops and simplify macros --- integer/CHANGELOG.md | 1 + integer/src/add_ops.rs | 41 ++------------------ integer/src/bits.rs | 73 +++++++++++++++++++++++++++++++++++- integer/src/div_ops.rs | 43 +++++---------------- integer/src/gcd_ops.rs | 32 ++-------------- integer/src/helper_macros.rs | 71 ++++++++++++++++++----------------- integer/src/mul_ops.rs | 15 +------- integer/tests/div.rs | 2 +- python/dashu.pyi | 13 ++++++- python/src/int.rs | 44 ++++++++++++---------- 10 files changed, 166 insertions(+), 169 deletions(-) diff --git a/integer/CHANGELOG.md b/integer/CHANGELOG.md index 9e403de..fdeef63 100644 --- a/integer/CHANGELOG.md +++ b/integer/CHANGELOG.md @@ -7,6 +7,7 @@ - Implement `TryFrom` and `TryFrom` for `f32`/`f64`. - Implement `IBig::{from_le_bytes, from_be_bytes}` and `IBig::{to_le_bytes, to_be_bytes}`. - The alterative `Debug` output of `UBig` and `IBig` will include `(digits: x, bits: y)` instead of `(x digits, y bits)`. +- Implement bit operations (`BitAnd`, `BitOr`, `BitXor`) between `UBig` and `IBig`, and between `IBig` and unsigned primitive integers. ## 0.4.1 diff --git a/integer/src/add_ops.rs b/integer/src/add_ops.rs index cb4dac7..8a9fa8b 100644 --- a/integer/src/add_ops.rs +++ b/integer/src/add_ops.rs @@ -33,43 +33,10 @@ helper_macros::forward_ibig_binop_to_repr!(impl Sub, sub, Output = IBig, impl_ib helper_macros::impl_binop_assign_by_taking!(impl AddAssign for IBig, add_assign, add); helper_macros::impl_binop_assign_by_taking!(impl SubAssign for IBig, sub_assign, sub); -macro_rules! impl_ubig_ibig_add { - ($mag0:ident, $sign1:ident, $mag1:ident) => { - match ($sign1) { - Positive => IBig($mag0.add($mag1)), - Negative => IBig($mag0.sub_signed($mag1)), - } - }; -} -macro_rules! impl_ubig_ibig_sub { - ($mag0:ident, $sign1:ident, $mag1:ident) => { - match ($sign1) { - Positive => IBig($mag0.sub_signed($mag1)), - Negative => IBig($mag0.add($mag1)), - } - }; -} -helper_macros::forward_ubig_ibig_binop_to_repr!(impl Add, add, Output = IBig, impl_ubig_ibig_add); -helper_macros::forward_ubig_ibig_binop_to_repr!(impl Sub, sub, Output = IBig, impl_ubig_ibig_sub); - -macro_rules! impl_ibig_ubig_add { - ($sign0:ident, $mag0:ident, $mag1:ident) => { - match ($sign0) { - Positive => IBig($mag0.add($mag1)), - Negative => IBig($mag1.sub_signed($mag0)), - } - }; -} -macro_rules! impl_ibig_ubig_sub { - ($sign0:ident, $mag0:ident, $mag1:ident) => { - match ($sign0) { - Positive => IBig($mag0.sub_signed($mag1)), - Negative => IBig($mag0.add($mag1).with_sign(Negative)), - } - }; -} -helper_macros::forward_ibig_ubig_binop_to_repr!(impl Add, add, Output = IBig, impl_ibig_ubig_add); -helper_macros::forward_ibig_ubig_binop_to_repr!(impl Sub, sub, Output = IBig, impl_ibig_ubig_sub); +helper_macros::forward_ubig_ibig_binop_to_repr!(impl Add, add, Output = IBig, impl_ibig_add); +helper_macros::forward_ubig_ibig_binop_to_repr!(impl Sub, sub, Output = IBig, impl_ibig_sub); +helper_macros::forward_ibig_ubig_binop_to_repr!(impl Add, add, Output = IBig, impl_ibig_add); +helper_macros::forward_ibig_ubig_binop_to_repr!(impl Sub, sub, Output = IBig, impl_ibig_sub); helper_macros::impl_binop_assign_by_taking!(impl AddAssign for IBig, add_assign, add); helper_macros::impl_binop_assign_by_taking!(impl SubAssign for IBig, sub_assign, sub); diff --git a/integer/src/bits.rs b/integer/src/bits.rs index bb417fb..fcec22c 100644 --- a/integer/src/bits.rs +++ b/integer/src/bits.rs @@ -9,6 +9,8 @@ use core::{ ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not}, }; +// Ops for UBig + impl UBig { /// Set the `n`-th bit, n starts from 0. /// @@ -207,6 +209,8 @@ impl PowerOfTwo for UBig { } } +// Ops for IBig + impl IBig { /// Returns the number of trailing zeros in the two's complement binary representation. /// @@ -1052,6 +1056,59 @@ helper_macros::impl_binop_assign_by_taking!(impl BitAndAssign for IBig, bi helper_macros::impl_binop_assign_by_taking!(impl BitOrAssign for IBig, bitor_assign, bitor); helper_macros::impl_binop_assign_by_taking!(impl BitXorAssign for IBig, bitxor_assign, bitxor); +// Ops between UBig & IBig + +macro_rules! impl_ubig_ibig_bitand { + ($sign0:ident, $mag0:ident, $sign1:ident, $mag1:ident) => {{ + debug_assert_eq!($sign0, Positive); + match $sign1 { + Positive => UBig($mag0.bitand($mag1)), + Negative => UBig($mag0.and_not($mag1.sub_one().into_typed())), + } + }}; +} +macro_rules! impl_ibig_ubig_bitand { + ($sign0:ident, $mag0:ident, $sign1:ident, $mag1:ident) => {{ + debug_assert_eq!($sign1, Positive); + match $sign0 { + Positive => UBig($mag1.bitand($mag0)), + Negative => UBig($mag1.and_not($mag0.sub_one().into_typed())), + } + }}; +} +helper_macros::forward_ubig_ibig_binop_to_repr!( + impl BitAnd, + bitand, + Output = UBig, + impl_ubig_ibig_bitand +); +helper_macros::forward_ubig_ibig_binop_to_repr!(impl BitOr, bitor, Output = IBig, impl_ibig_bitor); +helper_macros::forward_ubig_ibig_binop_to_repr!( + impl BitXor, + bitxor, + Output = IBig, + impl_ibig_bitxor +); +helper_macros::impl_binop_assign_by_taking!(impl BitAndAssign for UBig, bitand_assign, bitand); +helper_macros::forward_ibig_ubig_binop_to_repr!( + impl BitAnd, + bitand, + Output = UBig, + impl_ibig_ubig_bitand +); +helper_macros::forward_ibig_ubig_binop_to_repr!(impl BitOr, bitor, Output = IBig, impl_ibig_bitor); +helper_macros::forward_ibig_ubig_binop_to_repr!( + impl BitXor, + bitxor, + Output = IBig, + impl_ibig_bitxor +); +helper_macros::impl_binop_assign_by_taking!(impl BitAndAssign for IBig, bitand_assign, bitand); +helper_macros::impl_binop_assign_by_taking!(impl BitOrAssign for IBig, bitor_assign, bitor); +helper_macros::impl_binop_assign_by_taking!(impl BitXorAssign for IBig, bitxor_assign, bitxor); + +// Ops with primitives + macro_rules! impl_bit_ops_primitive_with_ubig { ($($t:ty)*) => {$( helper_macros::impl_commutative_binop_with_primitive!(impl BitAnd<$t> for UBig, bitand -> $t); @@ -1064,7 +1121,19 @@ macro_rules! impl_bit_ops_primitive_with_ubig { } impl_bit_ops_primitive_with_ubig!(u8 u16 u32 u64 u128 usize); -macro_rules! impl_bit_ops_primitive_with_ibig { +macro_rules! impl_bit_ops_unsigned_with_ibig { + ($($t:ty)*) => {$( + helper_macros::impl_commutative_binop_with_primitive!(impl BitAnd<$t> for IBig, bitand -> $t); + helper_macros::impl_commutative_binop_with_primitive!(impl BitOr<$t> for IBig, bitor); + helper_macros::impl_commutative_binop_with_primitive!(impl BitXor<$t> for IBig, bitxor); + helper_macros::impl_binop_assign_with_primitive!(impl BitAndAssign<$t> for IBig, bitand_assign); + helper_macros::impl_binop_assign_with_primitive!(impl BitOrAssign<$t> for IBig, bitor_assign); + helper_macros::impl_binop_assign_with_primitive!(impl BitXorAssign<$t> for IBig, bitxor_assign); + )*}; +} +impl_bit_ops_unsigned_with_ibig!(u8 u16 u32 u64 u128 usize); + +macro_rules! impl_bit_ops_signed_with_ibig { ($($t:ty)*) => {$( helper_macros::impl_commutative_binop_with_primitive!(impl BitAnd<$t> for IBig, bitand); helper_macros::impl_commutative_binop_with_primitive!(impl BitOr<$t> for IBig, bitor); @@ -1074,7 +1143,7 @@ macro_rules! impl_bit_ops_primitive_with_ibig { helper_macros::impl_binop_assign_with_primitive!(impl BitXorAssign<$t> for IBig, bitxor_assign); )*}; } -impl_bit_ops_primitive_with_ibig!(i8 i16 i32 i64 i128 isize); +impl_bit_ops_signed_with_ibig!(i8 i16 i32 i64 i128 isize); #[cfg(test)] mod tests { diff --git a/integer/src/div_ops.rs b/integer/src/div_ops.rs index 0a9fd12..0e627d0 100644 --- a/integer/src/div_ops.rs +++ b/integer/src/div_ops.rs @@ -61,7 +61,7 @@ helper_macros::forward_ibig_binop_to_repr!(impl Rem, rem, Output = IBig, impl_ib helper_macros::impl_binop_assign_by_taking!(impl DivAssign for IBig, div_assign, div); helper_macros::impl_binop_assign_by_taking!(impl RemAssign for IBig, rem_assign, rem); -macro_rules! impl_ibig_div_rem { +macro_rules! impl_ibig_divrem { ($sign0:ident, $mag0:ident, $sign1:ident, $mag1:ident) => {{ // truncate towards 0. let (q, r) = $mag0.div_rem($mag1); @@ -71,7 +71,7 @@ macro_rules! impl_ibig_div_rem { helper_macros::forward_ibig_binop_to_repr!( impl DivRem, div_rem -> (IBig, IBig), OutputDiv = IBig, OutputRem = IBig, - impl_ibig_div_rem + impl_ibig_divrem ); macro_rules! impl_ibig_div_euclid { @@ -144,50 +144,27 @@ helper_macros::impl_binop_assign_by_taking!( // Ops between UBig & IBig -macro_rules! impl_ubig_ibig_div { - ($mag0:ident, $sign1:ident, $mag1:ident) => { - IBig(($mag0 / $mag1).with_sign($sign1)) - }; -} macro_rules! impl_ubig_ibig_rem { - ($mag0:ident, $sign1:ident, $mag1:ident) => {{ + ($sign0:ident, $mag0:ident, $sign1:ident, $mag1:ident) => {{ + debug_assert_eq!($sign0, Positive); let _unused = $sign1; UBig($mag0 % $mag1) }}; } macro_rules! impl_ubig_ibig_divrem { - ($mag0:ident, $sign1:ident, $mag1:ident) => {{ + ($sign0:ident, $mag0:ident, $sign1:ident, $mag1:ident) => {{ + debug_assert_eq!($sign0, Positive); let (q, r) = $mag0.div_rem($mag1); (IBig(q.with_sign($sign1)), UBig(r)) }}; } -helper_macros::forward_ubig_ibig_binop_to_repr!(impl Div, div, Output = IBig, impl_ubig_ibig_div); +helper_macros::forward_ubig_ibig_binop_to_repr!(impl Div, div, Output = IBig, impl_ibig_div); helper_macros::forward_ubig_ibig_binop_to_repr!(impl Rem, rem, Output = UBig, impl_ubig_ibig_rem); helper_macros::forward_ubig_ibig_binop_to_repr!(impl DivRem, div_rem -> (IBig, UBig), OutputDiv = IBig, OutputRem = UBig, impl_ubig_ibig_divrem); helper_macros::impl_binop_assign_by_taking!(impl RemAssign for UBig, rem_assign, rem); - -macro_rules! impl_ibig_ubig_div { - ($sign0:ident, $mag0:ident, $mag1:ident) => { - // truncate towards 0. - IBig(($mag0 / $mag1).with_sign($sign0)) - }; -} -macro_rules! impl_ibig_ubig_rem { - ($sign0:ident, $mag0:ident, $mag1:ident) => {{ - // remainder with truncating division has same sign as lhs. - IBig(($mag0 % $mag1).with_sign($sign0)) - }}; -} -macro_rules! impl_ibig_ubig_divrem { - ($sign0:ident, $mag0:ident, $mag1:ident) => {{ - // remainder with truncating division has same sign as lhs. - let (q, r) = $mag0.div_rem($mag1); - (IBig(q.with_sign($sign0)), IBig(r.with_sign($sign0))) - }}; -} -helper_macros::forward_ibig_ubig_binop_to_repr!(impl Div, div, Output = IBig, impl_ibig_ubig_div); -helper_macros::forward_ibig_ubig_binop_to_repr!(impl Rem, rem, Output = IBig, impl_ibig_ubig_rem); -helper_macros::forward_ibig_ubig_binop_to_repr!(impl DivRem, div_rem -> (IBig, IBig), OutputDiv = IBig, OutputRem = IBig, impl_ibig_ubig_divrem); +helper_macros::forward_ibig_ubig_binop_to_repr!(impl Div, div, Output = IBig, impl_ibig_div); +helper_macros::forward_ibig_ubig_binop_to_repr!(impl Rem, rem, Output = IBig, impl_ibig_rem); +helper_macros::forward_ibig_ubig_binop_to_repr!(impl DivRem, div_rem -> (IBig, IBig), OutputDiv = IBig, OutputRem = IBig, impl_ibig_divrem); helper_macros::impl_binop_assign_by_taking!(impl DivAssign for IBig, div_assign, div); helper_macros::impl_binop_assign_by_taking!(impl RemAssign for IBig, rem_assign, rem); diff --git a/integer/src/gcd_ops.rs b/integer/src/gcd_ops.rs index 00c2145..94ce265 100644 --- a/integer/src/gcd_ops.rs +++ b/integer/src/gcd_ops.rs @@ -43,34 +43,10 @@ forward_ibig_binop_to_repr!( impl_ibig_gcd_ext ); -macro_rules! impl_ubig_ibig_gcd { - ($mag0:ident, $sign1:ident, $mag1:ident) => {{ - let _unused = $sign1; - UBig($mag0.gcd($mag1)) - }}; -} -macro_rules! impl_ibig_ubig_gcd { - ($sign0:ident, $mag0:ident, $mag1:ident) => {{ - let _unused = $sign0; - UBig($mag0.gcd($mag1)) - }}; -} -macro_rules! impl_ubig_ibig_gcd_ext { - ($mag0:ident, $sign1:ident, $mag1:ident) => {{ - let (r, s, t) = $mag0.gcd_ext($mag1); - (UBig(r), IBig(s), $sign1 * IBig(t)) - }}; -} -macro_rules! impl_ibig_ubig_gcd_ext { - ($sign0:ident, $mag0:ident, $mag1:ident) => {{ - let (r, s, t) = $mag0.gcd_ext($mag1); - (UBig(r), $sign0 * IBig(s), IBig(t)) - }}; -} -forward_ubig_ibig_binop_to_repr!(impl Gcd, gcd, Output = UBig, impl_ubig_ibig_gcd); -forward_ibig_ubig_binop_to_repr!(impl Gcd, gcd, Output = UBig, impl_ibig_ubig_gcd); -forward_ubig_ibig_binop_to_repr!(impl ExtendedGcd, gcd_ext -> (UBig, IBig, IBig), OutputGcd = UBig, OutputCoeff = IBig, impl_ubig_ibig_gcd_ext); -forward_ibig_ubig_binop_to_repr!(impl ExtendedGcd, gcd_ext -> (UBig, IBig, IBig), OutputGcd = UBig, OutputCoeff = IBig, impl_ibig_ubig_gcd_ext); +forward_ubig_ibig_binop_to_repr!(impl Gcd, gcd, Output = UBig, impl_ibig_gcd); +forward_ibig_ubig_binop_to_repr!(impl Gcd, gcd, Output = UBig, impl_ibig_gcd); +forward_ubig_ibig_binop_to_repr!(impl ExtendedGcd, gcd_ext -> (UBig, IBig, IBig), OutputGcd = UBig, OutputCoeff = IBig, impl_ibig_gcd_ext); +forward_ibig_ubig_binop_to_repr!(impl ExtendedGcd, gcd_ext -> (UBig, IBig, IBig), OutputGcd = UBig, OutputCoeff = IBig, impl_ibig_gcd_ext); mod repr { use super::*; diff --git a/integer/src/helper_macros.rs b/integer/src/helper_macros.rs index f4ce8d6..07e2654 100644 --- a/integer/src/helper_macros.rs +++ b/integer/src/helper_macros.rs @@ -220,6 +220,8 @@ macro_rules! forward_ubig_binop_to_repr { }; } +// TODO(next): modify this macro so that the $impl function can be shared between ibig op ibig and ubig op ibig + /// Implement `impl Op for IBig` by forwarding to the function-like macro `$impl` with arguments /// `(lhs_sign, lhs_repr, rhs_sign, rhs_repr)`, including &IBig. /// The output type is IBig. @@ -323,16 +325,17 @@ macro_rules! forward_ibig_binop_to_repr { /// Implement `impl OpAssign for A` by forwarding to `*A = mem::take(A).op(B)`, including &B. macro_rules! impl_binop_assign_by_taking { (impl $trait:ident<$t2:ty> for $t1:ty, $methodassign:ident, $method:ident) => { + // the .into() conversion at the end is to convert UBig to IBig in bitand_assign impl $trait<$t2> for $t1 { #[inline] fn $methodassign(&mut self, rhs: $t2) { - *self = core::mem::take(self).$method(rhs); + *self = core::mem::take(self).$method(rhs).into(); } } impl $trait<&$t2> for $t1 { #[inline] fn $methodassign(&mut self, rhs: &$t2) { - *self = core::mem::take(self).$method(rhs); + *self = core::mem::take(self).$method(rhs).into(); } } }; @@ -368,9 +371,9 @@ macro_rules! forward_ubig_ibig_binop_to_repr { #[inline] fn $method(self, rhs: IBig) -> $ty_output { - let lhs_mag = self.into_repr(); + let (lhs_sign, lhs_mag) = (dashu_base::Sign::Positive, self.into_repr()); let (rhs_sign, rhs_mag) = rhs.into_sign_repr(); - $impl!(lhs_mag, rhs_sign, rhs_mag) + $impl!(lhs_sign, lhs_mag, rhs_sign, rhs_mag) } } @@ -379,9 +382,9 @@ macro_rules! forward_ubig_ibig_binop_to_repr { #[inline] fn $method(self, rhs: &IBig) -> $ty_output { - let lhs_mag = self.into_repr(); + let (lhs_sign, lhs_mag) = (dashu_base::Sign::Positive, self.into_repr()); let (rhs_sign, rhs_mag) = rhs.as_sign_repr(); - $impl!(lhs_mag, rhs_sign, rhs_mag) + $impl!(lhs_sign, lhs_mag, rhs_sign, rhs_mag) } } @@ -390,9 +393,9 @@ macro_rules! forward_ubig_ibig_binop_to_repr { #[inline] fn $method(self, rhs: IBig) -> $ty_output { - let lhs_mag = self.repr(); + let (lhs_sign, lhs_mag) = (dashu_base::Sign::Positive, self.repr()); let (rhs_sign, rhs_mag) = rhs.into_sign_repr(); - $impl!(lhs_mag, rhs_sign, rhs_mag) + $impl!(lhs_sign, lhs_mag, rhs_sign, rhs_mag) } } @@ -401,9 +404,9 @@ macro_rules! forward_ubig_ibig_binop_to_repr { #[inline] fn $method(self, rhs: &IBig) -> $ty_output { - let lhs_mag = self.repr(); + let (lhs_sign, lhs_mag) = (dashu_base::Sign::Positive, self.repr()); let (rhs_sign, rhs_mag) = rhs.as_sign_repr(); - $impl!(lhs_mag, rhs_sign, rhs_mag) + $impl!(lhs_sign, lhs_mag, rhs_sign, rhs_mag) } } }; @@ -414,9 +417,9 @@ macro_rules! forward_ubig_ibig_binop_to_repr { #[inline] fn $method(self, rhs: IBig) -> $omethod { - let lhs_mag = self.into_repr(); + let (lhs_sign, lhs_mag) = (dashu_base::Sign::Positive, self.into_repr()); let (rhs_sign, rhs_mag) = rhs.into_sign_repr(); - $impl!(lhs_mag, rhs_sign, rhs_mag) + $impl!(lhs_sign, lhs_mag, rhs_sign, rhs_mag) } } @@ -426,9 +429,9 @@ macro_rules! forward_ubig_ibig_binop_to_repr { #[inline] fn $method(self, rhs: &IBig) -> $omethod { - let lhs_mag = self.into_repr(); + let (lhs_sign, lhs_mag) = (dashu_base::Sign::Positive, self.into_repr()); let (rhs_sign, rhs_mag) = rhs.as_sign_repr(); - $impl!(lhs_mag, rhs_sign, rhs_mag) + $impl!(lhs_sign, lhs_mag, rhs_sign, rhs_mag) } } @@ -438,9 +441,9 @@ macro_rules! forward_ubig_ibig_binop_to_repr { #[inline] fn $method(self, rhs: IBig) -> $omethod { - let lhs_mag = self.repr(); + let (lhs_sign, lhs_mag) = (dashu_base::Sign::Positive, self.repr()); let (rhs_sign, rhs_mag) = rhs.into_sign_repr(); - $impl!(lhs_mag, rhs_sign, rhs_mag) + $impl!(lhs_sign, lhs_mag, rhs_sign, rhs_mag) } } @@ -450,9 +453,9 @@ macro_rules! forward_ubig_ibig_binop_to_repr { #[inline] fn $method(self, rhs: &IBig) -> $omethod { - let lhs_mag = self.repr(); + let (lhs_sign, lhs_mag) = (dashu_base::Sign::Positive, self.repr()); let (rhs_sign, rhs_mag) = rhs.as_sign_repr(); - $impl!(lhs_mag, rhs_sign, rhs_mag) + $impl!(lhs_sign, lhs_mag, rhs_sign, rhs_mag) } } }; @@ -468,8 +471,8 @@ macro_rules! forward_ibig_ubig_binop_to_repr { #[inline] fn $method(self, rhs: UBig) -> $ty_output { let (lhs_sign, lhs_mag) = self.into_sign_repr(); - let rhs_mag = rhs.into_repr(); - $impl!(lhs_sign, lhs_mag, rhs_mag) + let (rhs_sign, rhs_mag) = (dashu_base::Sign::Positive, rhs.into_repr()); + $impl!(lhs_sign, lhs_mag, rhs_sign, rhs_mag) } } @@ -479,8 +482,8 @@ macro_rules! forward_ibig_ubig_binop_to_repr { #[inline] fn $method(self, rhs: &UBig) -> $ty_output { let (lhs_sign, lhs_mag) = self.into_sign_repr(); - let rhs_mag = rhs.repr(); - $impl!(lhs_sign, lhs_mag, rhs_mag) + let (rhs_sign, rhs_mag) = (dashu_base::Sign::Positive, rhs.repr()); + $impl!(lhs_sign, lhs_mag, rhs_sign, rhs_mag) } } @@ -490,8 +493,8 @@ macro_rules! forward_ibig_ubig_binop_to_repr { #[inline] fn $method(self, rhs: UBig) -> $ty_output { let (lhs_sign, lhs_mag) = self.as_sign_repr(); - let rhs_mag = rhs.into_repr(); - $impl!(lhs_sign, lhs_mag, rhs_mag) + let (rhs_sign, rhs_mag) = (dashu_base::Sign::Positive, rhs.into_repr()); + $impl!(lhs_sign, lhs_mag, rhs_sign, rhs_mag) } } @@ -501,8 +504,8 @@ macro_rules! forward_ibig_ubig_binop_to_repr { #[inline] fn $method(self, rhs: &UBig) -> $ty_output { let (lhs_sign, lhs_mag) = self.as_sign_repr(); - let rhs_mag = rhs.repr(); - $impl!(lhs_sign, lhs_mag, rhs_mag) + let (rhs_sign, rhs_mag) = (dashu_base::Sign::Positive, rhs.repr()); + $impl!(lhs_sign, lhs_mag, rhs_sign, rhs_mag) } } }; @@ -514,8 +517,8 @@ macro_rules! forward_ibig_ubig_binop_to_repr { #[inline] fn $method(self, rhs: UBig) -> $omethod { let (lhs_sign, lhs_mag) = self.into_sign_repr(); - let rhs_mag = rhs.into_repr(); - $impl!(lhs_sign, lhs_mag, rhs_mag) + let (rhs_sign, rhs_mag) = (dashu_base::Sign::Positive, rhs.into_repr()); + $impl!(lhs_sign, lhs_mag, rhs_sign, rhs_mag) } } @@ -526,8 +529,8 @@ macro_rules! forward_ibig_ubig_binop_to_repr { #[inline] fn $method(self, rhs: &UBig) -> $omethod { let (lhs_sign, lhs_mag) = self.into_sign_repr(); - let rhs_mag = rhs.repr(); - $impl!(lhs_sign, lhs_mag, rhs_mag) + let (rhs_sign, rhs_mag) = (dashu_base::Sign::Positive, rhs.repr()); + $impl!(lhs_sign, lhs_mag, rhs_sign, rhs_mag) } } @@ -538,8 +541,8 @@ macro_rules! forward_ibig_ubig_binop_to_repr { #[inline] fn $method(self, rhs: UBig) -> $omethod { let (lhs_sign, lhs_mag) = self.as_sign_repr(); - let rhs_mag = rhs.into_repr(); - $impl!(lhs_sign, lhs_mag, rhs_mag) + let (rhs_sign, rhs_mag) = (dashu_base::Sign::Positive, rhs.into_repr()); + $impl!(lhs_sign, lhs_mag, rhs_sign, rhs_mag) } } @@ -550,8 +553,8 @@ macro_rules! forward_ibig_ubig_binop_to_repr { #[inline] fn $method(self, rhs: &UBig) -> $omethod { let (lhs_sign, lhs_mag) = self.as_sign_repr(); - let rhs_mag = rhs.repr(); - $impl!(lhs_sign, lhs_mag, rhs_mag) + let (rhs_sign, rhs_mag) = (dashu_base::Sign::Positive, rhs.repr()); + $impl!(lhs_sign, lhs_mag, rhs_sign, rhs_mag) } } }; diff --git a/integer/src/mul_ops.rs b/integer/src/mul_ops.rs index bcbd60c..397c326 100644 --- a/integer/src/mul_ops.rs +++ b/integer/src/mul_ops.rs @@ -14,19 +14,8 @@ macro_rules! impl_ibig_mul { helper_macros::forward_ibig_binop_to_repr!(impl Mul, mul, Output = IBig, impl_ibig_mul); helper_macros::impl_binop_assign_by_taking!(impl MulAssign for IBig, mul_assign, mul); -macro_rules! impl_ubig_ibig_mul { - ($mag0:ident, $sign1:ident, $mag1:ident) => { - IBig($mag0.mul($mag1).with_sign($sign1)) - }; -} -helper_macros::forward_ubig_ibig_binop_to_repr!(impl Mul, mul, Output = IBig, impl_ubig_ibig_mul); - -macro_rules! impl_ibig_ubig_mul { - ($sign0:ident, $mag0:ident, $mag1:ident) => { - IBig($mag0.mul($mag1).with_sign($sign0)) - }; -} -helper_macros::forward_ibig_ubig_binop_to_repr!(impl Mul, mul, Output = IBig, impl_ibig_ubig_mul); +helper_macros::forward_ubig_ibig_binop_to_repr!(impl Mul, mul, Output = IBig, impl_ibig_mul); +helper_macros::forward_ibig_ubig_binop_to_repr!(impl Mul, mul, Output = IBig, impl_ibig_mul); helper_macros::impl_binop_assign_by_taking!(impl MulAssign for IBig, mul_assign, mul); // Ops with primitives diff --git a/integer/tests/div.rs b/integer/tests/div.rs index 4a680e3..b7c7b59 100644 --- a/integer/tests/div.rs +++ b/integer/tests/div.rs @@ -297,7 +297,7 @@ fn test_div_rem_ubig_unsigned() { let mut x = ubig!(23); x /= &10u8; assert_eq!(x, ubig!(2)); - + assert_eq!(23u8 / ubig!(10), 2); assert_eq!(23u8 / &ubig!(10), 2); assert_eq!(&23u8 / ubig!(10), 2); diff --git a/python/dashu.pyi b/python/dashu.pyi index 5f33982..62c9cd0 100644 --- a/python/dashu.pyi +++ b/python/dashu.pyi @@ -1,4 +1,4 @@ -from typing import Union +from typing import Union, overload class Words: pass @@ -6,4 +6,13 @@ class Words: class UBig: def __new__(obj: Union[int, str]): ... def to_words(self) -> Words: ... - def from_words(self, words: Words): ... + @staticmethod + def from_words(self, words: Words) -> UBig: ... + + @overload + def __add__(self, other: UBig) -> UBig: ... + @overload + def __add__(self, other: Union[int, IBig]) -> IBig: ... + +class IBig: + def __new__(obj: Union[int, str]): ... diff --git a/python/src/int.rs b/python/src/int.rs index a68e629..73db940 100644 --- a/python/src/int.rs +++ b/python/src/int.rs @@ -3,14 +3,11 @@ use std::hash::Hasher; use std::ops::*; use std::vec::Vec; -use dashu_base::{BitTest, Sign, Signed, UnsignedAbs}; -use pyo3::exceptions::{PyIndexError, PyNotImplementedError, PyValueError}; -use pyo3::prelude::*; -use pyo3::types::PySlice; -use pyo3::{ - exceptions::{PyOverflowError, PyTypeError}, - types::{PyBytes, PyLong}, +use pyo3::exceptions::{ + PyIndexError, PyNotImplementedError, PyOverflowError, PyTypeError, PyValueError, }; +use pyo3::prelude::*; +use pyo3::types::{PyBytes, PyLong, PySlice}; use crate::{ convert::{ @@ -19,6 +16,8 @@ use crate::{ }, types::{DPy, FPy, IPy, PyWords, RPy, UPy, UniInput}, }; + +use dashu_base::{BitTest, Sign, Signed, UnsignedAbs}; use dashu_int::{fast_div, IBig, UBig, Word}; use num_order::NumHash; type FBig = dashu_float::FBig; @@ -375,32 +374,39 @@ impl UPy { } } #[inline] - fn __pos__(&self, py: Python) -> PyObject { + fn __pos__(&self) -> UPy { todo!() } #[inline] - fn __neg__(&self, py: Python) -> PyObject { - todo!() + fn __neg__(&self) -> IPy { + IPy((&self.0).neg()) } #[inline] - fn __abs__(&self, py: Python) -> PyObject { + fn __abs__(&self) -> UPy { todo!() } #[inline] - fn __nonzero__(&self, py: Python) -> PyObject { - todo!() + fn __nonzero__(&self) -> bool { + !self.0.is_zero() } #[inline] - fn __lshift__(&self, other: usize, py: Python) -> PyObject { - todo!() + fn __lshift__(&self, other: usize) -> UPy { + UPy((&self.0) << other) } #[inline] - fn __rshift__(&self, other: usize, py: Python) -> PyObject { - todo!() + fn __rshift__(&self, other: usize) -> UPy { + UPy((&self.0) >> other) } #[inline] - fn __and__(&self, other: &PyAny, py: Python) -> PyObject { - todo!() + fn __and__(&self, other: UniInput, py: Python) -> PyObject { + match other { + UniInput::Uint(x) => (&self.0).bitand(x).into_py(py), + UniInput::BUint(x) => UPy((&self.0).bitand(&x.0)).into_py(py), + UniInput::Int(x) => UPy((&self.0).bitand(IBig::from(x))).into_py(py), + UniInput::BInt(x) => UPy((&self.0).bitand(&x.0)).into_py(py), + UniInput::OBInt(x) => UPy((&self.0).bitand(x)).into_py(py), + _ => todo!(), // TODO: raise TypeError + } } #[inline] fn __or__(&self, other: &PyAny, py: Python) -> PyObject { From 49d76c41ddb850836190cfdc81f816ab4c3e37ed Mon Sep 17 00:00:00 2001 From: Jacob Date: Sat, 2 Mar 2024 22:40:30 +0800 Subject: [PATCH 14/15] More ops for IPy --- python/dashu.pyi | 97 +++++++++++++- python/pyproject.toml | 2 +- python/src/int.rs | 300 +++++++++++++++++++++++++++++++----------- python/src/types.rs | 2 +- python/src/utils.rs | 1 + 5 files changed, 319 insertions(+), 83 deletions(-) diff --git a/python/dashu.pyi b/python/dashu.pyi index 62c9cd0..71d5388 100644 --- a/python/dashu.pyi +++ b/python/dashu.pyi @@ -1,18 +1,101 @@ -from typing import Union, overload +from typing import overload, Literal, TypeVar + +Bigs = TypeVar('Bigs', UBig, IBig, FBig, DBig, RBig) +SignedBigs = TypeVar('SignedBigs', IBig, FBig, DBig, RBig) class Words: - pass + def __init__(self, obj: list[int] | Words): ... + def __repr__(self) -> str: ... + def __len__(self) -> int: ... + @overload + def __getitem__(self, index: int) -> int: ... + @overload + def __getitem__(self, index: slice) -> Words: ... + @overload + def __setitem__(self, index: int, value: int): ... + @overload + def __setitem__(self, index: slice, value: list[int] | Words): ... + def __delitem__(self, index: int | slice): ... + def __add__(self, other: list[int] | Words) -> Words: ... + def __mul__(self, count: int) -> Words: ... class UBig: - def __new__(obj: Union[int, str]): ... + def __init__(self, obj: int | str): ... + def unwrap(self) -> int: ... + def __repr__(self) -> str: ... + def __str__(self) -> str: ... + def __format__(self) -> str: ... + def __hash__(self) -> int: ... + + def __len__(self) -> int: ... + @overload + def __getitem__(self, index: int) -> bool: ... + @overload + def __getitem__(self, index: slice) -> UBig: ... + def __setitem__(self, index: int | slice, value: bool): ... + def __delitem__(self, index: int, slice): ... + + def __int__(self) -> int: ... def to_words(self) -> Words: ... @staticmethod - def from_words(self, words: Words) -> UBig: ... + def from_words(words: Words) -> UBig: ... + def to_bytes(self, byteorder: Literal['little', 'big'] | None) -> bytes: ... + @staticmethod + def from_words(bytes: bytes, byteorder: Literal['little', 'big'] | None) -> UBig: ... @overload - def __add__(self, other: UBig) -> UBig: ... + def __add__(self, other: Bigs) -> Bigs: ... + @overload + def __add__(self, other: int) -> IBig: ... + @overload + def __radd__(self, other: Bigs) -> Bigs: ... + @overload + def __radd__(self, other: int) -> IBig: ... + @overload + def __sub__(self, other: Bigs) -> Bigs: ... + @overload + def __sub__(self, other: int) -> IBig: ... @overload - def __add__(self, other: Union[int, IBig]) -> IBig: ... + def __rsub__(self, other: Bigs) -> Bigs: ... + @overload + def __rsub__(self, other: int) -> IBig: ... + @overload + def __mul__(self, other: Bigs) -> Bigs: ... + @overload + def __mul__(self, other: int) -> IBig: ... + @overload + def __rmul__(self, other: Bigs) -> Bigs: ... + @overload + def __rmul__(self, other: int) -> IBig: ... + @overload + def __truediv__(self, other: Bigs) -> Bigs: ... + @overload + def __truediv__(self, other: int) -> IBig: ... + @overload + def __rtruediv__(self, other: Bigs) -> Bigs: ... + @overload + def __rtruediv__(self, other: int) -> IBig: ... + def __mod__(self, other: int | UBig | IBig) -> UBig: ... + def __pow__(self, exp: int | UBig, modulus: int | UBig | None) -> UBig: ... + def __pos__(self) -> UBig: ... + def __neg__(self) -> IBig: ... + def __abs__(self) -> IBig: ... + def __nonzero__(self) -> bool: ... + def __lshift__(self, other: int) -> UBig: ... + def __rshift__(self, other: int) -> UBig: ... + def __and__(self, other: int | UBig | IBig) -> UBig: ... + @overload + def __or__(self, other: UBig) -> UBig: ... + @overload + def __or__(self, other: int | IBig) -> IBig: ... + @overload + def __xor__(self, other: UBig) -> UBig: ... + @overload + def __xor__(self, other: int | IBig) -> IBig: ... class IBig: - def __new__(obj: Union[int, str]): ... + def __init__(self, obj: int | str): ... + +class FBig: ... +class DBig: ... +class RBig: ... diff --git a/python/pyproject.toml b/python/pyproject.toml index f3ff88d..66530ce 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -3,7 +3,7 @@ requires = ["maturin>=1,<2"] build-backend = "maturin" [project] -name = "dashu" +name = "dashu-rs" requires-python = ">=3.7" classifiers = [ "Programming Language :: Rust", diff --git a/python/src/int.rs b/python/src/int.rs index 73db940..78c9b03 100644 --- a/python/src/int.rs +++ b/python/src/int.rs @@ -17,7 +17,7 @@ use crate::{ types::{DPy, FPy, IPy, PyWords, RPy, UPy, UniInput}, }; -use dashu_base::{BitTest, Sign, Signed, UnsignedAbs}; +use dashu_base::{Abs, BitTest, Sign, Signed, UnsignedAbs}; use dashu_int::{fast_div, IBig, UBig, Word}; use num_order::NumHash; type FBig = dashu_float::FBig; @@ -37,14 +37,15 @@ const ERRMSG_NEGATIVE_TO_UNSIGNED: &'static str = "can't convert negative int to const ERRMSG_INT_WITH_RADIX: &'static str = "can't convert non-string with explicit base"; const ERRMSG_WRONG_INDEX_TYPE: &'static str = "indices must be integers or slices"; const ERRMSG_UBIG_BITS_OOR: &'static str = "bits index out of range"; +const ERRMSG_BITOPS_TYPE: &'static str = "bit operations are only defined between integers"; -macro_rules! impl_ubig_binops { - ($py_method:ident, $py_method_rev:ident, $rs_method:ident) => { - fn $py_method(lhs: &UPy, rhs: UniInput<'_>, py: Python) -> PyObject { +macro_rules! impl_binops { + ($ty_variant:ident, $py_method:ident, $rs_method:ident) => { + fn $py_method(lhs: &$ty_variant, rhs: UniInput<'_>, py: Python) -> PyObject { match rhs { - UniInput::Uint(x) => UPy((&lhs.0).$rs_method(x)).into_py(py), - UniInput::Int(x) => IPy(lhs.0.as_ibig().$rs_method(x)).into_py(py), - UniInput::BUint(x) => UPy((&lhs.0).$rs_method(&x.0)).into_py(py), + UniInput::Uint(x) => $ty_variant((&lhs.0).$rs_method(x)).into_py(py), + UniInput::Int(x) => IPy((&lhs.0).$rs_method(IBig::from(x))).into_py(py), + UniInput::BUint(x) => $ty_variant((&lhs.0).$rs_method(&x.0)).into_py(py), UniInput::BInt(x) => IPy((&lhs.0).$rs_method(&x.0)).into_py(py), UniInput::OBInt(x) => IPy((&lhs.0).$rs_method(x)).into_py(py), UniInput::Float(x) => { @@ -57,12 +58,15 @@ macro_rules! impl_ubig_binops { UniInput::OBRational(x) => RPy((&lhs.0).$rs_method(x)).into_py(py), } } + }; + ($ty_variant:ident, $py_method:ident, $py_method_rev:ident, $rs_method:ident) => { + impl_binops!($ty_variant, $py_method, $rs_method); - fn $py_method_rev(lhs: UniInput<'_>, rhs: &UPy, py: Python) -> PyObject { + fn $py_method_rev(lhs: UniInput<'_>, rhs: &$ty_variant, py: Python) -> PyObject { match lhs { - UniInput::Uint(x) => UPy(x.$rs_method(&rhs.0).into()).into_py(py), - UniInput::Int(x) => IPy(x.$rs_method(rhs.0.as_ibig()).into()).into_py(py), - UniInput::BUint(x) => UPy((&x.0).$rs_method(&rhs.0)).into_py(py), + UniInput::Uint(x) => $ty_variant(x.$rs_method(&rhs.0).into()).into_py(py), + UniInput::Int(x) => IPy(IBig::from(x).$rs_method(&rhs.0).into()).into_py(py), + UniInput::BUint(x) => $ty_variant((&x.0).$rs_method(&rhs.0)).into_py(py), UniInput::BInt(x) => IPy((&x.0).$rs_method(&rhs.0)).into_py(py), UniInput::OBInt(x) => IPy(x.$rs_method(&rhs.0)).into_py(py), UniInput::Float(x) => { @@ -78,10 +82,113 @@ macro_rules! impl_ubig_binops { }; } -impl_ubig_binops!(upy_add, upy_radd, add); -impl_ubig_binops!(upy_sub, upy_rsub, sub); -impl_ubig_binops!(upy_mul, upy_rmul, mul); -impl_ubig_binops!(upy_div, upy_rdiv, div); +impl_binops!(UPy, upy_add, add); +impl_binops!(UPy, upy_sub, upy_rsub, sub); +impl_binops!(UPy, upy_mul, mul); +impl_binops!(UPy, upy_div, upy_rdiv, div); +impl_binops!(IPy, ipy_add, add); +impl_binops!(IPy, ipy_sub, ipy_rsub, sub); +impl_binops!(IPy, ipy_mul, mul); +impl_binops!(IPy, ipy_div, ipy_rdiv, div); + +fn upy_bitand(lhs: &UPy, rhs: UniInput<'_>, py: Python) -> PyResult { + let result: Py = match rhs { + UniInput::Uint(x) => UPy((&lhs.0).bitand(x).into()).into_py(py), + UniInput::BUint(x) => UPy((&lhs.0).bitand(&x.0)).into_py(py), + UniInput::Int(x) => UPy((&lhs.0).bitand(IBig::from(x))).into_py(py), + UniInput::BInt(x) => UPy((&lhs.0).bitand(&x.0)).into_py(py), + UniInput::OBInt(x) => UPy((&lhs.0).bitand(x)).into_py(py), + _ => return Err(PyTypeError::new_err(ERRMSG_BITOPS_TYPE)), + }; + Ok(result) +} +fn ipy_bitand(lhs: &IPy, rhs: UniInput<'_>, py: Python) -> PyResult { + let result: Py = match rhs { + UniInput::Uint(x) => UPy((&lhs.0).bitand(x).into()).into_py(py), + UniInput::BUint(x) => UPy((&lhs.0).bitand(&x.0)).into_py(py), + UniInput::Int(x) => IPy((&lhs.0).bitand(x)).into_py(py), + UniInput::BInt(x) => IPy((&lhs.0).bitand(&x.0)).into_py(py), + UniInput::OBInt(x) => IPy((&lhs.0).bitand(x)).into_py(py), + _ => return Err(PyTypeError::new_err(ERRMSG_BITOPS_TYPE)), + }; + Ok(result) +} + +macro_rules! impl_ubig_bit_binops { + ($ty_variant:ident, $py_method:ident, $rs_method:ident) => { + fn $py_method(lhs: &$ty_variant, rhs: UniInput<'_>, py: Python) -> PyResult { + let result = match rhs { + UniInput::Uint(x) => $ty_variant((&lhs.0).$rs_method(x)).into_py(py), + UniInput::BUint(x) => $ty_variant((&lhs.0).$rs_method(&x.0)).into_py(py), + UniInput::Int(x) => IPy((&lhs.0).$rs_method(IBig::from(x))).into_py(py), + UniInput::BInt(x) => IPy((&lhs.0).$rs_method(&x.0)).into_py(py), + UniInput::OBInt(x) => IPy((&lhs.0).$rs_method(x)).into_py(py), + _ => return Err(PyTypeError::new_err(ERRMSG_BITOPS_TYPE)), + }; + Ok(result) + } + }; +} +impl_ubig_bit_binops!(UPy, upy_bitor, bitor); +impl_ubig_bit_binops!(UPy, upy_bitxor, bitxor); +impl_ubig_bit_binops!(IPy, ipy_bitor, bitor); +impl_ubig_bit_binops!(IPy, ipy_bitxor, bitxor); + +fn upy_mod(lhs: &UPy, rhs: UniInput<'_>, py: Python) -> PyObject { + match rhs { + UniInput::Uint(x) => UPy((&lhs.0).rem(x).into()).into_py(py), + UniInput::Int(x) => UPy((&lhs.0).rem(IBig::from(x))).into_py(py), + UniInput::BUint(x) => UPy((&lhs.0).rem(&x.0)).into_py(py), + UniInput::BInt(x) => UPy((&lhs.0).rem(&x.0)).into_py(py), + UniInput::OBInt(x) => UPy((&lhs.0).rem(x)).into_py(py), + _ => todo!(), + } +} +fn ipy_mod(lhs: &IPy, rhs: UniInput<'_>, py: Python) -> PyObject { + match rhs { + UniInput::Uint(x) => UPy((&lhs.0).rem(x).into()).into_py(py), + UniInput::Int(x) => IPy((&lhs.0).rem(x).into()).into_py(py), + UniInput::BUint(x) => IPy((&lhs.0).rem(&x.0)).into_py(py), + UniInput::BInt(x) => IPy((&lhs.0).rem(&x.0)).into_py(py), + UniInput::OBInt(x) => IPy((&lhs.0).rem(x)).into_py(py), + _ => todo!(), + } +} +fn ipy_pow(base: &IBig, exp: UniInput, modulus: Option) -> PyResult { + use fast_div::ConstDivisor; + + if let Some(m) = modulus { + // first parse the modulus + let (_sign, ring) = match m { + UniInput::Uint(x) => (Sign::Positive, ConstDivisor::new(x.into())), + UniInput::BUint(x) => (Sign::Positive, ConstDivisor::new(x.0.clone())), + UniInput::Int(x) => (x.sign(), ConstDivisor::new(x.unsigned_abs().into())), + UniInput::BInt(x) => (x.0.sign(), ConstDivisor::new((&x.0).unsigned_abs())), + UniInput::OBInt(x) => { + let (sign, m) = x.into_parts(); + (sign, ConstDivisor::new(m)) + } + _ => todo!(), + }; + + match exp { + UniInput::Uint(x) => { + let (sign, u) = base.clone().into_parts(); + let mut r = ring.reduce(u); + if sign == Sign::Negative { + r = r.neg(); + } + Ok(r.pow(&x.into()).residue().into()) + } + _ => todo!(), + } + } else { + match exp { + UniInput::Uint(x) => Ok(base.pow(x as _)), + _ => todo!(), + } + } +} #[pymethods] impl UPy { @@ -136,7 +243,7 @@ impl UPy { hasher.finish() } - // use as a bit vector + /********** use as a bit vector **********/ fn __len__(&self) -> usize { self.0.bit_len() } @@ -297,7 +404,7 @@ impl UPy { } #[inline] fn __radd__(&self, other: UniInput<'_>, py: Python) -> PyObject { - upy_radd(other, &self, py) + upy_add(&self, other, py) } #[inline] fn __sub__(&self, other: UniInput<'_>, py: Python) -> PyObject { @@ -313,7 +420,7 @@ impl UPy { } #[inline] fn __rmul__(&self, other: UniInput<'_>, py: Python) -> PyObject { - upy_rmul(other, &self, py) + upy_mul(&self, other, py) } #[inline] fn __truediv__(&self, other: UniInput<'_>, py: Python) -> PyObject { @@ -325,16 +432,6 @@ impl UPy { } #[inline] fn __mod__(&self, other: UniInput<'_>, py: Python) -> PyObject { - fn upy_mod(lhs: &UPy, rhs: UniInput<'_>, py: Python) -> PyObject { - match rhs { - UniInput::Uint(x) => (&lhs.0).rem(x).into_py(py), - UniInput::Int(x) => lhs.0.as_ibig().rem(x).into_py(py), - UniInput::BUint(x) => UPy((&lhs.0).rem(&x.0)).into_py(py), - UniInput::BInt(x) => UPy((&lhs.0).rem(&x.0)).into_py(py), - UniInput::OBInt(x) => UPy((&lhs.0).rem(x)).into_py(py), - _ => todo!(), - } - } upy_mod(self, other, py) } #[inline] @@ -344,51 +441,26 @@ impl UPy { modulus: Option, py: Python, ) -> PyResult { - use fast_div::ConstDivisor; - - if let Some(m) = modulus { - // first parse the modulus - let (_sign, ring) = match m { - UniInput::Uint(x) => (Sign::Positive, ConstDivisor::new(x.into())), - UniInput::BUint(x) => (Sign::Positive, ConstDivisor::new(x.0.clone())), - UniInput::Int(x) => (x.sign(), ConstDivisor::new(x.unsigned_abs().into())), - UniInput::BInt(x) => (x.0.sign(), ConstDivisor::new((&x.0).unsigned_abs())), - UniInput::OBInt(x) => { - let (sign, m) = x.into_parts(); - (sign, ConstDivisor::new(m)) - } - _ => todo!(), - }; - - match other { - UniInput::Uint(x) => { - Ok(UPy(ring.reduce(self.0.clone()).pow(&x.into()).residue()).into_py(py)) - } - _ => todo!(), - } - } else { - match other { - UniInput::Uint(x) => Ok(UPy(self.0.pow(x as _)).into_py(py)), - _ => todo!(), - } - } + ipy_pow(self.0.as_ibig(), other, modulus).map(|n| UPy(n.try_into().unwrap()).into_py(py)) } + #[inline] - fn __pos__(&self) -> UPy { - todo!() + fn __pos__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { + slf } #[inline] fn __neg__(&self) -> IPy { IPy((&self.0).neg()) } #[inline] - fn __abs__(&self) -> UPy { - todo!() + fn __abs__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { + slf } #[inline] fn __nonzero__(&self) -> bool { !self.0.is_zero() } + #[inline] fn __lshift__(&self, other: usize) -> UPy { UPy((&self.0) << other) @@ -398,23 +470,16 @@ impl UPy { UPy((&self.0) >> other) } #[inline] - fn __and__(&self, other: UniInput, py: Python) -> PyObject { - match other { - UniInput::Uint(x) => (&self.0).bitand(x).into_py(py), - UniInput::BUint(x) => UPy((&self.0).bitand(&x.0)).into_py(py), - UniInput::Int(x) => UPy((&self.0).bitand(IBig::from(x))).into_py(py), - UniInput::BInt(x) => UPy((&self.0).bitand(&x.0)).into_py(py), - UniInput::OBInt(x) => UPy((&self.0).bitand(x)).into_py(py), - _ => todo!(), // TODO: raise TypeError - } + fn __and__(&self, other: UniInput, py: Python) -> PyResult { + upy_bitand(self, other, py) } #[inline] - fn __or__(&self, other: &PyAny, py: Python) -> PyObject { - todo!() + fn __or__(&self, other: UniInput, py: Python) -> PyResult { + upy_bitor(self, other, py) } #[inline] - fn __xor__(&self, other: &PyAny, py: Python) -> PyObject { - todo!() + fn __xor__(&self, other: UniInput, py: Python) -> PyResult { + upy_bitxor(self, other, py) } } @@ -443,6 +508,8 @@ impl IPy { IBig::from_str_with_radix_prefix(s).map(|v| v.0) }; Ok(IPy(n.map_err(parse_error_to_py)?)) + } else if let Ok(obj) = as FromPyObject>::extract(ob) { + Ok(IPy(obj.0.clone().into())) } else if let Ok(obj) = as FromPyObject>::extract(ob) { Ok(IPy(obj.0.clone())) } else { @@ -468,7 +535,7 @@ impl IPy { hasher.finish() } - // use as a bit vector with very limited capabilities + /********** use as a bit vector with very limited capabilities **********/ fn __len__(&self) -> usize { self.0.bit_len() } @@ -476,6 +543,91 @@ impl IPy { self.0.bit(i) } + /********** operators **********/ + #[inline] + fn __add__(&self, other: UniInput<'_>, py: Python) -> PyObject { + ipy_add(&self, other, py) + } + #[inline] + fn __radd__(&self, other: UniInput<'_>, py: Python) -> PyObject { + ipy_add(&self, other, py) + } + #[inline] + fn __sub__(&self, other: UniInput<'_>, py: Python) -> PyObject { + ipy_sub(&self, other, py) + } + #[inline] + fn __rsub__(&self, other: UniInput<'_>, py: Python) -> PyObject { + ipy_rsub(other, &self, py) + } + #[inline] + fn __mul__(&self, other: UniInput<'_>, py: Python) -> PyObject { + ipy_mul(&self, other, py) + } + #[inline] + fn __rmul__(&self, other: UniInput<'_>, py: Python) -> PyObject { + ipy_mul(&self, other, py) + } + #[inline] + fn __truediv__(&self, other: UniInput<'_>, py: Python) -> PyObject { + ipy_div(&self, other, py) + } + #[inline] + fn __rtruediv__(&self, other: UniInput<'_>, py: Python) -> PyObject { + ipy_rdiv(other, &self, py) + } + #[inline] + fn __mod__(&self, other: UniInput<'_>, py: Python) -> PyObject { + ipy_mod(self, other, py) + } + #[inline] + fn __pow__( + &self, + other: UniInput, + modulus: Option, + py: Python, + ) -> PyResult { + ipy_pow(&self.0, other, modulus).map(|n| IPy(n).into_py(py)) + } + + #[inline] + fn __pos__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { + slf + } + #[inline] + fn __neg__(&self) -> IPy { + IPy((&self.0).neg()) + } + #[inline] + fn __abs__(&self) -> IPy { + IPy((&self.0).abs()) + } + #[inline] + fn __nonzero__(&self) -> bool { + !self.0.is_zero() + } + + #[inline] + fn __lshift__(&self, other: usize) -> IPy { + IPy((&self.0) << other) + } + #[inline] + fn __rshift__(&self, other: usize) -> IPy { + IPy((&self.0) >> other) + } + #[inline] + fn __and__(&self, other: UniInput, py: Python) -> PyResult { + ipy_bitand(self, other, py) + } + #[inline] + fn __or__(&self, other: UniInput, py: Python) -> PyResult { + ipy_bitor(self, other, py) + } + #[inline] + fn __xor__(&self, other: UniInput, py: Python) -> PyResult { + ipy_bitxor(self, other, py) + } + /********** interop **********/ fn __int__(&self, py: Python) -> PyResult { diff --git a/python/src/types.rs b/python/src/types.rs index f2bebe0..2c8024d 100644 --- a/python/src/types.rs +++ b/python/src/types.rs @@ -78,7 +78,7 @@ impl From for RPy { } } -#[pyclass] +#[pyclass(name = "Words")] pub struct PyWords(pub std::vec::Vec); /// An input type that accepts all possible numeric types from Python diff --git a/python/src/utils.rs b/python/src/utils.rs index e74d8df..1c26b37 100644 --- a/python/src/utils.rs +++ b/python/src/utils.rs @@ -25,6 +25,7 @@ pub fn auto(ob: UniInput, py: Python<'_>) -> PyResult { } }; + // TODO: shrink each type to the minimal representation (FBig/RBig -> IBig -> UBig) let obj = match ob { Uint(v) => UPy(v.into()).into_py(py), Int(v) => fit_ibig(v.into()), From 0bc78f8d65294aa9ad210cf476dcf897557b51fe Mon Sep 17 00:00:00 2001 From: Jacob Date: Sat, 2 Mar 2024 22:49:36 +0800 Subject: [PATCH 15/15] Fix fmt and clippy --- float/src/fmt.rs | 2 +- integer/src/convert.rs | 2 +- integer/src/third_party/serde.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/float/src/fmt.rs b/float/src/fmt.rs index ba87a04..c8d1aa6 100644 --- a/float/src/fmt.rs +++ b/float/src/fmt.rs @@ -403,7 +403,7 @@ impl Repr { } f.write_char(exp_marker.unwrap_or('@'))?; - f.write_str(&exp_str)?; + f.write_str(exp_str)?; // print right padding for _ in 0..right_pad { diff --git a/integer/src/convert.rs b/integer/src/convert.rs index ed6c2a9..a8b819d 100644 --- a/integer/src/convert.rs +++ b/integer/src/convert.rs @@ -37,7 +37,7 @@ impl Default for IBig { } } -fn words_to_le_bytes(words: &[Word]) -> Vec { +pub(crate) fn words_to_le_bytes(words: &[Word]) -> Vec { debug_assert!(!words.is_empty()); let n = words.len(); diff --git a/integer/src/third_party/serde.rs b/integer/src/third_party/serde.rs index 9c7aac5..5595da5 100644 --- a/integer/src/third_party/serde.rs +++ b/integer/src/third_party/serde.rs @@ -15,7 +15,7 @@ impl Serialize for UBig { } else if self.is_zero() { serializer.serialize_bytes(&[]) } else { - let bytes = words_to_le_bytes(self.as_words()); + let bytes = words_to_le_bytes::(self.as_words()); serializer.serialize_bytes(&bytes) } } @@ -62,7 +62,7 @@ impl Serialize for IBig { } else { // TODO(v0.5): Change to IBig::to_le_bytes(), which provides better interop robustness let (sign, words) = self.as_sign_words(); - let mut bytes = words_to_le_bytes(words); + let mut bytes = words_to_le_bytes::(words); // use the length to encode the sign, postive <=> even, negative <=> odd. // pad zeros when necessary