Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

integers shouldn't implement num::Round #4789

Closed
brson opened this issue Feb 4, 2013 · 21 comments
Closed

integers shouldn't implement num::Round #4789

brson opened this issue Feb 4, 2013 · 21 comments

Comments

@brson
Copy link
Contributor

brson commented Feb 4, 2013

They don't have a sensible implementation.

@brendanzab
Copy link
Member

Related: #4788

@brendanzab
Copy link
Member

In my current implementation on my own fork, I've put these in a Float trait where they seem to make more sense. (see my follow-up comment) If genericism and future-proofing is a concern, we could always shift these into a separate Round trait ie:

pub trait Round {
    pure fn ceil(&self) -> Self;
    pure fn floor(&self) -> Self;
    pure fn round(&self) -> Self;
    pure fn trunc(&self) -> Self;
}

But I definitely feel that the current implementation is over-engineered, and rounding functions should not be impled on integer types.

@brendanzab
Copy link
Member

Here is the current implementation:

libcore/num/num.rs

pub trait Round {
    pure fn round(&self, mode: RoundMode) -> Self;

    pure fn floor(&self) -> Self;
    pure fn ceil(&self) -> Self;
    pure fn fract(&self) -> Self;
}

pub enum RoundMode {
    RoundDown,
    RoundUp,
    RoundToZero,
    RoundFromZero
}

libcore/num/int-template.rs

impl T: num::Round {
    #[inline(always)]
    pure fn round(&self, _: num::RoundMode) -> T { *self }

    #[inline(always)]
    pure fn floor(&self) -> T { *self }
    #[inline(always)]
    pure fn ceil(&self) -> T { *self }
    #[inline(always)]
    pure fn fract(&self) -> T { 0 }
}

libcore/num/float.rs

impl float: num::Round {
    #[inline(always)]
    pure fn round(&self, mode: num::RoundMode) -> float {
        match mode {
            num::RoundDown
                => f64::floor(*self as f64) as float,
            num::RoundUp
                => f64::ceil(*self as f64) as float,
            num::RoundToZero if is_negative(*self)
                => f64::ceil(*self as f64) as float,
            num::RoundToZero
                => f64::floor(*self as f64) as float,
            num::RoundFromZero if is_negative(*self)
                => f64::floor(*self as f64) as float,
            num::RoundFromZero
                => f64::ceil(*self as f64) as float
        }
    }

    #[inline(always)]
    pure fn floor(&self) -> float { f64::floor(*self as f64) as float}
    #[inline(always)]
    pure fn ceil(&self) -> float { f64::ceil(*self as f64) as float}
    #[inline(always)]
    pure fn fract(&self) -> float {
        if is_negative(*self) {
            (*self) - (f64::ceil(*self as f64) as float)
        } else {
            (*self) - (f64::floor(*self as f64) as float)
        }
    }
}

@brendanzab
Copy link
Member

To follow up, I've moved the rounding functions into a separate trait. I think that's a better design actually. I haven't replicated pure fn round(&self, mode: RoundMode) -> Self – I think that's abit over-engineered, and doesn't really have a place in the core lib.

@kud1ing
Copy link

kud1ing commented Feb 5, 2013

I feel a bit uneasy about a Round trait without any other constraints.
People could implement Round on e.g. strings/chars.

Haskell declares round in typeclass RealFrac, which is an extension of typeclasses Real + Fractional:
http://www.bucephalus.org/text/Haskell98numbers/Haskell98numbers.png

I think having a Fractional trait would not be a bad idea.

@brendanzab
Copy link
Member

Ooh, that's very handy. Thank you!

@brendanzab
Copy link
Member

@kud1ing: how about this?

trait Eq {
    fn eq(&self, other: &Self) -> bool;
    fn ne(&self, other: &Self) -> bool;
}

trait Ord {
    fn lt(&self, other: &Self) -> bool;
    fn le(&self, other: &Self) -> bool;
    fn ge(&self, other: &Self) -> bool;
    fn gt(&self, other: &Self) -> bool;

    // These will be handled using default implementations, but can be
    // specialised, for example using cmath functions
    fn min(&self, other: &Self) -> Self;
    fn max(&self, other: &Self) -> Self;
    fn clamp(&self, mn: &Self, mx: &Self) -> Self;
}

