From b0ac0a2cd239c68ce19ea50d43547e6a3eec89dd Mon Sep 17 00:00:00 2001 From: Jacob Date: Sat, 13 Jan 2024 23:23:33 +0800 Subject: [PATCH] Add more --- guide/src/convert.md | 66 ++++++++++++++++++++++++---------------- integer/CHANGELOG.md | 2 +- integer/src/convert.rs | 40 ++++++++++++++---------- integer/tests/convert.rs | 14 +++++++++ 4 files changed, 78 insertions(+), 44 deletions(-) diff --git a/guide/src/convert.md b/guide/src/convert.md index 600f6d4..11388dd 100644 --- a/guide/src/convert.md +++ b/guide/src/convert.md @@ -6,29 +6,46 @@ Note that a general principle of implementations of `TryFrom` in `dashu` is that Most of the time, you can use `From`/`Into`/`TryFrom`/`TryInto` to convert between these types. When the conversion is fallible, only `TryFrom` and `TryInto` will be implemented. Below is a table of conversions between arbitrary precision types using these traits, where the columns are source types, and rows are destination types. -| Dest\Src | UBig | IBig | FBig/DBig | RBig | -|-----------|------|---------|-----------|----------| -| UBig | \ | TryFrom | TryFrom | TryFrom | -| IBig | From | \ | TryFrom | TryFrom | -| FBig/DBig | From | From | \ | TryFrom* | -| RBig | From | From | TryFrom* | \ | +| Dest\Src | UBig | IBig | FBig/DBig | RBig | +|-----------|------|---------|--------------|-------------| +| UBig | \ | TryFrom | TryFrom | TryFrom | +| IBig | From | \ | TryFrom | TryFrom | +| FBig/DBig | From | From | \ | TryFrom[^a] | +| RBig | From | From | TryFrom[^a] | \ | -> *: To use the conversion between `RBig` and `FBig`, the optional feature `dashu-float` must be enabled for the `dashu-ratio` crate. +> [^a]: To use the conversion between `RBig` and `FBig`, the optional feature `dashu-float` must be enabled for the `dashu-ratio` crate. These conversions will only succeed when the conversion is exact (lossless) and in-range. For example, the conversion from a float number to an integer will fail, if the float number is infinite (will return `Err(ConversionError::OutOfBounds)`), or it has fractional parts (will return `Err(ConversionError::LossOfPrecision)`). -Nevertheless, there are other useful methods for lossy conversions: -- `IBig` to `UBig`: `.unsigned_abs()` (from `dashu_base::UnsignedAbs`) -- `FBig`/`DBig` to `IBig`: `.to_int()` -- `RBig` to `IBig`: `.to_int()`, `.trunc()`, `.ceil()`, `.floor()` -- `RBig` to `FBig`/`FBig`: `.to_float()` -> The methods `.ceil()`, `.floor()` and `.trunc()` of `FBig` doesn't return `IBig`, because when `FBig` is very large (with a high exponent), the `IBig` result can consume a great amout of memory, which is usually not a desirable behavior. +Nevertheless, there are other useful methods for **lossy** conversions: + +| Src\Dest | UBig | IBig | FBig/DBig | RBig | +|-----------|-------------------|---------------------------------------|-------------------|------------------------------| +| UBig | \ | \ | \ | \ | +| IBig | `.unsigned_abs()` | \ | \ | \ | +| FBig/DBig | \ | `.to_int()`[^b] | ...[^c] | `.simplest_from_float()`[^d] | +| RBig | \ | `.to_int()/.trunc()/.floor()/.ceil()` | `.to_float()`[^e] | \ | + +> - [^b] The methods `.ceil()`, `.floor()` and `.trunc()` of `FBig` doesn't return `IBig`, because when `FBig` is very large (with a high exponent), the `IBig` result can consume a great amout of memory, which is usually not a desirable behavior. +> - [^c] See the section *Conversion for FBig/DBig* below for this conversion. +> - [^d] See the section *Conversion from Floats to RBig* below for more approaches. +> - [^e] This method requires the `dashu-float` feature to be enabled for the crate `dashu-ratio`. Another useful conversion is `UBig::as_ibig()`. Due to the fact that `UBig` and `IBig` has the same memory layout, A `UBig` can be directed used as an `IBig` through this method. Similarly, `RBig::as_relaxed()` can be helpful when you want to use an `RBig` instance as an `dashu_ratio::Relaxed`. Besides these methods designed for conversions, the constructors and destructors can also be used for the purpose of type conversion, especially from compound types to its parts. Please refer to the [Construction and Destruction](./construct.md#Construct_from_Parts) page for this approach. -# Conversion between Primitives + +## Conversion for FBig/DBig + +TODO: `with_rounding`, `with_precision`, `with_base`, `to_binary`, `to_decimal`, etc. +(how precision is determined) + +## Conversion from Floats to RBig + +TODO: `simplest_in()`, `simplest_from_*()`, `.nearest_in()`, `next_up()`, `next_down()`, etc. + +# Conversion between Big Numbers and Primitives All the numeric types in the `dashu` crates support conversion from and to primitive types. @@ -41,20 +58,15 @@ To convert from primitive to big numbers: | FBig/DBig | From | From | TryFrom* | | RBig | From | From | TryFrom | -> *: The conversion from `f32`/`f64` to `FBig` is only defined in base 2, because the conversion is almost always lossy when the base is not a power of two. To convert from `f32`/`f64` to big floats with other bases (such as `DBig` with base 10), the conversion can be achieved by converting to base 2 first, and then use the `.with_base()` method to convert to other bases. By this way, the rounding during the conversion can be explicitly selected. +> *: The conversion from `f32`/`f64` to `FBig` is **only defined in base 2**, because the conversion is almost always lossy when the base is not a power of two. To convert from `f32`/`f64` to big floats with other bases (such as `DBig` with base 10), the conversion can be achieved by converting to base 2 first, and then use the `.with_base()` method to convert to other bases. By this way, the rounding during the conversion can be explicitly selected. To convert from big numbers to primitive numbers: -| Dest\Src | UBig | IBig | FBig/DBig | RBig | -|---------------|------------|------------|------------|----------------------------| -| u* (e.g. u8) | TryFrom | TryFrom | TryFrom | TryFrom | -| i* (e.g. i8) | TryFrom | TryFrom | TryFrom | TryFrom | -| f* (e.g. f32) | `.to_f*()` | `.to_f*()` | `.to_f*()` | `.to_f*()`/`.to_f*_fast()` | - -In the table above, `.to_f*()` denotes `.to_f32()` and `.to_f64()`, similarly `.to_f*_fast()` denotes `.to_f32_fast()` and `.to_f64_fast()`. The *fast* methods doesn't guarantee corrent rounding so that it can be faster. It's recommended to use the `.to_f*()` methods over the `TryFrom`/`TryInto` trait, because `.to_f*()` will not fail and it also returns the rounding direction during the conversion (i.e. the sign of the rounding error). - -# Conversion for FBig/DBig - -(how precision is determined) +| Src\Dest | u* (e.g. u8) | i* (e.g. i8) | f* (e.g. f32) | +|-----------|--------------|--------------|------------------------------------| +| UBig | TryInto | TryInto | TryInto/`.to_f*()` | +| IBig | TryInto | TryInto | TryInto/`.to_f*()` | +| FBig/DBig | TryInto | TryInto | TryInto/`.to_f*()` | +| RBig | TryInto | TryInto | TryInto/`.to_f*()`/`.to_f*_fast()` | -# Conversion between FBig/RBig +In the table above, `.to_f*()` denotes `.to_f32()` and `.to_f64()`, similarly `.to_f*_fast()` denotes `.to_f32_fast()` and `.to_f64_fast()`. The *fast* methods don't guarantee corrent rounding so that they can be faster. It's recommended to use the `.to_f*()` methods over the `TryFrom`/`TryInto` trait, because `.to_f*()` will not fail and it also returns the rounding direction during the conversion (i.e. the sign of the rounding error). diff --git a/integer/CHANGELOG.md b/integer/CHANGELOG.md index 2cc478f..daf83f6 100644 --- a/integer/CHANGELOG.md +++ b/integer/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -- Implement `TryFrom` for `f32`/`f64`. +- Implement `TryFrom` and `TryFrom` for `f32`/`f64`. ## 0.4.1 diff --git a/integer/src/convert.rs b/integer/src/convert.rs index 7e91222..f847228 100644 --- a/integer/src/convert.rs +++ b/integer/src/convert.rs @@ -450,10 +450,8 @@ macro_rules! ubig_float_conversions { } ubig_float_conversions!(f32 => u32; f64 => u64;); -// TODO: implement TryFrom for f32/f64, and document this in the guide and change log. - macro_rules! ibig_float_conversions { - ($($t:ty)*) => {$( + ($($t:ty => $i:ty;)*) => {$( impl TryFrom<$t> for IBig { type Error = ConversionError; @@ -468,9 +466,27 @@ macro_rules! ibig_float_conversions { Ok(result) } } + + impl TryFrom for $t { + type Error = ConversionError; + + fn try_from(value: IBig) -> Result { + const MAX_BIT_LEN: usize = (<$t>::MANTISSA_DIGITS + 1) as usize; + let (sign, value) = value.into_parts(); + if value.bit_len() > MAX_BIT_LEN + || (value.bit_len() == MAX_BIT_LEN && !value.is_power_of_two()) + { + // precision loss occurs when the integer has more digits than what the mantissa can store + Err(ConversionError::LossOfPrecision) + } else { + let float = <$i>::try_from(value).unwrap() as $t; + Ok(sign * float) + } + } + } )*}; } -ibig_float_conversions!(f32 f64); +ibig_float_conversions!(f32 => u32; f64 => u64;); impl From for IBig { #[inline] @@ -627,14 +643,10 @@ mod repr { } #[inline] - #[allow(clippy::unnecessary_cast)] // because DoubleWord is not always u128 pub fn to_f32(self) -> Approximation { match self { - RefSmall(dword) => to_f32_small(dword as u128), - RefLarge(_) => match self.try_to_unsigned::() { - Ok(val) => to_f32_small(val as u128), - Err(_) => self.to_f32_nontrivial(), - }, + RefSmall(dword) => to_f32_small(dword), + RefLarge(_) => self.to_f32_nontrivial(), } } @@ -653,14 +665,10 @@ mod repr { } #[inline] - #[allow(clippy::unnecessary_cast)] // because DoubleWord is not always u128 pub fn to_f64(self) -> Approximation { match self { - RefSmall(dword) => to_f64_small(dword as u128), - RefLarge(_) => match self.try_to_unsigned::() { - Ok(val) => to_f64_small(val as u128), - Err(_) => self.to_f64_nontrivial(), - }, + RefSmall(dword) => to_f64_small(dword as DoubleWord), + RefLarge(_) => self.to_f64_nontrivial(), } } diff --git a/integer/tests/convert.rs b/integer/tests/convert.rs index 6a04e61..2ac97c7 100644 --- a/integer/tests/convert.rs +++ b/integer/tests/convert.rs @@ -247,8 +247,15 @@ fn test_to_f32() { assert_eq!((ubig!(1) << 1000).to_f32(), Inexact(f32::INFINITY, Positive)); assert_eq!(ibig!(0).to_f32(), Exact(0.0f32)); + assert_eq!(f32::try_from(ibig!(0)).unwrap(), 0.0f32); assert_eq!(ibig!(7).to_f32(), Exact(7.0f32)); + assert_eq!(f32::try_from(ibig!(7)).unwrap(), 7.0f32); assert_eq!(ibig!(-7).to_f32(), Exact(-7.0f32)); + assert_eq!(f32::try_from(ibig!(-7)).unwrap(), -7.0f32); + assert_eq!(ibig!(-0x1000000).to_f32(), Exact(-16777216.0f32)); + assert_eq!(f32::try_from(ibig!(-0x1000000)).unwrap(), -16777216.0f32); + assert_eq!(ibig!(-0x1000001).to_f32(), Inexact(-16777216.0f32, Positive)); + assert_eq!(f32::try_from(ibig!(-0x1000001)), Err(LossOfPrecision)); assert!((ibig!(-0xffffff7) << 100).to_f32().value() > -f32::INFINITY); assert_eq!((ibig!(-0xffffff8) << 100).to_f32(), Inexact(f32::NEG_INFINITY, Negative)); } @@ -322,8 +329,15 @@ fn test_to_f64() { assert_eq!((ubig!(1) << 10000).to_f64(), Inexact(f64::INFINITY, Positive)); assert_eq!(ibig!(0).to_f64(), Exact(0.0f64)); + assert_eq!(f64::try_from(ibig!(0)).unwrap(), 0.0f64); assert_eq!(ibig!(7).to_f64(), Exact(7.0f64)); + assert_eq!(f64::try_from(ibig!(7)).unwrap(), 7.0f64); assert_eq!(ibig!(-7).to_f64(), Exact(-7.0f64)); + assert_eq!(f64::try_from(ibig!(-7)).unwrap(), -7.0f64); + assert_eq!(ibig!(-0x20000000000000).to_f64(), Exact(-9007199254740992.0f64)); + assert_eq!(f64::try_from(ibig!(-0x20000000000000)).unwrap(), -9007199254740992.0f64); + assert_eq!(ibig!(-0x20000000000001).to_f64(), Inexact(-9007199254740992.0f64, Positive)); + assert_eq!(f64::try_from(ibig!(-0x20000000000001)), Err(LossOfPrecision)); assert!((ibig!(-0x1fffffffffffff7) << 967).to_f64().value() > -f64::INFINITY); assert_eq!( (ibig!(-0x1fffffffffffff8) << 967).to_f64(),