Change:
<!-- Thank you for creating a tracking issue! Tracking issues are for tracking a feature from implementation to stabilization. Make sure to include the relevant RFC for the feature if it has one. If the new feature is small, it may be fine to skip the RFC process. In that case, you can use use in your initial implementation PR. The reviewer will ask you to open a tracking issue if they agree your feature can be without an RFC. --Feature gate: This is a tracking issue for the methods in the standard library. <!-- Include a short description of the feature. --<!-- For most library features, it'd be useful to include a summarized version of the public API. (E.g. just the public function signatures without their doc comments or implementation.) --<!-- For larger features, more steps might be involved. If the feature is changed later, please add those PRs here as well. --- [x] Earlier feature request: [x] Earlier (closed) implementation: [x] Implementation: [ ] Final commenting period (FCP) [ ] Stabilization PR <!-- Once the feature has gone through a few release cycles and there are no unresolved questions left, the feature might be ready for stabilization. If this feature didn't go through the RFC process, a final commenting period (FCP) is always needed before stabilization. This works as follows: A library team member can kick off the stabilization process, at which point the rfcbot will ask all the team members to verify they agree with stabilization. Once enough members agree and there are no concerns, the final commenting period begins: this issue will be marked as such and will be listed in the next This Week in Rust newsletter. If no blocking concerns are raised in that period of 10 days, a stabilzation PR can be opened by anyone. --<!-- Include any open questions that need to be answered before the feature can be stabilised. If multiple (unrelated) big questions come up, it can be a good idea to open a separate issue for each, to make it easier to keep track of the discussions. It's useful to link any relevant discussions and conclusions (whether on GitHub, Zulip, or the internals forum) here. --- Should we have all of the guarantees of ? What specific mathematical version of lerp should we use? Is the calling convention good? Normally the order is but we have t.lerp(a, b)` here. Should we have an inverse lerp method? What should it be called?
Considering this is isn't C++, don't you want to give it a more descriptive name, like "linear_interpolation"?
In this particular case I think it is good to copy the name. It is associated with this specific formula and the convention is used in other libraries and languages. I think it is better to not have a parameter for the same reason min/max functions do not have a parameter. None of the inputs are self-evidently the "primary" input. and are a pair and is the "other" parameter, but that does not make "primary". Perhaps you may conceptualize the formula in a way that feels primary, but that is not the most natural/common way to conceptualize it IMO. I think it is more natural to think of the inputs in the order . "Start. End. Position between the two."
In Rust you can write .
Huh TIL
Yeah, lerp is a standard name for this, like signum, min, or max. We don't write out minimum or maximum for those methods. ;)
Given that a significant part of the advantage to lerp is that it's super fast and trivial, it'll be interesting to sell using an implementation other than the two standard implementations ( (monotonic) or (exact for )). Especially on , being fast and branchless might be prioritized over being exact, consistent, and monotonic. Exact and consistent are commonly selected for, but monotonicity is rarely considered key, given lerp is typically (in my domain, games) used primarily for visual output, where small deviation doesn't matter much. That said, the given implementation of (that is, ) is promising, as it's basically equivalent to the second, but... It's just , so we can check a full series for a given min/max, and... it's not monotonic. As just one example, (Min and max were just chosen randomly here.) My code found pairs of adjacent float s that were not monotonic. But that said, I don't think we should care about monotonicity. What actually potentially matters, in my eyes at least, is boundedness; that is, that for any (finite) , (flipped if ). As far as I've found, this implementation does uphold that requirement. According to Wikipedia at least, both and are in use. For method use, seems unambiguous to me, but is ambiguous again. A small survey of existing math/linalg crates' implementations (collected from rdeps): : : : : : : : : provides both ("using the supposedly fastest but less precise implementation") and ("using a possibly slower but more precise operation"). emath has a great solution to use here: ! That is, use , so it's called as or . No confusion necessary! As far as I can tell, the only somewhat common name is or (in contrast to ).
An alternative option is to add to instead of , so that it's or . This would be interesting, but I think potentially problematic for the core/std split? As well as much more difficult to discover.
The current choice of lerp signature appears to be unfortunate for two reasons: First, it is impossible to be consistent with implementation while implementing lerp for custom alebraic types (such as Vec3 or Quat etc.). One would not be able to extend in an another crate to add interpolation of additional types.
Second, it is inconsistent with most of existing algebraic crates in the ecosystem. As has mentioned above: , , , , , and all are using signature. That is a lot of breaking changes to be done to stay consistent with new std implementation.
Note that there is the unfortunate but possible implementation of requiring a function call style, e.g. , which would not be equivalent to those other implementations but would be much less breaking.
One downside is that it would require type names to be stated explicitly, which is not the case with many other methods such as as or /. And this only addresses the first issue.
That is an angle I hadn't realized, and consistent even for using . can be applied to any custom lerpable type, as they can always implement their own methods. If we want the ecosystem to be able to also use , though, we need to use a trait as an extension point, roughly and it's starting to get kind of messy, due to how overloading works in Rust. Assuming we only lerp between two values of the same type, we effectively want to do double dispatch, on the bitwidth of and (more importantly) the type of the value being interpolated. That, at least, speaks to putting the dispatch on the interpolated type. This brings me back to the possibility of providing it as , as downstream can add trait implementations to add new to different concretizations of . (Though it makes me wish more that range types were instead of ... but w/e that ship has sailed long ago.) Although, on the other hand, the intricacy here makes me wonder whether (for scalars) should live in , for the same reason was extracted from the stdlib to begin with.
I feel like it is a smart, but complicated solution to this problem.
Consider that some implementations of are not identical to f32. For example, Quaternions are being normalized in after interpolation:
(GitHub issues are not a great medium for in-depth back-and-forth: consider discussing .) Here are a few notes about guarantees. For finite inputs, we would like to provide guarantees that the function is <dl<dtexact</dt<dd(v0, v1) == v0</codeand (v0, v1) == v1</code</dd<dtmonotonic</dt<dd<codecmp(v0, v1) == cmp(t.lerp(v0, v1), nextfloat(t).lerp(v0, v1))</code</dd<dtconsistent</dt<dd(v, v) == v</code</dd<dtbounded</dt<ddfor <codet</codein <code0.0..=1.0</code, (v0, v1)</codein <codemin(v0, v1)..=max(v0, v1)</code</dlNote that bounded implies consistent (while in ), and that an implementation being bounded and/or consistent falls out of an implementation being both exact and monotonic, but if an implementation is not exact and/or not monotonic, it is better for it to still be bounded and/or consistent. Separately, an implementation is said to be clamped if it returns a value in no matter the (it logically clamps into ). An implementation is unclamped if it allows values outside to extrapolate outside of that range. For each potential implementation, I'll provide the x64 assembly as generated by for both and for a between two known, increasing prime values, as an eyeball of relative performance characteristics, both with the default CPU target and with (which allows inlining FMA). I think using relaxed math and allowing the compiler to provide the FMA in optimization might be better, depending on exact guarantees and such. The simplest implementation (and the one that many gamedevs will reach for initially) is monotonic and consistent, but not quite exact nor bounded. <details<summaryASM</summary</detailsThis is monotonic because as increases, the result of the FMA strictly increases. It's consistent, because when , , so the FMA will just return . This is not exact (and similarly not bounded) because may not necessarily equal for values of significantly different enough scale, and will even overflow if and are of largest exponent and opposite signs. (IIUC, it could vary by up to ±1 ULP if the subtraction and addition round in the same direction, if no overflow happens.) This might be enough if we say that is not exact but is accurate to ±1 ULP rather than ±½ ULP (and document the overflow condition), assuming my understanding is correct. While it does not recover the exact quality, it's rather trivial to recover boundedness for a clamped lerp, by clamping to the range of , or even just clamping . <details<summaryASM</summary</detailsThis, if I understand correctly, fulfills all of the above defined properties for a clamped lerp, except that the returned value for may be 1 ULP short. It's also worth noting that while the prior implementation contains no panicking code paths, this implementation still contains panicking code, I assume for some combination of nonfinite inputs. This is the other simple implementation, and the one the other portion of gamedevs will reach for. I've written the simple definition you'll often see, but the current double FMA implementation is algebraically equivalent to it, just with factored to . <details<summaryASM</summaryNote that this doesn't use explicit FMA, but could use at least one. </detailsThis implementation is exact, but it is not monotonic nor consistent, nor even bounded. Again, clamping recovers boundedness and consistency, so I've included that implementation as well for completeness. <details<summaryASM</summary</detailsThis implementation also contains panicking control flow, presumably for some combination of nonfinite inputs. This is the last implementation that I've personally seen in use, though I know some others exist. This adds a consistency check to our exact implementation, but still fails at being monotonic or bounded. <details<summaryASM</summary</detailsThis is the one I currently use, though I might reconsider after doing this analysis. Note also that this doesn't actually contain any branching assembly, just a cmov, despite having a branch in the source. And finally, the proposed implementation from . I make no assertion whether it does or doesn't meet the promised properties, just reproduce it here. <details<summaryASM</summary</detailsThe fact that this is so branchy, even for constant and , means that (to a first order of approximation) no gamedev is ever going to use this implementation of . I think it would be a useful exercise to dig up what (if any) guarantees GPU shaders give on their implementation of /, as well as hardware barycentric interpolations within triangles. Unfortunately I wasn't able to find any documentation on this, though I'm not familiar enough with GPU/shader documentation to realistically find such information.
Excerpts from lerp/mix used in shading languages: Khronos documentation on GLSL : NVIDIA Cg documentation on : Microsoft HLSL documentation on : WGSL on : Note that lerp does not do additional clamping there and can be used to extrapolate values.
in : Note that they also use signature there.
In terms of the calling convention, I personally feel very strongly about not splitting the bounds between and the method arguments, but I'm not the only person in the rust community and I think that if a reasonable number of people are concerned about breakage that it's worth opening a PR to change it.
Just wanted to make sure you're aware that a good amount of discussion has also happened on Zulip at https://rust- , if you'd like to also weigh in there.
The calling convention for this bit me, I've never encountered a library where lerp was (t, a, b) rather than (a, b, t), and (foolishly) assumed that the warning for there being a new function that collides with my own lerp interpolation on f32 I'd just be able to swap them right over. I'm ambivalent on whether this should be implemented on ranges rather than floats (or even both), but please make the calling convention. I note that in the small survey of rust crates not a single one of them has t first.
seems to be more common in more mathy applications, where seems to be more common in more gamey applications. Right now, based on the discussion in Zulip, my current feeling is that the next task here is to open a community RFC to determine 1) what properties of are most desired to uphold for different applications, and which ones it's practical for std to provide; 2) from that, what implementation of lerp should be considered as the "standard" lerp; and 3) how a std lerp should be provided, such that not only / are available to be lerped, but also and , and that those can be used as the value in interpolation for wide enough types as well, with a compatible signature. As a stretch goal, it would be nice if the signature could also be easily extended to support higher-order bezier interpolation (but that's a low order desire). (To be clear, I know how it can be done, but it's a question of supporting lerp first.)
That was exactly my experience: first reaction: sweet, there is now standard lerp coming. Second: I have to reorder my lerp arguments, but only for f32.
Any API for this in std should balance user expectation with Rust idioms. I'm sympathetic to the familiarity of from other languages. At the same time, reads like an abuse of method syntax. If it must be , and if it isn't , then make this an associated function rather than a method: . Also note that Rust isn't exactly a stranger to adapting APIs to make them more Rust-like, even at the expense of familiarity: see how deliberately has its arguments swapped compared to its C equivalent , prioritizing internal consistency over cross-language consistency.
Well, if you want to be last, how about: Example use:
I use in several places, it feels perfectly natural to me. Read as "lerp A towards B by T". There are other methods on f32 in the standard library that might feel similarly "weird" to you, but are still not associated functions: If you don't like the look of them you can always just call them using associated function syntax. And what I am asking for here is not (just) familiarity from other languages, but the existing rust ecosystem.
Functions like are part of my point, as they are widely derided. That you can use UFCS to call them as is true, but also highly obscure; I have had to explain this feature to many an experienced Rust user who came to me griping about . The rationalization for the existence of is 1) we want a trait, so that people can bound on , and because of how methods work we have to pick something to be the receiver, 2) there are only two arguments, so one of them has to be the receiver, and 3) even though isn't implemented on floats, we want the interface to be consistent with integers, so we implement this as an inherent method. But I don't see that these same rationalizations hold for lerp: 1) although people in here have proposed traits, the original PR just adds these as inherent methods on floats, 2) there are more than two arguments, so we don't need to split the receiver across the boundaries of a conceptual range (yuck); we can make the third argument the receiver (which users find unfamiliar) or we can coalesce the boundary arguments into a range, tuple, or array (which I see no arguments against yet), 3) there is no consistency to be be gained from making these inherent methods on floats since there is no other equivalent method in std that we are trying to emulate; they can easily just be associated functions.
They are? If that's the case, shouldn't the examples in the docs at least be calling them as then? how methods work we have to pick something to be the receiver, 2) there are only two arguments, so one of them has to be the receiver, and 3) even though isn't implemented on floats, we want the interface to be consistent with integers, so we implement this as an inherent method. 1 and 3 here don't hold for and . My argument against this would be that you're introducing complexity where it doesn't need to exist, and would add noise to call sites. Granted, not a strong argument, but the benefits aren't particularly strong either.
If lerp becomes a method of it becomes impossible (or at least difficult) to overload it for other algebraic types such as vectors or quaternions. All of these will require construction of useless temporary objects. There is a number of existing algebraic crates (listed above) that use convention. A single std-function is unlikely to change this convention, this will only introduce fragmentation, misunderstanding and inconsistency into the ecosystem.
It's simple with a trait: rust pub trait Lerp<T = [Self; 2]: Sized { type Output; fn lerp(self, controlpoints: T) -Self::Output; } impl Lerp<[Vec3; 2]for f32 { type Output = Vec3; fn lerp(self, controlpoints: [Vec3; 2]) -Self::Output { (1.0 - self) controlpoints[0] + self controlpoints[1] } }
They're not useless, arrays allow easily being generic over and higher-order Bezier Curves, while also making it extremely obvious which arguments are the endpoints and which argument is . Just seeing you can't tell for sure if is or without having to go see which specific definition the code's author is using today. For or it's completely obvious that any sane API designer would have be and are the endpoints.
There is Rust code that uses : A project I partially finished several years ago: I'm probably far from the only one...
Use of array causes suboptimal code generation (both debug and optimized) even for f32. Here is an example: For larger types this trait is likely to cause two undesired copies.
They produce identical code with optimizations on once the s are decorated with like they should be in : If optimizations are turned off, passing arrays/separate-args to is the least of your worries... That's inherent in having larger types passed across non-inlined function call boundaries -- caused by current rustc inheriting C's poor ABI decisions of not binpacking function arguments'/returns' fields in registers -- nothing specific to .
additional crate that uses : Has nearly 67k downloads.
Since 1.55 the following code doesn't compile anymore: Because of Even though I can't enable the feature as I'm using stable. How could I fix this on stable until this feature is stabilised?
instead of invoking it through is one way.
I thought that unstable library features wouldn't be preferred if you didn't have the features enabled?
That's if the two items are ambiguous / are both trait methods, IIRC. Here is an inherent method, so it's prioritized over trait methods, even as unstable, apparently. If we aren't actively pursuing progress on deciding the correct implementation and API for a std <sup[1]</sup, perhaps we should consider removing it for the time being to give the method space back. (I also didn't realize this was going to be an issue.) [1] and as a reminder, I consider 3rd party math types being able to offer the same lerp API a hard requirement, because having differing APIs by requirements is awful
Given the complexity here, this is where I think I lean right now. Especially since so much of the discussion is about "gamey" uses, where there's going to be another math library involved anyways for interpolation of vectors. That library can just provide it for scalars too (by an extension trait if it must). And maybe that's true for non-gamey uses too? Like perhaps those cases are all using ndarray or similar, which could provide this instead. Then Rust can wait, and add one later should IEEE 754 decide to add it to a future version of their spec. FWIW there are many other floating-point methods which are also ±1 ULP, like , so that wouldn't be particularly out of place. Though I don't know that I'd expect this one to be worse than ±½ ULP.
RIP std . Long live
For posterity, this is actually a bit subtle than those docs seem to suggest. Per from the Vulkan spec: (emphasis ) This caused me considerable pain in an application where I absolutely required the "exact" property, but nVidia implemented as , whereas my graphics debugger implemented it as , which made it very difficult to track down!
Why not?: Usage:
This has been closed, so, it's unlikely this would be accepted without a new ACP. However, there's still the issue of vs. which is unlikely to be resolved at this point. We currently have as a stable API so stabilising seems like a bad idea. Your suggestion has been made in various forms above in the discussion already, so, I would suggest reading through and seeing why the decision was made to simply close this. I think that, if we were to push the existing ecosystem to adopt and resolve the issues about what guarantees are worthwhile in the standard library, we could maybe make an ACP with these updates and potentially get this in. But without that, it seems highly unlikely.

