-
Notifications
You must be signed in to change notification settings - Fork 144
Add RealNum trait for real data types (Float, but without floating-point specific features) #23
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
Conversation
Wait, I just realized this is a breaking change for those that do global imports like |
(last commit was supposed to be named "Remove |
This is a neat trait. I've been toying around with an implementation of fixed-point numbers where this trait would be quite useful. I was wondering if it would be useful to introduce additional subtraits, e.g. How about changing the trait name to |
I like I think subtraits as you mentioned would be quite elegant, but it's difficult to decide where to draw the line (i.e how far do we go ? and how often would generics need e.g Today a possible solution for 1. would be to use I gave a try at splitting functionality into such subtraits below (pseudo-Rust), and now I find the idea to be a bit scary. // Q: How to split all of this ? Do we want to ?
pub trait ????? {
// MinMax
fn min_value() -> Self;
fn min_positive_value() -> Self;
fn max_value() -> Self;
// Sign-related
fn abs(self) -> Self;
fn abs_sub(self, other: Self) -> Self;
fn signum(self) -> Self;
fn is_sign_positive(self) -> bool;
fn is_sign_negative(self) -> bool;
// Simple arithmetic ops
fn mul_add(self, a: Self, b: Self) -> Self;
fn recip(self) -> Self;
// These two are both simple arith. and constants
fn to_degrees(self) -> Self { ... }
fn to_radians(self) -> Self { ... }
// Constants
fn epsilon() -> Self { ... }
// Comparison-related
fn max(self, other: Self) -> Self;
fn min(self, other: Self) -> Self;
}
pub trait Rounding {
fn floor(self) -> Self;
fn ceil(self) -> Self;
fn round(self) -> Self;
fn trunc(self) -> Self;
fn fract(self) -> Self;
}
// That's still a lot to implement. Does it solve our problem ?
// Otherwise, if we split all of this, how many traits would we end up with,
// and what would their names be ?
pub trait Transcendental {
fn powi(self, n: i32) -> Self;
fn powf(self, n: Self) -> Self;
fn sqrt(self) -> Self;
fn exp(self) -> Self;
fn exp2(self) -> Self;
fn ln(self) -> Self;
fn log(self, base: Self) -> Self;
fn log2(self) -> Self;
fn log10(self) -> Self;
fn cbrt(self) -> Self;
fn hypot(self, other: Self) -> Self;
fn sin(self) -> Self;
fn cos(self) -> Self;
fn tan(self) -> Self;
fn asin(self) -> Self;
fn acos(self) -> Self;
fn atan(self) -> Self;
fn atan2(self, other: Self) -> Self;
fn sin_cos(self) -> (Self, Self);
fn exp_m1(self) -> Self;
fn ln_1p(self) -> Self;
fn sinh(self) -> Self;
fn cosh(self) -> Self;
fn tanh(self) -> Self;
fn asinh(self) -> Self;
fn acosh(self) -> Self;
fn atanh(self) -> Self;
}
// Then in any case :
pub trait Real: ????? + Rounding + Transcendental + Num + etc... {} Say my I feel like The EDIT: Let's also not forget about the possible impact on |
BTW, I wonder if, while we're at it, we should make However, such types would still prefer not to be consumed by calls, and all of |
Now that I come to think of it, starting to split things into subtraits leads to a lot of trait proliferation, as you pointed out. Especially if we go with it ad nauseam, we end up with one trait per function. Eventually we'll construct umbrella traits like Is there a consensus as to where the line is drawn at the moment? I see that One effort I could imagine would be to extend the |
I'd prefer to avoid too many traits here. If you can think of a reasonable type that could only implement part of the functionality, then perhaps that does deserve a separate trait, but let's definitely not get down to one trait per method.
Historical, and such idiosyncrasies are part of why this stuff never got stabilized while it was still in the standard library. You could describe most of We definitely need to settle on these questions and things like |
I'm fine with the current situation in this PR. This may be the single opportunity we have to split
About @fabianschuiki any thoughts before this possibly gets merged ? About issues related to |
"Good enough to be useful" seems like a good compromise. Especially because you can make the division into subtraits arbitrarily fine-grained, I agree that erring on the side of less traits is the better choice. You also make good points that if need be, you can always roll your own custom trait; and there's no shame in using I think this PR is good as it is! |
If the goal is minimizing the number of traits I'd rather remove the Num and the NumOps trait. |
IMO it's fine that the trait may require more things than you need, as long as it's not too much burden for the types that might want to implement this trait. You can always write your own local trait if you want something with reduced constraints. |
@porky11 thanks for your input! Please correct me if you think I misunderstood some of your statements.
Actually the goal is really to provide a
Conceptually this could be also true for a bunch of traits other than
Me too! In these case we'll want to replace all Also I've read your issue and one part I find very interesting is how you concern yourself with not just real number types, but most mathematical types such as matrices and quaternions, in which case e.g Truth to be told, implementing "Float items" for matrices, quaternions, etc. is more the exception than the norm, and the problems you mentioned can be solved today with the current situation (it's not perfect, but can reasonably be dealt with). |
I just realized there's one unsolved question before we should merge (if we want to). Should we say that all methods are allowed to panic instead when the type has no meaningful |
Ok I think everything is fine now. |
Just a real type also is a good enough solution for most types. Maybe powi could get it's own trait, because it can be defined over every type My new type would also be able to implement Transcedental, if you would be able to define multiple kinds of output types. Because I want to be able to specialize some types, so that in my case, for example |
FWIW, there's already a @yoanlcq final review note -- do we really want the default implementations for |
Put this way, I've taken a good look and I'm pretty sure we don't. Gonna remove them now for the better. |
OK, let's merge. Thanks everyone, I appreciate the collaboration on this! bors r+ |
23: Add RealNum trait for real data types (Float, but without floating-point specific features) r=cuviper a=yoanlcq This is supposed to fix [#19](#19); I assumed going ahead would be better than bumping the thread. In any case, I understand that it is a quite significant addition and won't mind too much if it doesn't make it. This adds a new `RealNum` trait, along with a universal impl `impl<T: Float> RealNum for T { ... }`. Therefore, this shouldn't be a breaking change, except in places where both traits are imported (which obviously only happened in a few places in this crate). The intent is that generic code may prefer to use `RealNum` instead of `Float` when floating-point isn't a requirement. In the future (next major version ?), I guess `Float` could be made to only provide floating-point-specific features on top of `RealNum`. Most of the code+doc was copy-pasted from `Float`, but the doc comments should be up-to-date with the situation; `Float` only makes an appearance when talking about NaN and infinity. Issues I've seen : - `RealNum` might not be the name we want; - I've mentioned that `sqrt()` is allowed to panic if the input is negative and has no meaningful NaN representation; - Should we do that too for e.g `log()` ? Like `sqrt()`, it's supposed to return Nan when `x < 0`. Thanks for your time. :)
Build succeeded |
This is supposed to fix #19; I assumed going ahead would be better than bumping the thread.
In any case, I understand that it is a quite significant addition and won't mind too much if it doesn't make it.
This adds a new
RealNum
trait, along with a universal implimpl<T: Float> RealNum for T { ... }
.Therefore, this shouldn't be a breaking change, except in places where both traits are imported (which obviously only happened in a few places in this crate).
The intent is that generic code may prefer to use
RealNum
instead ofFloat
when floating-point isn't a requirement. In the future (next major version ?), I guessFloat
could be made to only provide floating-point-specific features on top ofRealNum
.Most of the code+doc was copy-pasted from
Float
, but the doc comments should be up-to-date with the situation;Float
only makes an appearance when talking about NaN and infinity.Issues I've seen :
RealNum
might not be the name we want;sqrt()
is allowed to panic if the input is negative and has no meaningful NaN representation;log()
? Likesqrt()
, it's supposed to return Nan whenx < 0
.Thanks for your time. :)