-
Notifications
You must be signed in to change notification settings - Fork 13.3k
Linear interpolation (lerp) implementation #71016
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
r? @cramertj (rust_highfive has picked a reviewer for you, use r? to override) |
The job Click to expand the log.
I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact |
2038ab8
to
9983502
Compare
The job Click to expand the log.
I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact |
Enable feature flag for the use of it
9983502
to
2ee6d9a
Compare
The I've used the implementation from http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0811r2.html#linear-interpolation elsewhere, but this has the drawback that it is quite branch heavy. |
I find it hard to believe that the C++ version would see much use, given that it's much slower than the simpler alternatives and monotonicity does not seem useful for the most common uses of I'd like to hear about applications where monotonicity matters, but regardless of whether a monotonic lerp is useful, I think it's valuable to have the fast and numerically stable I'm already worried that the proposed implementation might be eschewed by performance-minded users because it does not use FMA operations where available (which, to be fair, is currently difficult to do in libstd). |
bcfecb4
to
d255996
Compare
I agree that there are probably a limited number of stiuations that require strict monotinicity. However, there are undoubtedly some that do (I have seen a case where I think it mattered, and chaging to a monotonic implementation seemed to help fix the issue I was seeing). Given that is the case, it seems odd for the standard library to include this implementation of I would expect a game engine to use the |
You could argue that the standard library should not include any |
The standard library often doesn't have the "best" implementation of a particular problem. There's usually always some trade-off. That being said, the guarantee should probably be mentioned in the documentation so that people are aware of the trade-offs being made. |
Maybe my wording wasn't very clear - by best I mean an implementation that is both correct and as fast as possible. Elsewhere, the standard library seems to favour correctness over speed (e.g. HashMap using SipHash by default). It would be odd for it not to do so here. If we have a slower but correct implementation here, it is very easy for someone to profile their software and discover that this implementation is causing a slowdown. The converse, discovering that |
For reference here is the implementation used in LLVM's libcxx. It is very similar to the one from P0811R3. Whatever implementation is decided on it ought to pass the same tests as libcxx. |
Reality is a lot more complicated than a "correctness over speed" slogan suggests. Just one example thematically related to this PR: Furthermore, your arguments are based on a relatively arbitrary and contestable definition of correctness. For example, why does it matter if results are monotonic in Finally, once again: if the end result is that libstd provides a function that most prospective users avoid over performance concerns, then little of value is gained. So in my view, your comments at most provide reasons to not provide |
Let me reassign this to T-libs to make the decision here. |
I feel that if we do decide to include I have concerns regarding the argument order however: |
Would it make sense to have |
r? @Amanieu |
@cramertj Oh sorry I forgot to update you all. This is not ready for review yet, I need to refactor a couple of things. This version won't compile to the expected intrinsic in every rust project. So will use suboptimal non-fma ops. I will ping again. Thanks :) |
It would make sense to me for |
#![feature(clamp)] | ||
#![feature(lerp)] | ||
#![feature(concat_idents)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -912,6 +912,30 @@ impl f32 { | |||
} | |||
x | |||
} | |||
|
|||
/// Computes `self + step(upper - pol)` the linear interpolation between |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what step
refers to but this description doesn't look like it matches the implementation. In particular pol
is dimensionless and upper
is not so upper - pol
is not an expression that would make sense.
@vertexclique any updates? |
☔ The latest upstream changes (presumably #73265) made this pull request unmergeable. Please resolve the merge conflicts. |
Ok, I have no disk space left for compiling the compiler. I will carry on when I have a good machine at handy. |
Linear interpolation rust-lang#71016 is a previous attempt at implementation that was closed by the author. I decided to reuse the feature request issue (rust-lang#71015) as a tracking issue. A member of the rust-lang org will have to edit the original post to be formatted correctly as I am not the issue's original author. The common name `lerp` is used because it is the term used by most code in a wide variety of contexts; it also happens to be the recently chosen name of the function that was added to C++20. To ensure symmetry as a method, this breaks the usual ordering of the method from `lerp(a, b, t)` to `t.lerp(a, b)`. This makes the most sense to me personally, and there will definitely be discussion before stabilisation anyway. Implementing lerp "correctly" is very dififcult even though it's a very common building-block used in all sorts of applications. A good prior reading is [this proposal](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0811r2.html#linear-interpolation) for the C++20 lerp which talks about the various guarantees, which I've simplified down to: 1. Exactness: `(0.0).lerp(start, end) == start` and `(1.0).lerp(start, end) == end` 2. Consistency: `anything.lerp(x, x) == x` 3. Monotonicity: once you go up don't go down Fun story: the version provided in that proposal, from what I understand, isn't actually monotonic. I messed around with a *lot* of different lerp implementations because I kind of got a bit obsessed and I ultimately landed on one that uses the fused `mul_add` instruction. Floating-point lerp lore is hard to come by, so, just trust me when I say that this ticks all the boxes. I'm only 90% certain that it's monotonic, but I'm sure that people who care deeply about this will be there to discuss before stabilisation. The main reason for using `mul_add` is that, in general, it ticks more boxes with fewer branches to be "correct." Although it will be slower on architectures without the fused `mul_add`, that's becoming more and more rare and I have a feeling that most people who will find themselves needing `lerp` will also have an efficient `mul_add` instruction available.
Linear interpolation rust-lang#71016 is a previous attempt at implementation that was closed by the author. I decided to reuse the feature request issue (rust-lang#71015) as a tracking issue. A member of the rust-lang org will have to edit the original post to be formatted correctly as I am not the issue's original author. The common name `lerp` is used because it is the term used by most code in a wide variety of contexts; it also happens to be the recently chosen name of the function that was added to C++20. To ensure symmetry as a method, this breaks the usual ordering of the method from `lerp(a, b, t)` to `t.lerp(a, b)`. This makes the most sense to me personally, and there will definitely be discussion before stabilisation anyway. Implementing lerp "correctly" is very dififcult even though it's a very common building-block used in all sorts of applications. A good prior reading is [this proposal](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0811r2.html#linear-interpolation) for the C++20 lerp which talks about the various guarantees, which I've simplified down to: 1. Exactness: `(0.0).lerp(start, end) == start` and `(1.0).lerp(start, end) == end` 2. Consistency: `anything.lerp(x, x) == x` 3. Monotonicity: once you go up don't go down Fun story: the version provided in that proposal, from what I understand, isn't actually monotonic. I messed around with a *lot* of different lerp implementations because I kind of got a bit obsessed and I ultimately landed on one that uses the fused `mul_add` instruction. Floating-point lerp lore is hard to come by, so, just trust me when I say that this ticks all the boxes. I'm only 90% certain that it's monotonic, but I'm sure that people who care deeply about this will be there to discuss before stabilisation. The main reason for using `mul_add` is that, in general, it ticks more boxes with fewer branches to be "correct." Although it will be slower on architectures without the fused `mul_add`, that's becoming more and more rare and I have a feeling that most people who will find themselves needing `lerp` will also have an efficient `mul_add` instruction available.
Implements lerp with feature flag: #71015