Discussion:
pub fn atanh(self) -> f32 { 0.5 * ((2.0 * self) / (1.0 - self)).ln_1p() } <del> /// Linear interpolation between `start` and `end`. /// /// This enables linear interpolation between `start` and `end`, where start is represented by /// `self == 0.0` and `end` is represented by `self == 1.0`. This is the basis of all /// "transition", "easing", or "step" functions; if you change `self` from 0.0 to 1.0 /// at a given rate, the result will change from `start` to `end` at a similar rate. /// /// Values below 0.0 or above 1.0 are allowed, allowing you to extrapolate values outside the /// range from `start` to `end`. This also is useful for transition functions which might /// move slightly past the end or start for a desired effect. Mathematically, the values /// returned are equivalent to `start + self * (end - start)`, although we make a few specific /// guarantees that are useful specifically to linear interpolation. /// /// These guarantees are: /// /// * If `start` and `end` are [finite], the value at 0.0 is always `start` and the ///   value at 1.0 is always `end`. (exactness) /// * If `start` and `end` are [finite], the values will always move in the direction from ///   `start` to `end` (monotonicity) /// * If `self` is [finite] and `start == end`, the value at any point will always be ///   `start == end`. (consistency) /// /// [finite]: #method.is_finite #[must_use = "method returns a new number and does not mutate the original value"] #[unstable(feature = "float_interpolation", issue = "86269")] pub fn lerp(self, start: f32, end: f32) -> f32 { // consistent if start == end { start // exact/monotonic } else { self.mul_add(end, (-self).mul_add(start, start)) } } </del> }

assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f32::INFINITY)); assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&s_nan())); } <del> #[test] fn test_lerp_exact() { // simple values assert_eq!(f32::lerp(0.0, 2.0, 4.0), 2.0); assert_eq!(f32::lerp(1.0, 2.0, 4.0), 4.0); // boundary values assert_eq!(f32::lerp(0.0, f32::MIN, f32::MAX), f32::MIN); assert_eq!(f32::lerp(1.0, f32::MIN, f32::MAX), f32::MAX); } #[test] fn test_lerp_consistent() { assert_eq!(f32::lerp(f32::MAX, f32::MIN, f32::MIN), f32::MIN); assert_eq!(f32::lerp(f32::MIN, f32::MAX, f32::MAX), f32::MAX); // as long as t is finite, a/b can be infinite assert_eq!(f32::lerp(f32::MAX, f32::NEG_INFINITY, f32::NEG_INFINITY), f32::NEG_INFINITY); assert_eq!(f32::lerp(f32::MIN, f32::INFINITY, f32::INFINITY), f32::INFINITY); } #[test] fn test_lerp_nan_infinite() { // non-finite t is not NaN if a/b different assert!(!f32::lerp(f32::INFINITY, f32::MIN, f32::MAX).is_nan()); assert!(!f32::lerp(f32::NEG_INFINITY, f32::MIN, f32::MAX).is_nan()); } #[test] fn test_lerp_values() { // just a few basic values assert_eq!(f32::lerp(0.25, 1.0, 2.0), 1.25); assert_eq!(f32::lerp(0.50, 1.0, 2.0), 1.50); assert_eq!(f32::lerp(0.75, 1.0, 2.0), 1.75); } #[test] fn test_lerp_monotonic() { // near 0 let below_zero = f32::lerp(-f32::EPSILON, f32::MIN, f32::MAX); let zero = f32::lerp(0.0, f32::MIN, f32::MAX); let above_zero = f32::lerp(f32::EPSILON, f32::MIN, f32::MAX); assert!(below_zero <= zero); assert!(zero <= above_zero); assert!(below_zero <= above_zero); // near 0.5 let below_half = f32::lerp(0.5 - f32::EPSILON, f32::MIN, f32::MAX); let half = f32::lerp(0.5, f32::MIN, f32::MAX); let above_half = f32::lerp(0.5 + f32::EPSILON, f32::MIN, f32::MAX); assert!(below_half <= half); assert!(half <= above_half); assert!(below_half <= above_half); // near 1 let below_one = f32::lerp(1.0 - f32::EPSILON, f32::MIN, f32::MAX); let one = f32::lerp(1.0, f32::MIN, f32::MAX); let above_one = f32::lerp(1.0 + f32::EPSILON, f32::MIN, f32::MAX); assert!(below_one <= one); assert!(one <= above_one); assert!(below_one <= above_one); } </del>

