Skip to content

Commit

Permalink
Add more
Browse files Browse the repository at this point in the history
  • Loading branch information
cmpute committed Jan 13, 2024
1 parent 64f0250 commit b0ac0a2
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 44 deletions.
66 changes: 39 additions & 27 deletions guide/src/convert.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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).
2 changes: 1 addition & 1 deletion integer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Unreleased

- Implement `TryFrom<UBig>` for `f32`/`f64`.
- Implement `TryFrom<UBig>` and `TryFrom<IBig>` for `f32`/`f64`.

## 0.4.1

Expand Down
40 changes: 24 additions & 16 deletions integer/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,10 +450,8 @@ macro_rules! ubig_float_conversions {
}
ubig_float_conversions!(f32 => u32; f64 => u64;);

// TODO: implement TryFrom<IBig/FBig/RBig> 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;

Expand All @@ -468,9 +466,27 @@ macro_rules! ibig_float_conversions {
Ok(result)
}
}

impl TryFrom<IBig> for $t {
type Error = ConversionError;

fn try_from(value: IBig) -> Result<Self, Self::Error> {
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<UBig> for IBig {
#[inline]
Expand Down Expand Up @@ -627,14 +643,10 @@ mod repr {
}

#[inline]
#[allow(clippy::unnecessary_cast)] // because DoubleWord is not always u128
pub fn to_f32(self) -> Approximation<f32, Sign> {
match self {
RefSmall(dword) => to_f32_small(dword as u128),
RefLarge(_) => match self.try_to_unsigned::<u128>() {
Ok(val) => to_f32_small(val as u128),
Err(_) => self.to_f32_nontrivial(),
},
RefSmall(dword) => to_f32_small(dword),
RefLarge(_) => self.to_f32_nontrivial(),
}
}

Expand All @@ -653,14 +665,10 @@ mod repr {
}

#[inline]
#[allow(clippy::unnecessary_cast)] // because DoubleWord is not always u128
pub fn to_f64(self) -> Approximation<f64, Sign> {
match self {
RefSmall(dword) => to_f64_small(dword as u128),
RefLarge(_) => match self.try_to_unsigned::<u128>() {
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(),
}
}

Expand Down
14 changes: 14 additions & 0 deletions integer/tests/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down Expand Up @@ -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(),
Expand Down

0 comments on commit b0ac0a2

Please sign in to comment.