From 694ae24d77c808273e553608dedfbfce10f4bc35 Mon Sep 17 00:00:00 2001 From: Trevor Spiteri Date: Fri, 8 May 2020 12:41:35 +0200 Subject: [PATCH] add LosslessTry{From,Into} and immplement them for Fixed <-> Fixed --- README.md | 10 +++++++ RELEASES.md | 1 + src/convert.rs | 54 ++++++++++++++++++++++++++++++++-- src/lib.rs | 10 ++++++- src/traits.rs | 80 +++++++++++++++++++++++++++++++++++++++++++++----- 5 files changed, 144 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 8465ec6..c655477 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,10 @@ The conversions supported cover the following cases. numeric primitives are provided using the [`LossyFrom`] and [`LossyInto`] traits. The source can have more fractional bits than the destination. + * Checked lossless conversions between fixed-point numbers and + numeric primitives are provided using the [`LosslessTryFrom`] and + [`LosslessTryInto`] traits. The source cannot have more fractional + bits than the destination. * Checked conversions between fixed-point numbers and numeric primitives are provided using the [`FromFixed`] and [`ToFixed`] traits, or using the [`from_num`] and [`to_num`] methods and @@ -76,6 +80,8 @@ The conversions supported cover the following cases. ### Version 0.5.7 news (unreleased) + * The [`LosslessTryFrom`][ltf-0-5-7] and + [`LosslessTryInto`][lti-0-5-7] traits were added. * The [`PHI`][phi-0-5-7] and [`FRAC_1_PHI`][f1phi-0-5-7] constants were added to the [`consts`][cons-0-5-7] module and as [associated constants][f-phi-0-5-7] for fixed-point types. @@ -83,6 +89,8 @@ The conversions supported cover the following cases. [cons-0-5-7]: https://tspiteri.gitlab.io/fixed/dev/fixed/consts/index.html [f-phi-0-5-7]: https://tspiteri.gitlab.io/fixed/dev/fixed/struct.FixedI32.html#associatedconstant.PHI [f1phi-0-5-7]: https://tspiteri.gitlab.io/fixed/dev/fixed/consts/constant.FRAC_1_PHI.html +[ltf-0-5-7]: https://tspiteri.gitlab.io/fixed/dev/fixed/traits/trait.LosslessTryFrom.html +[lti-0-5-7]: https://tspiteri.gitlab.io/fixed/dev/fixed/traits/trait.LosslessTryInto.html [phi-0-5-7]: https://tspiteri.gitlab.io/fixed/dev/fixed/consts/constant.PHI.html ### Version 0.5.6 news (2020-05-01) @@ -256,6 +264,8 @@ additional terms or conditions. [`I4F12`]: https://docs.rs/fixed/0.5.6/fixed/types/type.I4F12.html [`I4F4`]: https://docs.rs/fixed/0.5.6/fixed/types/type.I4F4.html [`Into`]: https://doc.rust-lang.org/nightly/core/convert/trait.Into.html +[`LosslessTryFrom`]: https://tspiteri.gitlab.io/fixed/dev/fixed/traits/trait.LosslessTryFrom.html +[`LosslessTryInto`]: https://tspiteri.gitlab.io/fixed/dev/fixed/traits/trait.LosslessTryInto.html [`LossyFrom`]: https://docs.rs/fixed/0.5.6/fixed/traits/trait.LossyFrom.html [`LossyInto`]: https://docs.rs/fixed/0.5.6/fixed/traits/trait.LossyInto.html [`LowerHex`]: https://doc.rust-lang.org/nightly/core/fmt/trait.LowerHex.html diff --git a/RELEASES.md b/RELEASES.md index 12eda12..0414294 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -8,6 +8,7 @@ as-is, without any warranty. --> Version 0.5.7 (unreleased) ========================== + * The `LosslessTryFrom` and `LosslessTryInto` traits were added. * The `PHI` and `FRAC_1_PHI` constants were added to the `consts` module and as associated constants for fixed-point types. diff --git a/src/convert.rs b/src/convert.rs index 3a7984b..a40f2f1 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -15,7 +15,7 @@ use crate::{ helpers::IntHelper, - traits::LossyFrom, + traits::{LosslessTryFrom, LossyFrom}, types::extra::{ Diff, IsLessOrEqual, LeEqU128, LeEqU16, LeEqU32, LeEqU64, LeEqU8, True, U0, U1, U127, U128, U15, U16, U31, U32, U63, U64, U7, U8, @@ -91,10 +91,47 @@ macro_rules! convert { }; } +macro_rules! convert_lossless { + ( + ($Src:ident, $SrcBits:ident, $SrcLeEqU:ident) -> + ($Dst:ident, $DstBits:ident, $DstLeEqU:ident) + ) => { + // lossless because Src::FRAC_NBITS <= Dst::FRAC_NBITS + impl LosslessTryFrom<$Src> + for $Dst + where + FracSrc: IsLessOrEqual, + { + /// Converts a fixed-pint number. + /// + /// This conversion may fail (fallible) but does not lose + /// precision (lossless). + #[inline] + fn lossless_try_from(src: $Src) -> Option { + src.checked_to_num() + } + } + }; + ($Src:ident, $SrcBits:ident, $SrcLeEqU:ident) => { + convert_lossless! { ($Src, $SrcBits, $SrcLeEqU) -> (FixedI8, U8, LeEqU8) } + convert_lossless! { ($Src, $SrcBits, $SrcLeEqU) -> (FixedI16, U16, LeEqU16) } + convert_lossless! { ($Src, $SrcBits, $SrcLeEqU) -> (FixedI32, U32, LeEqU32) } + convert_lossless! { ($Src, $SrcBits, $SrcLeEqU) -> (FixedI64, U64, LeEqU64) } + convert_lossless! { ($Src, $SrcBits, $SrcLeEqU) -> (FixedI128, U128, LeEqU128) } + convert_lossless! { ($Src, $SrcBits, $SrcLeEqU) -> (FixedU8, U8, LeEqU8) } + convert_lossless! { ($Src, $SrcBits, $SrcLeEqU) -> (FixedU16, U16, LeEqU16) } + convert_lossless! { ($Src, $SrcBits, $SrcLeEqU) -> (FixedU32, U32, LeEqU32) } + convert_lossless! { ($Src, $SrcBits, $SrcLeEqU) -> (FixedU64, U64, LeEqU64) } + convert_lossless! { ($Src, $SrcBits, $SrcLeEqU) -> (FixedU128, U128, LeEqU128) } + }; +} + macro_rules! convert_lossy { ( ($SrcU:ident, $SrcI:ident, $SrcBits:ident, $SrcLeEqU:ident) -> - ($DstU:ident, $DstI:ident, $DstBits:ident, $DstBitsM1:ident, $DstLeEqU:ident)) => { + ($DstU:ident, $DstI:ident, $DstBits:ident, $DstBitsM1:ident, $DstLeEqU:ident) + ) => { + // unsigned -> unsigned, infallible because Src::INT_NBITS <= Dst::INT_NBITS impl LossyFrom<$SrcU> for $DstU where $SrcBits: Sub, @@ -113,6 +150,7 @@ macro_rules! convert_lossy { } } + // signed -> signed, infallible because Src::INT_NBITS <= Dst::INT_NBITS impl LossyFrom<$SrcI> for $DstI where $SrcBits: Sub, @@ -131,6 +169,7 @@ macro_rules! convert_lossy { } } + // signed -> signed, infallible because Src::INT_NBITS <= Dst::INT_NBITS - 1 impl LossyFrom<$SrcU> for $DstI where $SrcBits: Sub, @@ -182,6 +221,17 @@ convert! { (FixedU32, FixedI32, U32, LeEqU32) -> (FixedU128, FixedI128, U128, U1 convert! { (FixedU64, FixedI64, U64, LeEqU64) -> (FixedU128, FixedI128, U128, U127, LeEqU128) } +convert_lossless! { FixedI8, U8, LeEqU8 } +convert_lossless! { FixedI16, U16, LeEqU16 } +convert_lossless! { FixedI32, U32, LeEqU32 } +convert_lossless! { FixedI64, U64, LeEqU64 } +convert_lossless! { FixedI128, U128, LeEqU128 } +convert_lossless! { FixedU8, U8, LeEqU8 } +convert_lossless! { FixedU16, U16, LeEqU16 } +convert_lossless! { FixedU32, U32, LeEqU32 } +convert_lossless! { FixedU64, U64, LeEqU64 } +convert_lossless! { FixedU128, U128, LeEqU128 } + convert_lossy! { FixedU8, FixedI8, U8, LeEqU8 } convert_lossy! { FixedU16, FixedI16, U16, LeEqU16 } convert_lossy! { FixedU32, FixedI32, U32, LeEqU32 } diff --git a/src/lib.rs b/src/lib.rs index a9bcb1f..2163044 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,6 +68,10 @@ The conversions supported cover the following cases. numeric primitives are provided using the [`LossyFrom`] and [`LossyInto`] traits. The source can have more fractional bits than the destination. + * Checked lossless conversions between fixed-point numbers and + numeric primitives are provided using the [`LosslessTryFrom`] and + [`LosslessTryInto`] traits. The source cannot have more fractional + bits than the destination. * Checked conversions between fixed-point numbers and numeric primitives are provided using the [`FromFixed`] and [`ToFixed`] traits, or using the [`from_num`] and [`to_num`] methods and @@ -218,6 +222,8 @@ additional terms or conditions. [`I4F12`]: types/type.I4F12.html [`I4F4`]: types/type.I4F4.html [`Into`]: https://doc.rust-lang.org/nightly/core/convert/trait.Into.html +[`LosslessTryFrom`]: traits/trait.LosslessTryFrom.html +[`LosslessTryInto`]: traits/trait.LosslessTryInto.html [`LossyFrom`]: traits/trait.LossyFrom.html [`LossyInto`]: traits/trait.LossyInto.html [`LowerHex`]: https://doc.rust-lang.org/nightly/core/fmt/trait.LowerHex.html @@ -303,7 +309,9 @@ use core::{ /// /// [prelude]: https://doc.rust-lang.org/nightly/std/prelude/index.html pub mod prelude { - pub use crate::traits::{FromFixed, LossyFrom, LossyInto, ToFixed}; + pub use crate::traits::{ + FromFixed, LosslessTryFrom, LosslessTryInto, LossyFrom, LossyInto, ToFixed, + }; } #[macro_use] diff --git a/src/traits.rs b/src/traits.rs index df0182e..99ff565 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1209,6 +1209,70 @@ pub trait FixedUnsigned: Fixed { fn checked_next_power_of_two(self) -> Option; } +/// This trait provides lossless conversions that might be fallible. +/// +/// This trait is implemented for conversions between integer +/// primitives, floating-point primitives and fixed-point numbers. +/// +/// # Examples +/// +/// ```rust +/// use fixed::traits::LosslessTryFrom; +/// use fixed::types::{I4F12, I24F8}; +/// // original is 0x000001.23, lossless is 0x1.230 +/// let original = I24F8::from_bits(0x0000_0123); +/// let lossless = I4F12::lossless_try_from(original); +/// assert_eq!(lossless, Some(I4F12::from_bits(0x1230))); +/// // too_large is 0x000012.34, 0x12.340 does not fit in I4F12 +/// let too_large = I24F8::from_bits(0x0000_1234); +/// let overflow = I4F12::lossless_try_from(too_large); +/// assert_eq!(overflow, None); +/// ``` +pub trait LosslessTryFrom: Sized { + /// Performs the conversion. + fn lossless_try_from(src: Src) -> Option; +} + +/// This trait provides lossless conversions that might be fallible. +/// This is the reciprocal of [`LosslessTryFrom`]. +/// +/// Usually [`LosslessTryFrom`] should be implemented instead of this +/// trait; there is a blanket implementation which provides this trait +/// when [`LosslessTryFrom`] is implemented (similar to [`Into`] and +/// [`From`]). +/// +/// # Examples +/// +/// ```rust +/// use fixed::traits::LosslessTryInto; +/// use fixed::types::{I4F12, I24F8}; +/// // original is 0x000001.23, lossless is 0x1.230 +/// let original = I24F8::from_bits(0x0000_0123); +/// let lossless: Option = original.lossless_try_into(); +/// assert_eq!(lossless, Some(I4F12::from_bits(0x1230))); +/// // too_large is 0x000012.34, 0x12.340 does not fit in I4F12 +/// let too_large = I24F8::from_bits(0x0000_1234); +/// let overflow: Option = too_large.lossless_try_into(); +/// assert_eq!(overflow, None); +/// ``` +/// +/// [`From`]: https://doc.rust-lang.org/nightly/core/convert/trait.From.html +/// [`Into`]: https://doc.rust-lang.org/nightly/core/convert/trait.Into.html +/// [`LosslessTryFrom`]: trait.LosslessTryFrom.html +pub trait LosslessTryInto { + /// Performs the conversion. + fn lossless_try_into(self) -> Option; +} + +impl LosslessTryInto for Src +where + Dst: LosslessTryFrom, +{ + fn lossless_try_into(self) -> Option { + Dst::lossless_try_from(self) + } +} + /// This trait provides infallible conversions that might be lossy. /// /// This trait is implemented for conversions between integer @@ -1218,11 +1282,11 @@ pub trait FixedUnsigned: Fixed { /// /// ```rust /// use fixed::traits::LossyFrom; -/// use fixed::types::{I12F4, I4F60}; -/// // original is 0x1.234 -/// let original = I4F60::from_bits(0x1234i64 << (60 - 12)); +/// use fixed::types::{I12F4, I8F24}; +/// // original is 0x12.345678, lossy is 0x012.3 +/// let original = I8F24::from_bits(0x1234_5678); /// let lossy = I12F4::lossy_from(original); -/// assert_eq!(lossy, I12F4::from_bits(0x0012)); +/// assert_eq!(lossy, I12F4::from_bits(0x0123)); /// ``` pub trait LossyFrom { /// Performs the conversion. @@ -1240,11 +1304,11 @@ pub trait LossyFrom { /// /// ```rust /// use fixed::traits::LossyInto; -/// use fixed::types::{I12F4, I4F12}; -/// // original is 0x1.234 -/// let original = I4F12::from_bits(0x1234); +/// use fixed::types::{I12F4, I8F24}; +/// // original is 0x12.345678, lossy is 0x012.3 +/// let original = I8F24::from_bits(0x1234_5678); /// let lossy: I12F4 = original.lossy_into(); -/// assert_eq!(lossy, I12F4::from_bits(0x0012)); +/// assert_eq!(lossy, I12F4::from_bits(0x0123)); /// ``` /// /// [`From`]: https://doc.rust-lang.org/nightly/core/convert/trait.From.html