0.5 * ((2.0 * self) / (1.0 - self)).ln_1p() } <del> /// Linear interpolation between `start` and `end`. /// /// This enables linear interpolation between `start` and `end`, where start is represented by /// `self == 0.0` and `end` is represented by `self == 1.0`. This is the basis of all /// "transition", "easing", or "step" functions; if you change `self` from 0.0 to 1.0 /// at a given rate, the result will change from `start` to `end` at a similar rate. /// /// Values below 0.0 or above 1.0 are allowed, allowing you to extrapolate values outside the /// range from `start` to `end`. This also is useful for transition functions which might /// move slightly past the end or start for a desired effect. Mathematically, the values /// returned are equivalent to `start + self * (end - start)`, although we make a few specific /// guarantees that are useful specifically to linear interpolation. /// /// These guarantees are: /// /// * If `start` and `end` are [finite], the value at 0.0 is always `start` and the ///   value at 1.0 is always `end`. (exactness) /// * If `start` and `end` are [finite], the values will always move in the direction from ///   `start` to `end` (monotonicity) /// * If `self` is [finite] and `start == end`, the value at any point will always be ///   `start == end`. (consistency) /// /// [finite]: #method.is_finite #[must_use = "method returns a new number and does not mutate the original value"] #[unstable(feature = "float_interpolation", issue = "86269")] pub fn lerp(self, start: f64, end: f64) -> f64 { // consistent if start == end { start // exact/monotonic } else { self.mul_add(end, (-self).mul_add(start, start)) } } </del> // Solaris/Illumos requires a wrapper around log, log2, and log10 functions // because of their non-standard behavior (e.g., log(-n) returns -Inf instead // of expected NaN).

assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f64::INFINITY)); assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&s_nan())); } <del> #[test] fn test_lerp_exact() { // simple values assert_eq!(f64::lerp(0.0, 2.0, 4.0), 2.0); assert_eq!(f64::lerp(1.0, 2.0, 4.0), 4.0); // boundary values assert_eq!(f64::lerp(0.0, f64::MIN, f64::MAX), f64::MIN); assert_eq!(f64::lerp(1.0, f64::MIN, f64::MAX), f64::MAX); } #[test] fn test_lerp_consistent() { assert_eq!(f64::lerp(f64::MAX, f64::MIN, f64::MIN), f64::MIN); assert_eq!(f64::lerp(f64::MIN, f64::MAX, f64::MAX), f64::MAX); // as long as t is finite, a/b can be infinite assert_eq!(f64::lerp(f64::MAX, f64::NEG_INFINITY, f64::NEG_INFINITY), f64::NEG_INFINITY); assert_eq!(f64::lerp(f64::MIN, f64::INFINITY, f64::INFINITY), f64::INFINITY); } #[test] fn test_lerp_nan_infinite() { // non-finite t is not NaN if a/b different assert!(!f64::lerp(f64::INFINITY, f64::MIN, f64::MAX).is_nan()); assert!(!f64::lerp(f64::NEG_INFINITY, f64::MIN, f64::MAX).is_nan()); } #[test] fn test_lerp_values() { // just a few basic values assert_eq!(f64::lerp(0.25, 1.0, 2.0), 1.25); assert_eq!(f64::lerp(0.50, 1.0, 2.0), 1.50); assert_eq!(f64::lerp(0.75, 1.0, 2.0), 1.75); } #[test] fn test_lerp_monotonic() { // near 0 let below_zero = f64::lerp(-f64::EPSILON, f64::MIN, f64::MAX); let zero = f64::lerp(0.0, f64::MIN, f64::MAX); let above_zero = f64::lerp(f64::EPSILON, f64::MIN, f64::MAX); assert!(below_zero <= zero); assert!(zero <= above_zero); assert!(below_zero <= above_zero); // near 1 let below_one = f64::lerp(1.0 - f64::EPSILON, f64::MIN, f64::MAX); let one = f64::lerp(1.0, f64::MIN, f64::MAX); let above_one = f64::lerp(1.0 + f64::EPSILON, f64::MIN, f64::MAX); assert!(below_one <= one); assert!(one <= above_one); assert!(below_one <= above_one); } </del>

#![feature(exact_size_is_empty)] #![feature(exhaustive_patterns)] #![feature(extend_one)] <del> #![feature(float_interpolation)] </del> #![feature(fn_traits)] #![feature(format_args_nl)] #![feature(gen_future)]