Closed auroranockert closed 10 years ago
@JensNockert Yes, the interpretation of an integer significand would indeed end up being value / 2^(significant bits); or as some textbooks put it: "just reinterpret the bits of the int as if the decimal point is on the left end instead of the right end."
My thinking was centered around expressing floating point operations in terms of fixed point ones; i.e. if we were living back in the 60's and wanted to implement software floating point in terms of integer arithmetic. (Talk about academic examples...) Given that scenario, getting an integral value back as the significand seemed natural.
But I do not have a real motivating example; I have not done floating point work (in C or otherwise) beyond reading Seminumerical Algorithms (and some support work years ago for Larceny's numerics).
So I am not voting either way, I was just expressing my surprise at an "encode" operation of type (T, int) -> T
(as I think I said above, I intuitively expected some more primitive component type for the input), and was wondering if the chosen interface was an artifact of our lack of Associated Items. Maybe my confusion is an artifact of the chosen names.
@pnkfelix Perhaps the names are wrong.
We need to figure out what to do about the Quot/Rem operator trait naming, ideally before stage0
is updated again. My suggestion:
pub trait Div<RHS,Result> {
fn div(&self, rhs: &RHS) -> Result;
}
pub trait Rem<RHS,Result> {
fn rem(&self, rhs: &RHS) -> Result;
}
pub trait Integer: Num
+ Orderable
+ Div<Self,Self>
+ Rem<Self,Self> {
fn div_rem(&self, other: &Self) -> (Self,Self);
fn div_floor(&self, other: &Self) -> Self;
fn mod_floor(&self, other: &Self) -> Self;
fn div_mod_floor(&self, other: &Self) -> (Self,Self);
fn div_euclid(&self, other: &Self) -> Self;
fn mod_euclid(&self, other: &Self) -> Self;
fn div_mod_euclid(&self, other: &Self) -> (Self,Self);
//...
}
See Division and Modulus for Computer Scientists for a good explanation of the differences between the operations.
Truncated division and modulo would be the defaults for the /
and %
operators. We could even rename Rem back to Modulo for even greater consistency with the other functions (it's a shame the mod
keyword is taken), but I think @thestinger has an issue with that.
Another option (suggested by @dobkeratops in IRC):
pub trait Div<RHS,Result> {
fn div(&self, rhs: &RHS) -> Result;
}
pub trait Rem<RHS,Result> {
fn rem(&self, rhs: &RHS) -> Result;
}
pub trait Integer: Num
+ Orderable
+ Div<Self,Self>
+ Rem<Self,Self> {
fn div_trunc(&self, other: &Self) -> Self; // same as `div`
fn mod_trunc(&self, other: &Self) -> Self; // same as `rem`
fn div_mod_trunc(&self, other: &Self) -> (Self,Self);
fn div_floor(&self, other: &Self) -> Self;
fn mod_floor(&self, other: &Self) -> Self;
fn div_mod_floor(&self, other: &Self) -> (Self,Self);
fn div_euclid(&self, other: &Self) -> Self;
fn mod_euclid(&self, other: &Self) -> Self;
fn div_mod_euclid(&self, other: &Self) -> (Self,Self);
//...
}
The *_trunc
methods would be added for consistency with the other methods.
If the div
method is clearly defined as truncating for integer division, I don't think including duplicate methods is a good idea. It will give the impression that div
is something different than div_trunc
.
I'd recommend having just F-division (div_floor) and T-division (div_trunc, or div). The others (rounding, Euclidian, ceiling) are pretty rare. They're useful, but should probably live in a library somewhere instead of as part of the Integer trait.
I thought % was going to map to remainder.
@thestinger Good point. I will (and already have) documented the behaviour of the operators in int-template.rs
and uint-template.rs
.
@graydon Still hasn't changed. Still naming issues. Groundhog day. Out the front of the bikeshed.
@Erik-S yup, having euclidean complicates the api too. Ok, this is how it stands now:
pub trait Div<RHS,Result> {
fn div(&self, rhs: &RHS) -> Result;
}
pub trait Rem<RHS,Result> {
fn rem(&self, rhs: &RHS) -> Result;
}
pub trait Integer: Num
+ Orderable
+ Div<Self,Self>
+ Rem<Self,Self> {
fn div_rem(&self, other: &Self) -> (Self,Self);
fn div_floor(&self, other: &Self) -> Self;
fn mod_floor(&self, other: &Self) -> Self;
fn div_mod_floor(&self, other: &Self) -> (Self,Self);
}
Is that ok?
@bjz Looks fine to me. Having both versions called "div" but then "rem" vs "mod" is a little ugly, but probably the best way to do it
I'll reiterate that I'd like the floor versions available as operators, too, if at all possible. (divf and modf, maybe? or even /f and %f?)
Having both version called "div" but then rem vs mod is a little ugly, but probably the best way to do it
@Erik-S that's what I find ugly. Shame :(
What about rem_floor
and div_rem_floor
? :P
@bjz The only decent way around it that I see is to keep T-division named "quot". It might be a little more consistent, but I'm not sure if it's any better.
Ok. I'm deciding to make the executive decision to go with Quot
-> Div
. The feeling I'm getting is that we can at least agree on that. I will do the other renamings following my last code proposal, but there's always room to change those in the future.
Are there guidelines for number conversion? For example,
NumCast
instead of IntConvertible
for custom number types? (IntConvertible
is no longer implemented for built-in numbers)@gifnksm I think this needs to be clarified and improved. Haskell has the following conversions:
Num::fromInteger
Fractional::fromRational
RealFrac::realToFrac
Integral::fromIntegral
Ratio::approxRational
NumCast was intended for only primitive types, to 'generify' the as
operator. My current doc-comment says "An interface for casting between machine scalars". Although I see that it's already being used in ways I wasn't intending: see Operator Overloading in Rust.
It would be great if we could overload as
. I currently have an issue #6016 open for it. Then NumCast could be made up of the following:
pub trait NumCast: Cast<u8>
+ Cast<u16>
+ Cast<u32>
+ Cast<u64>
+ Cast<uint>
+ Cast<i8>
+ Cast<i16>
+ Cast<i32>
+ Cast<i64>
+ Cast<int>
+ Cast<f32>
+ Cast<f64>
+ Cast<float>
+ Cast<bool> {}
I'm not sure though. We need another way to do this for other numeric types. And I'll add that 'cast' should imply that no calculation is involved.
I'll also add that I think casting from floats to ints only makes sense for machine scalars. For example, this should not be available for Reals. Reals/Fractionals should implement some kind of round-to-int methods, perhaps.
@bjz: Casting between float and int is always fairly expensive, with some calculation involved. float -> int can be particularly bad, because you may have to change the rounding mode twice - that may flush the floating point pipeline. Personally, I'd be fine with float/int casts requiring a function call, but others may disagree.
On a different topic, is there an easy way to do the cheap "bitwise" float/int cast that just copies the bits from a f32 to/from a u32?
@Erik-S do you mean let x: u32 = unsafe { cast::transmute(1.23456f32) };
for the last one?
@Erik-S ahh I didn't know that. The functions are inlined, but I guess you mean 3.0f as int
would cause a calculation?
@huonw: Yes, that's it. Thanks! @bjz: Yes. Assuming it's not optimized out, it will result in 1 to 3 floating point ops.
For that matter, even the direct bitwise convert that huonw posted isn't quite free: it'll have to copy the value from a floating-point register to an integer register, which can take several clock cycles.
I'm thinking it would be good to have a lerp function somewhere in here. If you guys agree, where do you guys think it would fit trait-wise? Here's an interesting article: Linear interpolation past, present and future
i think it would be great to have that lerp function, its SO common. Where it fits in traits i dont know. Would you consider adding these related functions aswell:- invlerp(a,b,t) = (t-a)/(b-a) and ' lerpBetween(y0,y1, x0,x1,x)=lerp(y0,y1,invlerp(x0,x1,x))=y0+(y1-y0)*(x-x0)/(x1-x0)
"lerpBetween" is a name i just spuriously invented, its a more general form of lerp some people use; i've always had C++ overloading . the latter can sometimes be appropriate, eg if passing integers in the fraction type is hidden). will benefit from default implementations..
r.e. traits, perhaps lerp makes sense as soon as you have + - . but invlerp needs division aswell. would it be neater to have them all alongside eachother? would you want lerp for some fixed point cases (SIMD?) where you might have ,+,- but no divide. invlerp might take ints and return a fraction..
also to support vectors, could you parameterize it such that the 't' is a different type. so you can lerp<Vec3,float>(v0,v1,t:float) with the same trait? (again what to do for invlerp of vector..)
It should be a generic interpolation trait imho, with a lot more than linear.
On H.25/05/03, at 11:02, dobkeratops notifications@github.com wrote:
i think it would be great to have that lerp function, its SO common. Where it fits in traits i dont know. could you put it alongside related functions;_ invlerp(a,b,t) = (t-a)/(b-a) and 'lerpBetween(y0,y1, x0,x1,x)=lerp(y0,y1,invlerp(x0,x1,x))=(y1-y0)*(x-x0)/(x1-x0) << the latter can sometimes be appropriate, eg if passing integers in the fraction type is hidden). "lerpBetween" is a name i just spuriously invented, its a more general form of lerp some people use;
perhaps lerp makes sense as soon as you have + - . but invlerp needs division aswell. would it be neater to have them all alongside eachother? would you want lerp for some fixed point cases (SIMD?) where you might have ,+,- but no divide. also for vectors, perhaps parameterize it such that the 't' is a different type. so you can lerp(v0,v1,t:float) with the same trait?
\ Reply to this email directly or view it on GitHub.
also to support vectors, could you parameterize it such that the 't' is a different type. so you can lerp(v0,v1,t:float) with the same trait? (again what to do for invlerp of vector..)
@dobkeratops Do you mean so that you could do something like this? It uses @nikomatsakis' method for overloading method parameters so that you can lerp using a vector or a scalar. Perhaps this might be useful for @sanxiyn's SIMD work: #6214.
@bjz Yes thats exactly what I had in mind. I need to re-read what you've shown to clarify how it works in Rust. I'm familiar with this sort of thing in C++, but you dont have the type constraints there of course. the number of types can explode (3d coords.. texture coordinates.. integer image coordinates.. RGBA values..).. but its great to have the same arithmetic interface work for all. (template<typename T,int N> Vec{ T elem[N]; ...} .. that sort of thing) I also like the existence of the mul_add functions you have there. mul_add and mul_sub (vmaddf,vmsubf) are common instructions i've seen on various ISAs, i think you're referencing those in the effficient lerp implementations )
This came up on #6471: the Integer
trait encompasses many properties, some of which might be worth splitting out, specifically, it makes sense to have a rational polynomial (i.e. ratio of two polynomials) and it'd be nice for std::Ratio
to use the per-type gcd
implementation (rather than using a naive Euclid's algorithm implementation), but the Integer
trait is a little too broad for polynomials.
Or at least, poorly named, since only the *_floor
and is_{even,odd}
methods don't make complete sense (although they do have non-stupid intepretations).
@huonw Sure. Do you or @gifnksm have any proposals?
any opinions on the idea of adding a fn sincos(&self)->(Self,Self) to trait Trigonometric{} - IMO (a) its a nice convinience to have..polar conversion etc - you could implement it as a default in terms of sin,cos once you have that planned functionality ... much neater with Rusts's tuples than it is in C and (b) there might be optimized implementations of it (eg x86 has fsincos, or you could interleave a series expansion).
@dobkeratops Sounds good. As far as I can see, the function is there more for convenience than speed, therefore it would make sense in Trigonometric.
@dobkeratops Do you know a fast was of calculating sin_cos, or should I just implement it naively for now?
@bjz there is the cmath sincos, but it's possibly better to just use the two functions separately and let LLVM optimise it if it sees fit.
(In theory, sincos
is for speed, since there are (probably) optimisations that allow them to be computed faster together.)
@huonw ok, I'm implementing these using (self.sin(), self.cos()) for now. Ideally I'd prefer it if we actually stopped depending on cmath and used our own implementations for the math functions. But LLVM intrinsics are fine.
Looks good.
(I agree about moving away from cmath.)
Plainly one of our most remarkable metabugs; I wonder if it will ever close? Revisiting during triage on 2013-06-05, still seems like it's going strong.
@graydon We still need to look at numeric conversions. I'm also wondering if we should make some of the maths methods associated functions, eg abs
, sin
, cos
, tan
etc... I've also begun work on Interpolate
. Still much to do!
One other thing we will have to consider is breaking free of the shackles of cmath. It would be very nice to implement most of the maths functions in pure Rust (at least, the ones that aren't covered by LLVM intrinsics).
Something that @thestinger brought up as useful for range
-style iterators (and in general, I'm sure): arithmetic traits that return Some
or None
based on whether overflow occured (there are LLVM intrinsics). Possibly something like
trait CheckedAdd { fn checked_add(self, &Self) -> Option<Self>; }
trait CheckedSub { fn checked_sub(self, &Self) -> Option<Self>; }
trait CheckedMul { fn checked_mul(self, &Self) -> Option<Self>; }
(I guess CheckedDiv
that returns None
when dividing by 0 could also fit in there.)
@huonw: division can also overflow by dividing by -1 with a signed integer due to their weird asymmetry
Checked and saturated arithmetic would be cool.
If https://github.com/mozilla/rust/pull/7117 gets merged, I propose that we should remove all num functions that currently allow function notation f(x)
that does not use function notation in math, and only allow those to be called via method notation?
The controversial ones of those would be sqrt and exp I guess? Would it make sense?
I would also like to move the trait RealExt to extra, and/or remove it. The commonly useful function in there is tgamma, which is used mainly for winning factorial benchmarks.
I would also like to move to extra the following methods, because while they are commonly useful, I think 2 * float::pi will be as useful for those people as the correctly rounded constant.
fn exp_m1(&self) -> Self;
fn ln_1p(&self) -> Self;
fn two_pi() -> Self;
fn frac_pi_2() -> Self;
fn frac_pi_3() -> Self;
fn frac_pi_4() -> Self;
fn frac_pi_6() -> Self;
fn frac_pi_8() -> Self;
fn frac_1_pi() -> Self;
fn frac_2_pi() -> Self;
fn frac_2_sqrtpi() -> Self;
fn sqrt2() -> Self;
fn frac_1_sqrt2() -> Self;
fn log2_e() -> Self;
fn log10_e() -> Self;
fn ln_2() -> Self;
fn ln_10() -> Self;
@jensnockert sqrt
, exp
, ln
, abs
, etc.. should all have free-standing versions, seeing as that is the conventional mathematical notation.
I consider the 'conventional' notation for those to be something like √ x, e^x, ln(x) and |x|. I am of course open to changing my mind on this, as it isn't important for me to impose my preference of method-notation on others.
@jensnockert how about using Unicode for the constants!
fn π() -> Self;
fn ½π() -> Self;
fn √2() -> Self;
fn ½√π() -> Self;
/jokes
If we could have associated constants, I would think they would be good alternative names ;)
we should totally have √(x)
:+1:
Seems like the next unary operator for Rust is proposed!
In #4789 discussion was started about redesigning the traits related to numbers and I think the first step is to design and document the traits we think are required and why.
We probably also depend on #4183 and some other bugs for the actual implementation?
(I have an initial sketch/RFC here, based on the work in #4789 etc. https://gist.github.com/JensNockert/4719287)