Rollup merge of #90296 - CAD97:rip-lerp, r=Mark-Simulacrum
Remove fNN::lerp Lerp is [surprisingly complex with multiple tradeoffs depending on what guarantees you want to provide](https://github.com/rust-lang/rust/issues/86269#issuecomment-869108301) (and what you're willing to drop for raw speed), so we don't have consensus on what implementation to use, let alone what signature - `t.lerp(a, b)` nicely puts `a, b` together, but makes dispatch to lerp custom types with the same signature basically impossible, and major ecosystem crates (e.g. nalgebra, glium) use `a.lerp(b, t)`, which is easily confusable. It was suggested to maybe provide a `Lerp<T>` trait and `t.lerp([a, b])`, which _could_ be implemented by downstream math libraries for their types, but also significantly raises the bar from a simple fNN method to a full trait, and does nothing to solve the implementation question. (It also raises the question of whether we'd support higher-order bezier interpolation.) The only consensus we have is the lack of consensus, and the [general temperature](https://github.com/rust-lang/rust/issues/86269#issuecomment-951347135) is that we should just remove this method (giving the method space back to 3rd party libs) and revisit this if (and likely only if) IEEE adds lerp to their specification. If people want a lerp, they're _probably_ already using (or writing) a math support library, which provides a lerp function for its custom math types and can provide the same lerp implementation for the primitive types via an extension trait. See also [previous Zulip discussion](https://rust-lang.zulipchat.com/#narrow/stream/219381-t-libs/topic/lerp.20API.20design) cc ``@clarfonthey`` (original PR author), ``@m-ou-se`` (original r+), ``@scottmcm`` (last voice in tracking issue, prompted me to post this) Closes #86269 (removed)
This commit is contained in:
commit
8871fe8bda
5 changed files with 0 additions and 191 deletions
|
@ -878,40 +878,4 @@ impl f32 {
|
|||
pub fn atanh(self) -> f32 {
|
||||
0.5 * ((2.0 * self) / (1.0 - self)).ln_1p()
|
||||
}
|
||||
|
||||
/// 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -757,66 +757,3 @@ fn test_total_cmp() {
|
|||
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f32::INFINITY));
|
||||
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&s_nan()));
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
|
|
|
@ -881,42 +881,6 @@ impl f64 {
|
|||
0.5 * ((2.0 * self) / (1.0 - self)).ln_1p()
|
||||
}
|
||||
|
||||
/// 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))
|
||||
}
|
||||
}
|
||||
|
||||
// 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).
|
||||
|
|
|
@ -753,58 +753,3 @@ fn test_total_cmp() {
|
|||
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f64::INFINITY));
|
||||
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&s_nan()));
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
|
|
|
@ -284,7 +284,6 @@
|
|||
#![feature(exact_size_is_empty)]
|
||||
#![feature(exhaustive_patterns)]
|
||||
#![feature(extend_one)]
|
||||
#![feature(float_interpolation)]
|
||||
#![feature(fn_traits)]
|
||||
#![feature(format_args_nl)]
|
||||
#![feature(gen_future)]
|
||||
|
|
Loading…
Add table
Reference in a new issue