trait Num: Eq
           Neg<Self>
           Add<Self,Self>
           Sub<Self,Self>
           Mul<Self,Self> {
    // These will be handled using default implementations
    fn abs(&self) -> Self;
    fn signum(&self) -> Self;
}

trait Real: Ord Num {
}

trait Floating: Real
                Div<Self,Self>
                Mod<Self,Self> {
    static pure fn NaN() -> Self;
    static pure fn infinity() -> Self;
    static pure fn neg_infinity() -> Self;

    pure fn is_NaN(&self) -> bool;
    pure fn is_infinite(&self) -> bool;
    pure fn is_finite(&self) -> bool;

    static pure fn pi() -> Self;
    static pure fn two_pi() -> Self;
    static pure fn frac_pi_2() -> Self;
    static pure fn frac_pi_4() -> Self;
    static pure fn frac_1_pi() -> Self;
    static pure fn frac_2_pi() -> Self;
    static pure fn frac_2_sqrtpi() -> Self;
    static pure fn sqrt2() -> Self;
    static pure fn frac_1_sqrt2() -> Self;
    static pure fn e() -> Self;
    static pure fn log2_e() -> Self;
    static pure fn log10_e() -> Self;
    static pure fn ln_2() -> Self;
    static pure fn ln_10() -> Self;

    pure fn abs_sub(&self, b: Self) -> Self;
    pure fn mul_add(&self, b: Self, c: Self) -> Self;
    pure fn modf(&self) -> (Self, Self);
    pure fn nextafter(&self, y: Self) -> Self;

    pure fn recip(&self) -> Self;

    pure fn ceil(&self) -> Self;
    pure fn floor(&self) -> Self;
    pure fn round(&self) -> Self;
    pure fn trunc(&self) -> Self;

    pure fn pow(&self, e: Self) -> Self;
    pure fn sqrt(&self) -> Self;
    pure fn invsqrt(&self) -> Self;
    pure fn cbrt(&self) -> Self;
    pure fn exp(&self) -> Self;
    pure fn expm1(&self) -> Self;
    pure fn exp2(&self) -> Self;
    pure fn frexp<I:Int>(&self) -> (Self, I);
    pure fn ldexp<I:Int>(&self, n: I) -> Self;
    pure fn ldexp_radix<I:Int>(&self, i: I) -> Self;
    pure fn ln(&self) -> Self;
    pure fn log_radix(&self) -> Self;
    pure fn ln1p(&self) -> Self;
    pure fn log2(&self) -> Self;
    pure fn log10(&self) -> Self;
    pure fn logarithm(&self, b: Self) -> Self;
    pure fn ilog_radix<I:Int>(&self) -> I;
    pure fn erf(&self) -> Self;
    pure fn erfc(&self) -> Self;
    pure fn lgamma<I:Int>(&self) -> (Self, I);
    pure fn tgamma(&self) -> Self;

    pure fn sin(&self) -> Self;
    pure fn cos(&self) -> Self;
    pure fn tan(&self) -> Self;
    pure fn sinh(&self) -> Self;
    pure fn cosh(&self) -> Self;
    pure fn tanh(&self) -> Self;
    pure fn asin(&self) -> Self;
    pure fn acos(&self) -> Self;
    pure fn atan(&self) -> Self;
    pure fn atan2(&self, b: Self) -> Self;
    pure fn hypot(&self, b: Self) -> Self;

    pure fn j0(&self) -> Self;
    pure fn j1(&self) -> Self;
    pure fn jn<I:Int>(&self, n: I) -> Self;
    pure fn y0(&self) -> Self;
    pure fn y1(&self) -> Self;
    pure fn yn<I:Int>(&self, n: I) -> Self;
}

trait Integer: Real
               SigNum
               Div<Self,Self>
               Mod<Self,Self> {
    fn div_mod(&self, other: &Self) -> (Self,Self);

    fn even(&self) -> bool;
    fn odd(&self) -> bool;
}

trait Bounded {
    static pure fn min_bound() -> Self;
    static pure fn max_bound() -> Self;
}

trait Int: Integer
           Bounded
           Not<Self>
           BitOr<Self,Self>
           BitXor<Self,Self>
           Shl<Self,Self>
           Shr<Self,Self> {
}

@brendanzab
Copy link
Member

@pcwalton ping^

@pcwalton
Copy link
Contributor

pcwalton commented Feb 5, 2013

