diff --git a/crates/fayalite/src/int.rs b/crates/fayalite/src/int.rs index d6f4cfa..4f41e53 100644 --- a/crates/fayalite/src/int.rs +++ b/crates/fayalite/src/int.rs @@ -18,7 +18,10 @@ use std::{ fmt, hash::Hash, marker::PhantomData, - ops::{Add, BitAnd, BitOr, BitXor, Bound, Mul, Neg, Not, Range, RangeBounds, Shl, Shr, Sub}, + ops::{ + Add, BitAnd, BitOr, BitXor, Bound, Mul, Neg, Not, Range, RangeBounds, RangeInclusive, Shl, + Shr, Sub, + }, }; #[derive(Clone, Eq, PartialEq, Hash, Default)] @@ -1003,6 +1006,66 @@ impl DynUIntType { pub fn mask(self) -> BigUint { self.modulo() - 1u8 } + /// gets the smallest `UInt` that fits `v` losslessly + pub fn for_value(v: impl Into) -> Self { + let v: BigUint = v.into(); + Self::new(v.bits().try_into().expect("too big")) + } + /// gets the smallest `UInt` that fits `r` losslessly, panics if `r` is empty + #[track_caller] + pub fn range(r: Range>) -> Self { + let start: BigUint = r.start.into(); + let end: BigUint = r.end.into(); + assert!(!end.is_zero(), "empty range"); + Self::range_inclusive(start..=(end - 1u8)) + } + /// gets the smallest `UInt` that fits `r` losslessly, panics if `r` is empty + #[track_caller] + pub fn range_inclusive(r: RangeInclusive>) -> Self { + let (start, end) = r.into_inner(); + let start: BigUint = start.into(); + let end: BigUint = end.into(); + assert!(start <= end, "empty range"); + // no need to check `start`` since it's no larger than `end` + // so must not take more bits than `end` + Self::for_value(end) + } +} + +impl DynSIntType { + /// gets the smallest `SInt` that fits `v` losslessly + pub fn for_value(v: impl Into) -> Self { + let v: BigInt = v.into(); + Self::new( + match v.sign() { + Sign::Minus => { + // account for sign bit and for the minimum value of an `SInt` + // being the negative of the maximum value minus one. + v.not().bits().checked_add(1).expect("too big") + } + Sign::NoSign => 0, + Sign::Plus => v.bits(), + } + .try_into() + .expect("too big"), + ) + } + /// gets the smallest `SInt` that fits `r` losslessly, panics if `r` is empty + #[track_caller] + pub fn range(r: Range>) -> Self { + let start: BigInt = r.start.into(); + let end: BigInt = r.end.into(); + Self::range_inclusive(start..=(end - 1)) + } + /// gets the smallest `SInt` that fits `r` losslessly, panics if `r` is empty + #[track_caller] + pub fn range_inclusive(r: RangeInclusive>) -> Self { + let (start, end) = r.into_inner(); + let start: BigInt = start.into(); + let end: BigInt = end.into(); + assert!(start <= end, "empty range"); + Self::new(Self::for_value(start).width.max(Self::for_value(end).width)) + } } impl sealed::Sealed for DynIntType {}