rust-num / num-traits

Numeric traits for generic mathematics in Rust
Apache License 2.0
731 stars 135 forks source link

Add `trait UnsignedAbs` #315

Open emilk opened 8 months ago

emilk commented 8 months ago

All signed rust integer types has an unsgined_abs function, e.g. (-7_i32).unsigned_abs() == 7_u32, which is there to correctly handle i32::MIN (which cannot be negated as a i32).

I suggest we ~either add this to PrimInt (returning identify for unsigned integers), or~ add a new UnsignedAbs trait for this.

I'd be happy to make a PR if this sounds good.

cuviper commented 8 months ago

I think it will have to be a separate trait, especially because it needs an associated type for the unsigned return.

I fear that associated type will also make this a bear to actually use in generic code though. Have you experimented with a local trait doing this?

emilk commented 8 months ago

You are right that a separate trait makes most sense.

I tried this in a real world example, and it works great:

pub trait UnsignedAbs {
    /// An unsigned type which is large enough to hold the absolute value of `Self`.
    type Unsigned;

    /// Computes the absolute value of `self` without any wrapping or panicking.
    fn unsigned_abs(self) -> Self::Unsigned;
}

impl UnsignedAbs for i64 {
    type Unsigned = u64;
    fn unsigned_abs(self) -> Self::Unsigned {
        self.unsigned_abs()
    }
}

impl UnsignedAbs for isize {
    type Unsigned = usize;
    fn unsigned_abs(self) -> Self::Unsigned {
        self.unsigned_abs()
    }
}

/// Pretty format a signed number by using thousands separators for readability.
pub fn format_int<Int>(number: Int) -> String
where
    Int: Display + PartialOrd + num_traits::Zero + UnsignedAbs,
    Int::Unsigned: Display + num_traits::Unsigned,
{
    if number < Int::zero() {
        format!("-{}", format_uint(number.unsigned_abs()))
    } else {
        add_thousands_separators(&number.to_string())
    }
}

/// Pretty format an unsigned integer by using thousands separators for readability
#[allow(clippy::needless_pass_by_value)]
pub fn format_uint<Uint>(number: Uint) -> String
where
    Uint: Display + num_traits::Unsigned,
{
    add_thousands_separators(&number.to_string())
}

/// Add thousands separators to a number, every three steps,
/// counting from the last character.
fn add_thousands_separators(number: &str) -> String {
    …
}
cuviper commented 7 months ago

I think it should probably take &self for consistency, especially compared to Signed::abs.