Sounds good to me… I don't have much of an opinion here, really.

@Kimundi
Copy link
Member

Kimundi commented Feb 5, 2013

As the one who is responsible for this trait, I fully agree that implementing it on integers is not a good solution, but together with the generic NaN/inf functions (#4788) it was necessary for the str conversion functions in num to work on all numbers. (Which in itself might be a bad idea, I don't know.)

However, I think that at least having the Roundmode is worthwhile (and, again, necessary for the conversions functions to work), as it really just allows to treat rounding in a symmetric way in regards to the sign.
floor and ceil are, although historical, really just special cases for 'round towards +inf' and 'round towards -inf'. Comparison:

  • RoundUp - round towards +inf, absolute value of two numbers only differing in sign could be different.
  • RoundDown - round towards -inf, absolute value of two numbers only differing in sign could be different.
  • RoundToZero - round towards 0, absolute value of two numbers only differing in sign is identical.
  • RoundFromZero - round towards -inf for negative values, and +inf for positive values, absolute value of two numbers only differing in sign is identical.

It's really just about considering the sign.

@Kimundi
Copy link
Member

Kimundi commented Feb 6, 2013

Also, for the Bounded trait, maybe it might make sense to have a more general 'Limits' trait where upperLimit and lowerLimit would be defined as one of:

  • NoLimit - For things like bigints that can grow infinite.
  • WeakLimit - Highest exact value on numbers that handle infinity in a special way. For example, the highest exact value of an f32 is ~3.4×10^38, but the highest possible but not exact value is inf.
  • HardLimit - Highest exact value on numbers that don't handle infinity in a special way, like ints.

@auroranockert
Copy link
Contributor

@Kimundi While I don't think that having a RoundingMode argument really makes sense when compared to just having multiple methods, the most important problem here is that you've missed the IEEE754 default rounding mode, round-to-even. (Maybe something like this? https://gist.github.com/JensNockert/4719287/raw/65c32ad7eb64e67b55b107e5afb4dfe6e6f6dac6/rounding.rs)

When it comes to a bounded trait, we could just check http://www.cplusplus.com/reference/limits/numeric_limits/ for example, I assume they have been thinking and arguing about this for a very long while and it seems quite sane.

@kud1ing
Copy link

kud1ing commented Feb 6, 2013

@bjz: Nice. I am not sure, whether separate Neg, Add, Sub, Mul are necessary. Also not sure about Real.

Maybe we should create a Wiki page for this, explaining what traits we need and what for.
Could also serve as big-picture documentation, when stuff is implemented.

@kud1ing
Copy link

kud1ing commented Feb 6, 2013

ping @erickt (added Zero and One)

@auroranockert
Copy link
Contributor

@kud1ing: I started doing some thinking on this yesterday, just a sketch, not thought through https://gist.github.com/JensNockert/4719287 and there are loads of refactoring opportunities.

But separate Neg, Add, Sub, Mul are necessary, not for numbers, but for other types. String-like types could have an add and mul, but no neg/sub for example. Arrays could have Add, Sub and Mul without Neg. Sets could have Add, Neg and Sub without Mul. The list of silly usecases that we might never think of is endless, pairing the operators now wouldn't really make sense.

I proposed above instead traits that include them in pairs for types that implement them in a reasonably mathematical way, called Additive / Multiplicative, to reduce typing.

@kud1ing
Copy link

kud1ing commented Feb 6, 2013

@JensNockert; nice

@brendanzab
Copy link
Member

Looks like a good improvement to my effort @JensNockert. :)

Now we need to figure out how to fit in FromStr/ToStr.

@brendanzab
Copy link
Member

A word of warning, we'll need #4183 to be fixed before we can inherit the operator traits.

@auroranockert
Copy link
Contributor

I created a new (meta?) bug #4819 for the numeric traits so it can be found easier, since we kind of expanded the scope of this one by huge amounts.

@Kimundi
Copy link
Member

Kimundi commented Feb 19, 2013

This can be closed now.

@catamorphism
Copy link
Contributor

Fixed in bd93a36

brendanzab added a commit to brendanzab/rust that referenced this issue Apr 29, 2013
After discussions on IRC and rust-lang#4789, we have decided to revert this change. This is due to the traits expressing different ideas and because hyperbolic functions are not trivially implementable from exponential functions for floating-point types.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants